Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion FusionIIIT/Fusion/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
url(r'^complaint/', include('applications.complaint_system.urls')),
url(r'^healthcenter/', include('applications.health_center.urls')),
url(r'^leave/', include('applications.leave.urls')),
url(r'^placement/', include('applications.placement_cell.urls')),
url(r'^placement/', include('applications.placement_cell.api.urls')),
url(r'^filetracking/', include('applications.filetracking.urls')),
url(r'^spacs/', include('applications.scholarships.urls')),
url(r'^visitorhostel/', include('applications.visitor_hostel.urls')),
Expand Down
166 changes: 166 additions & 0 deletions FusionIIIT/applications/placement_cell/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# Placement Cell

The Placement Cell module manages the full campus placement lifecycle for
IIITDM Jabalpur — company drives, student applications and offers, interview
rounds, statistics and reports, debarments and restrictions, an interactive
placement calendar, an alumni network, and off-campus / published-CPI tracking.

It is an API-only Django app (`applications/placement_cell`) consumed by the
React placement module in `Fusion-client` (`src/Modules/PlacementCell`).

---

## Roles

The module recognises four roles (the user's selected designation, exposed to
the frontend as `state.user.role`):

| Role (designation) | Capabilities |
|----------------------|--------------|
| `placement officer` | TPO: schedules/drives, applications, interview rounds, statistics, reports, debarments, restrictions, fields, notifications, off-campus records, published-CPI export, calendar management, announcements |
| `placement chairman` | Admin oversight: officer capabilities plus placement policies |
| `student` | Browse drives, apply, manage placement profile, view offers/timeline, download CV, read announcements/calendar, alumni network |
| `alumni` | Alumni profile, post job referrals, mentorship sessions, student network |

**How roles resolve**

- **Backend** authorizes via `HoldsDesignation(working=user, designation__name=…)`.
`selectors.is_tpo` is true for `placement officer` / `placement chairman`;
officer/chairman-only endpoints return `403` with `{ "detail": … }`.
- **Sidebar visibility** comes from `ModuleAccess.placement_cell` for the
designation.
- **Frontend** chooses the tab set in `PlacementCellPage` from `state.user.role`.

---

## Features

### Students
- **Placement Schedule** — browse drives as a chronological **Agenda**
(Today / This Week / Upcoming / Closed) or a filterable **card** view, with
per-drive eligibility and deadline countdowns; apply, withdraw, track status.
- **My Applications / My Offers** — application timeline; accept/decline offers.
- **Placement Calendar** — read-only, colour-coded view of drives, tests,
interviews and deadlines.
- **Announcements** — read placement-cell announcements.
- **Download CV**, placement profile, notification preferences.
- **Alumni Network** — connect with alumni, browse referrals, mentorship.

