Claiming This Task
Before you start working, check the Assignees section. If no one is assigned, leave a comment claiming the issue and assign it to yourself. This prevents duplicate work.
See the Community Wiki for contributing guidelines and git workflow.
Problem
The notification system has a model (notifications/models.py) and signals for friend requests and chat invitations, but:
- No API endpoints — there are no views or URLs registered for notifications, so the frontend cannot list, read, or manage notifications
- No course invitation notification — when a teacher invites a user to a course (creating a
CourseMembership with status="invited"), no notification is created. The invited user has no way to know they were invited unless they happen to visit the course page
- Missing notification type —
COURSE_INVITATION is not in NOTIFICATION_TYPE_CHOICES (only FRIEND_REQUEST, CHAT_INVITE, NEW_MESSAGE, SYSTEM_ANNOUNCEMENT exist)
Requirements
1. Add COURSE_INVITATION notification type
- Add
("COURSE_INVITATION", "Course Invitation") to NOTIFICATION_TYPE_CHOICES in notifications/models_choices.py
- Generate and apply migration
2. Create course invitation signal
- Add a
post_save signal on CourseMembership (or hook into the invite view) that creates a notification when a membership is created with status="invited"
- Notification fields:
recipient: the invited user
actor: the teacher who sent the invite
verb: e.g. "invited you to the course '{course.title}'"
notification_type: "COURSE_INVITATION"
target: the CourseMembership instance (via GenericForeignKey)
- Follow the same defensive pattern as
users/signals.py (try/except to prevent signal from breaking the main operation)
3. Build notification API endpoints
Following existing DRF patterns (users/views.py as reference):
| Method |
URL |
Description |
| GET |
/api/notifications/ |
List current user's notifications (paginated, newest first) |
| GET |
/api/notifications/unread-count/ |
Return { "count": N } for the navbar badge |
| PATCH |
/api/notifications/<id>/ |
Mark a single notification as read ({ "is_read": true }) |
| POST |
/api/notifications/mark-all-read/ |
Mark all of the current user's notifications as read |
| DELETE |
/api/notifications/<id>/ |
Delete a single notification |
- All endpoints require authentication (
IsAuthenticated)
- Users can only access their own notifications
- List endpoint should support filtering by
is_read and notification_type query params
4. Register URLs
- Add
path("notifications/", include("notifications.urls")) to EduLite/urls.py
Architecture Context
- Existing pattern:
users/signals.py lines 47-100 show how friend request notifications are created via post_save signal with defensive error handling
- Existing pattern:
chat/signals.py lines 1-11 show the chat invitation notification signal
- Notification model:
notifications/models.py — already has recipient, actor, verb, notification_type, is_read, target (GenericForeignKey), description, created_at
- Serializer:
notifications/serializers.py — NotificationSerializer is already built (read-only), includes target_details method for the GenericForeignKey
- Views file:
notifications/views.py exists but is empty
- Course invite view:
courses/views.py CourseMembershipListInviteView.post() (line ~705) creates the membership — this is where the signal would fire from
Files to be Altered
| File |
Change |
notifications/models_choices.py |
Add COURSE_INVITATION type |
notifications/views.py |
Build all API views |
notifications/urls.py |
Define URL patterns |
notifications/serializers.py |
May need a write serializer for mark-as-read |
notifications/permissions.py |
Create IsNotificationOwner permission (optional, can check in view) |
courses/signals.py |
New file — post_save signal for course invitation notification |
courses/apps.py |
Register signal import in ready() |
EduLite/urls.py |
Register notifications/ URL include |
| Migration |
New migration for COURSE_INVITATION choice addition |
Testing Requirements
Unit tests (notifications/tests/)
- Signal tests: Verify notification is created when
CourseMembership is saved with status="invited", verify it is NOT created for status="enrolled" or status="pending"
- List endpoint: Verify pagination, ordering (newest first), filtering by
is_read and notification_type, verify users only see their own notifications
- Unread count endpoint: Verify correct count, verify it updates after marking as read
- Mark as read: Verify single notification, verify mark-all-read
- Delete: Verify deletion, verify users cannot delete other users' notifications
- Permissions: Verify 401 for unauthenticated, verify users cannot access others' notifications
Additional Context (Optional)
- The frontend notification bell icon already links to
/notifications but the page is empty — frontend work depends on this issue being completed first
- The
EnrollmentActions component already detects status="invited" and renders accept/decline buttons (currently disabled) — those buttons are a separate frontend issue and don't depend on this one
- Existing notification tests are in
notifications/tests/test_Notifications.py (model-only tests)
Claiming This Task
Before you start working, check the Assignees section. If no one is assigned, leave a comment claiming the issue and assign it to yourself. This prevents duplicate work.
See the Community Wiki for contributing guidelines and git workflow.
Problem
The notification system has a model (
notifications/models.py) and signals for friend requests and chat invitations, but:CourseMembershipwithstatus="invited"), no notification is created. The invited user has no way to know they were invited unless they happen to visit the course pageCOURSE_INVITATIONis not inNOTIFICATION_TYPE_CHOICES(only FRIEND_REQUEST, CHAT_INVITE, NEW_MESSAGE, SYSTEM_ANNOUNCEMENT exist)Requirements
1. Add
COURSE_INVITATIONnotification type("COURSE_INVITATION", "Course Invitation")toNOTIFICATION_TYPE_CHOICESinnotifications/models_choices.py2. Create course invitation signal
post_savesignal onCourseMembership(or hook into the invite view) that creates a notification when a membership is created withstatus="invited"recipient: the invited useractor: the teacher who sent the inviteverb: e.g."invited you to the course '{course.title}'"notification_type:"COURSE_INVITATION"target: theCourseMembershipinstance (via GenericForeignKey)users/signals.py(try/except to prevent signal from breaking the main operation)3. Build notification API endpoints
Following existing DRF patterns (
users/views.pyas reference):/api/notifications//api/notifications/unread-count/{ "count": N }for the navbar badge/api/notifications/<id>/{ "is_read": true })/api/notifications/mark-all-read//api/notifications/<id>/IsAuthenticated)is_readandnotification_typequery params4. Register URLs
path("notifications/", include("notifications.urls"))toEduLite/urls.pyArchitecture Context
users/signals.pylines 47-100 show how friend request notifications are created viapost_savesignal with defensive error handlingchat/signals.pylines 1-11 show the chat invitation notification signalnotifications/models.py— already hasrecipient,actor,verb,notification_type,is_read,target(GenericForeignKey),description,created_atnotifications/serializers.py—NotificationSerializeris already built (read-only), includestarget_detailsmethod for the GenericForeignKeynotifications/views.pyexists but is emptycourses/views.pyCourseMembershipListInviteView.post()(line ~705) creates the membership — this is where the signal would fire fromFiles to be Altered
notifications/models_choices.pyCOURSE_INVITATIONtypenotifications/views.pynotifications/urls.pynotifications/serializers.pynotifications/permissions.pyIsNotificationOwnerpermission (optional, can check in view)courses/signals.pypost_savesignal for course invitation notificationcourses/apps.pyready()EduLite/urls.pynotifications/URL includeCOURSE_INVITATIONchoice additionTesting Requirements
Unit tests (
notifications/tests/)CourseMembershipis saved withstatus="invited", verify it is NOT created forstatus="enrolled"orstatus="pending"is_readandnotification_type, verify users only see their own notificationsAdditional Context (Optional)
/notificationsbut the page is empty — frontend work depends on this issue being completed firstEnrollmentActionscomponent already detectsstatus="invited"and renders accept/decline buttons (currently disabled) — those buttons are a separate frontend issue and don't depend on this onenotifications/tests/test_Notifications.py(model-only tests)