Use this file to discover all available pages before exploring further.
Internship Portal’s data layer is built on four Django models spread across two apps. Profile (in users) extends Django’s built-in User with role-specific fields. Internship (in internships) represents a job listing owned by a company user. Application (in applications) links a student to an internship with a lifecycle status, and Notification (also in applications) delivers in-app messages to any user. Together they form the complete relational backbone of the portal.
The Profile model lives in users/models.py and is attached to Django’s User via a OneToOneField. Rather than using two separate models for students and companies, all role-specific fields coexist on a single model — unused fields are left blank. The user_type field determines which fields are relevant for a given account.
The Internship model lives in internships/models.py and represents a single job listing created by a company user. Its status field controls visibility to students.
A company can toggle an internship between active and closed at any time via the toggle_internship_status() service function. Only active internships are surfaced to students in the public listing.
The Application model lives in applications/models.py and records a student’s intent to apply for an internship. A unique_together constraint at the database level guarantees that one student cannot submit more than one application per internship.
The unique_together = ("student", "internship") constraint means the view can safely use get_or_create() — if the application already exists it is returned rather than duplicated, and the user sees an informational message instead of an error.
The Notification model also lives in applications/models.py and supports simple in-app messaging. Notifications are created automatically when a student applies to an internship (alerting the company) and when a company accepts or rejects an application (alerting the student).
Unread notifications are injected into every template request via the users.context_processors.notifications context processor, which queries Notification.objects.filter(user=request.user, is_read=False) and exposes both the queryset and count.
The diagram below describes the relationships between all four models and Django’s built-in User:
User → Profile (1:1) — every User has exactly one Profile, auto-created via the post_save signal in users/signals.py.
User (company) → Internship (1:N) — a company user can own many Internship listings; deleting the user cascades and removes all their listings.
User (student) → Application (1:N) — a student user can submit many Application records; a unique_together constraint limits one application per internship.
Internship → Application (1:N) — each Internship can receive many Application records from different students.
User → Notification (1:N) — any user (student or company) can have many Notification records; unread count is surfaced in the nav bar.
InternshipAdmin uses the @admin.register decorator and configures list_display to show all key columns in the changelist view, list_filter to allow filtering by status and location in the sidebar, and search_fields to enable full-text search across title and description. Profile is registered with default settings, giving admins full CRUD access without additional configuration.