### Placement Officer / Chairman
- **Add / edit drives** with eligibility (min CPI, branches from the live
department list, passout year, gender) and custom **application fields**
(creatable inline).
- **Student CPI** — per-batch published CPI (computed from the examination
module's announced results), with off-campus companies and **Excel export**.
- **Off-Campus Placements** — record offers students received off campus
(company autocomplete from registered companies).
- **Placement Statistics & Reports** — stats, report generation and export.
- **Debarred Students** and **Restrictions** (institute-wide eligibility bars).
- **Send Notifications**, **Company Registration**, **Fields** management.
- **Placement Calendar** — Google-Calendar-style: click a date/slot to add an
event, edit/delete events; merged with drives and deadlines.
- **Announcements** — post / pin / delete.
- **Placement Appeals**, **Higher Studies**, **Alumni Verification**.

### Chairman
- All officer capabilities plus **Placement Policies**.

---

## Backend

- **Models** (`models.py`, ~45): profiles & academic info (Education, Skill,
Experience, …), `NotifyStudent`, `PlacementSchedule`, `PlacementApplication`,
`PlacementStatus`, `PlacementRound`, `PlacementRecord`, `StudentRecord`,
`PlacementRestriction`, `PlacementPolicy`, `PlacementAppeal`, alumni models,
and the additive `PlacementAnnouncement`, `OffCampusPlacement`,
`PlacementCalendarEvent`.
- **API** (`api/urls.py`, `api/views.py`, `api/serializers.py`): DRF, Token
authentication. URLs are mounted under `placement/` (see `Fusion/urls.py`),
so routes are `…/placement/api/<route>/`.

### Endpoint groups

| Area | Routes |
|------|--------|
| Drives & schedule | `api/placement/`, `api/placement/<id>/`, `api/calender/`, `api/timeline/<id>/`, `api/nextround/<id>/` |
| Applications & offers | `api/apply-for-placement/`, `api/my-applications/`, `api/my-offers/`, `api/offer/<id>/`, `api/offer/<id>/respond/`, `api/student-applications/<id>/`, `api/application-detail/<id>/`, `api/download-applications/<id>/` |
| Statistics & reports | `api/statistics/`, `api/delete-statistics/<id>/`, `api/reports/`, `api/reports/export/`, `api/report-schedules/`, `api/higher-studies/` |
| Eligibility & policy | `api/restrictions/`, `api/policies/`, `api/branches/` |
| Debarment | `api/debared-students/`, `api/debared-status/<roll_no>/` |
| Fields & profile | `api/add-field/`, `api/form-fields/`, `api/profile/`, `api/notification-preferences/`, `api/registration/`, `api/generate-cv/` |
| Notifications | `api/send-notification/` |
| Announcements | `api/announcements/`, `api/announcements/<id>/` |
| Off-campus | `api/offcampus/`, `api/offcampus/<id>/` |
| Published CPI | `api/cpi-batches/`, `api/cpi-students/` (`?batch_id=` , `?export=excel`) |
| Calendar events | `api/calendar-events/`, `api/calendar-events/<id>/` |
| Appeals | `api/placement-appeals/`, `api/placement-appeals/<id>/` |
| Alumni | `api/alumni/profile/`, `api/alumni/directory/`, `api/alumni/verification/`, `api/alumni/referrals/`, `api/alumni/connections/`, `api/alumni/sessions/` |

**Authorization** — all endpoints require authentication; write/sensitive
operations are gated on `selectors.is_tpo` (officer/chairman). Server-controlled
fields (`created_by`, `added_by`, `posted_by`) are never client-settable.

### Published CPI

`selectors.get_student_published_cpi` derives a student's CPI from the
examination module's latest **announced** `ResultAnnouncement` (not the static
`Student.cpi`). The per-batch view memoises each student's computed CPI in the
cache (keyed by roll number + semester), so reloading a batch is near-instant.

---

## Frontend (`Fusion-client/src/Modules/PlacementCell`)

- React 18 + Vite, Mantine v7, Redux Toolkit, axios, `mantine-react-table`,
`react-big-calendar`.
- `pages/PlacementCellPage.jsx` renders the shared `ModuleTabs` navbar and the
role-specific tab set. **Every tab is lazy-loaded** behind a `Suspense`
boundary so opening the module only downloads the active tab's code; Vite
`manualChunks` splits the heavy vendors into cacheable chunks.
- `api.js` (+ `services/api.js` re-export) holds `placementApi` with one method
per endpoint and `buildAuthConfig()` for the token header.
- Date inputs use native `datetime-local` / `date` controls for reliability.

---

## Setup — role accounts

`manage.py setup_placement_roles` creates one idempotent login per role and
enables `ModuleAccess.placement_cell`. The password is supplied at runtime and
is **not** stored in the repo:

```bash
cd FusionIIIT
python manage.py setup_placement_roles --password '<password>'
# or set PLACEMENT_ROLE_PASSWORD
```

| Username | Role |
|----------------------|--------------------|
| `placement_officer` | placement officer |
| `placement_chairman` | placement chairman |
| `placement_student` | student |
| `placement_alumni` | alumni |

---

## Tests

See [`tests/README.md`](tests/README.md). Run with the dedicated settings
(migrations disabled so the historical chain cannot block test-DB creation):

```bash
cd FusionIIIT
python manage.py test \
applications.placement_cell.tests.test_placement_api \
applications.placement_cell.tests.test_use_cases \
applications.placement_cell.tests.test_business_rules \
applications.placement_cell.tests.test_workflows \
applications.placement_cell.tests.test_module \
--settings=test_settings
```

`test_placement_api` covers schema regressions, API-only URL wiring,
authentication and role authorization (including announcements, off-campus,
published-CPI export and calendar-event CRUD).
1 change: 1 addition & 0 deletions FusionIIIT/applications/placement_cell/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

2 changes: 1 addition & 1 deletion FusionIIIT/applications/placement_cell/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class StudentRecordAdmin(admin.ModelAdmin):


class ChairmanVisitAdmin(admin.ModelAdmin):
list_display = ('company_name', 'location', 'visiting_date', 'timestamp')
list_display = ('company_name', 'location', 'visiting_date', 'start_date', 'end_date', 'timestamp')


class PlacementScheduleAdmin(admin.ModelAdmin):
Expand Down
1 change: 1 addition & 0 deletions FusionIIIT/applications/placement_cell/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

85 changes: 83 additions & 2 deletions FusionIIIT/applications/placement_cell/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,16 @@
from applications.placement_cell.models import (Achievement, Course, Education,
Experience, Has, Patent,
Project, Publication, Skill,
PlacementStatus, NotifyStudent)
PlacementAppeal, PlacementStatus,
NotifyStudent, PlacementAnnouncement,
OffCampusPlacement,
PlacementCalendarEvent)


class PlacementAppealSerializer(serializers.ModelSerializer):
class Meta:
model = PlacementAppeal
fields = '__all__'

class SkillSerializer(serializers.ModelSerializer):

Expand All @@ -17,7 +26,7 @@ class HasSerializer(serializers.ModelSerializer):

class Meta:
model = Has
fields = ('skill_id','skill_rating')
fields = ('id', 'skill_id', 'skill_rating')

def create(self, validated_data):
skill = validated_data.pop('skill_id')
Expand All @@ -28,6 +37,22 @@ def create(self, validated_data):
raise serializers.ValidationError({'skill': 'This skill is already present'})
return has_obj

def update(self, instance, validated_data):
skill = validated_data.pop('skill_id', None)
if skill:
skill_id, _ = Skill.objects.get_or_create(**skill)
duplicate = Has.objects.filter(
unique_id=instance.unique_id,
skill_id=skill_id,
).exclude(pk=instance.pk).exists()
if duplicate:
raise serializers.ValidationError({'skill': 'This skill is already present'})
instance.skill_id = skill_id
if 'skill_rating' in validated_data:
instance.skill_rating = validated_data['skill_rating']
instance.save()
return instance

class EducationSerializer(serializers.ModelSerializer):

class Meta:
Expand Down Expand Up @@ -82,3 +107,59 @@ class PlacementStatusSerializer(serializers.ModelSerializer):
class Meta:
model = PlacementStatus
fields = ('notify_id', 'invitation', 'placed', 'timestamp', 'no_of_days')


class PlacementAnnouncementSerializer(serializers.ModelSerializer):
posted_by_name = serializers.SerializerMethodField()

class Meta:
model = PlacementAnnouncement
fields = ('id', 'title', 'body', 'posted_by_name', 'posted_at', 'is_pinned')
read_only_fields = ('id', 'posted_at')

def get_posted_by_name(self, obj):
if obj.posted_by:
full_name = '{} {}'.format(
obj.posted_by.first_name, obj.posted_by.last_name
).strip()
return full_name or obj.posted_by.username
return None


class PlacementAnnouncementWriteSerializer(serializers.ModelSerializer):

class Meta:
model = PlacementAnnouncement
fields = ('title', 'body', 'is_pinned')


class OffCampusPlacementSerializer(serializers.ModelSerializer):
roll_no = serializers.CharField(source='student.user.username', read_only=True)
student_name = serializers.SerializerMethodField()

class Meta:
model = OffCampusPlacement
fields = ('id', 'roll_no', 'student_name', 'company_name', 'role',
'offer_type', 'ctc', 'stipend', 'offer_date', 'notes', 'created_at')
read_only_fields = ('id', 'created_at')

def get_student_name(self, obj):
user = obj.student.user
return '{} {}'.format(user.first_name, user.last_name).strip()


class OffCampusPlacementWriteSerializer(serializers.ModelSerializer):

class Meta:
model = OffCampusPlacement
fields = ('student', 'company_name', 'role', 'offer_type',
'ctc', 'stipend', 'offer_date', 'notes')


class PlacementCalendarEventSerializer(serializers.ModelSerializer):

class Meta:
model = PlacementCalendarEvent
fields = ('id', 'title', 'description', 'start', 'end', 'all_day',
'category', 'location', 'created_at')
read_only_fields = ('id', 'created_at')
70 changes: 70 additions & 0 deletions FusionIIIT/applications/placement_cell/api/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from django.conf.urls import url

from . import views

app_name = "placement"

urlpatterns = [
url(r"^api/placement/$", views.placement_api, name="placement_api"),
url(r"^api/placement/(?P<schedule_id>[0-9]+)/$", views.placement_detail_api, name="placement_detail_api"),
url(r"^api/statistics/$", views.placement_statistics_api, name="placement_statistics_api"),
url(r"^api/reports/$", views.placement_reports_api, name="placement_reports_api"),
url(r"^api/reports/export/$", views.placement_reports_export_api, name="placement_reports_export_api"),
url(r"^api/report-schedules/$", views.placement_report_schedules_api, name="placement_report_schedules_api"),
url(r"^api/report-schedules/(?P<schedule_id>[0-9]+)/$", views.placement_report_schedule_detail_api, name="placement_report_schedule_detail_api"),
url(r"^api/delete-statistics/(?P<record_id>[0-9]+)/$", views.delete_placement_statistics_api, name="delete_placement_statistics_api"),
url(r"^api/higher-studies/$", views.higher_studies_api, name="higher_studies_api"),
url(r"^api/higher-studies/(?P<record_id>[0-9]+)/$", views.higher_studies_detail_api, name="higher_studies_detail_api"),
url(r"^api/registration/$", views.registration_api, name="registration_api"),
url(r"^api/add-field/$", views.placement_fields_api, name="placement_fields_api"),
url(r"^api/form-fields/$", views.form_fields_api, name="form_fields_api"),
url(r"^api/profile/$", views.placement_profile_api, name="placement_profile_api"),
url(r"^api/notification-preferences/$", views.notification_preferences_api, name="notification_preferences_api"),
url(r"^api/apply-for-placement/$", views.apply_for_placement_api, name="apply_for_placement_api"),
url(r"^api/apply-for-placement/(?P<schedule_id>[0-9]+)/$", views.withdraw_application_api, name="withdraw_application_api"),
url(r"^api/my-applications/$", views.my_applications_api, name="my_applications_api"),
url(r"^api/my-offers/$", views.my_offers_api, name="my_offers_api"),
url(r"^api/offer/(?P<offer_id>[0-9]+)/$", views.offer_detail_api, name="offer_detail_api"),
url(r"^api/offer/(?P<offer_id>[0-9]+)/respond/$", views.offer_respond_api, name="offer_respond_api"),
url(r"^api/student-applications/(?P<identifier>[0-9]+)/$", views.student_applications_api, name="student_applications_api"),
url(r"^api/application-detail/(?P<application_id>[0-9]+)/$", views.application_detail_api, name="application_detail_api"),
url(r"^api/application-detail/(?P<application_id>[0-9]+)/interview/$", views.application_interview_schedule_api, name="application_interview_schedule_api"),
url(r"^api/download-applications/(?P<schedule_id>[0-9]+)/$", views.download_applications_api, name="download_applications_api"),
url(r"^api/nextround/(?P<schedule_id>[0-9]+)/$", views.next_round_api, name="next_round_api"),
url(r"^api/timeline/(?P<schedule_id>[0-9]+)/$", views.timeline_api, name="timeline_api"),
url(r"^api/calender/$", views.calendar_api, name="calendar_api"),
url(r"^api/generate-cv/$", views.generate_cv_api, name="generate_cv_api"),
url(r"^api/debared-students/$", views.debarred_students_api, name="debarred_students_api"),
url(r"^api/debared-status/(?P<roll_no>[A-Za-z0-9]+)/$", views.debarred_status_api, name="debarred_status_api"),
url(r"^api/send-notification/$", views.send_notification_api, name="send_notification_api"),
url(r"^api/restrictions/$", views.restrictions_api, name="restrictions_api"),
url(r"^api/restrictions/(?P<restriction_id>[0-9]+)/$", views.restriction_detail_api, name="restriction_detail_api"),
url(r"^api/policies/$", views.placement_policies_api, name="placement_policies_api"),
url(r"^api/policies/(?P<policy_id>[0-9]+)/$", views.placement_policy_detail_api, name="placement_policy_detail_api"),
url(r"^api/alumni/profile/$", views.alumni_profile_api, name="alumni_profile_api"),
url(r"^api/alumni/directory/$", views.alumni_directory_api, name="alumni_directory_api"),
url(r"^api/alumni/verification/$", views.alumni_verification_list_api, name="alumni_verification_list_api"),
url(r"^api/alumni/verification/(?P<profile_id>[0-9]+)/$", views.alumni_verification_detail_api, name="alumni_verification_detail_api"),
url(r"^api/alumni/referrals/$", views.alumni_referrals_api, name="alumni_referrals_api"),
url(r"^api/alumni/connections/$", views.alumni_connections_api, name="alumni_connections_api"),
url(r"^api/alumni/connections/(?P<connection_id>[0-9]+)/$", views.alumni_connection_detail_api, name="alumni_connection_detail_api"),
url(r"^api/alumni/sessions/$", views.alumni_sessions_api, name="alumni_sessions_api"),
url(r"^api/alumni/sessions/(?P<session_id>[0-9]+)/$", views.alumni_session_detail_api, name="alumni_session_detail_api"),
# PlacementAppeal API endpoints
url(r"^api/placement-appeals/$", views.placement_appeal_list_create_api, name="placement_appeal_list_create_api"),
url(r"^api/placement-appeals/(?P<pk>[0-9]+)/$", views.placement_appeal_detail_api, name="placement_appeal_detail_api"),
# Placement Announcements API endpoints
url(r"^api/announcements/$", views.placement_announcements_api, name="placement_announcements_api"),
url(r"^api/announcements/(?P<announcement_id>[0-9]+)/$", views.placement_announcement_detail_api, name="placement_announcement_detail_api"),
# Off-Campus Placements API endpoints
url(r"^api/offcampus/$", views.offcampus_placements_api, name="offcampus_placements_api"),
url(r"^api/offcampus/(?P<placement_id>[0-9]+)/$", views.offcampus_placement_detail_api, name="offcampus_placement_detail_api"),
# Published-CPI student view + export API endpoints
url(r"^api/cpi-batches/$", views.placement_cpi_batches_api, name="placement_cpi_batches_api"),
url(r"^api/cpi-students/$", views.placement_cpi_students_api, name="placement_cpi_students_api"),
# Branch (department) reference list for placement forms
url(r"^api/branches/$", views.placement_branches_api, name="placement_branches_api"),
# Free-form placement calendar events (Google-Calendar style)
url(r"^api/calendar-events/$", views.placement_calendar_events_api, name="placement_calendar_events_api"),
url(r"^api/calendar-events/(?P<event_id>[0-9]+)/$", views.placement_calendar_event_detail_api, name="placement_calendar_event_detail_api"),
]
Loading
Loading