- Notification model with idempotency_key dedup (unique per event+object+recipient) - NotificationDelivery audit model (pending/sent/failed/delivered per channel) - notify() service: creates notification idempotently, enqueues per-channel tasks - deliver_notification_task Celery task: sends email via send_mail, marks in-app as sent without email; marks FAILED with error_detail on exception (autoretry x3) - Event trigger helpers: notify_course_assigned, notify_attempt_limit_reached, notify_certificate_issued, notify_certificate_expiring (daily-reminder safe) - send_course_due_reminders_task: periodic Celery Beat stub for due-date alerts - REST API: list notifications (with ?unread=true filter), mark-read, mark-all-read - Admin registrations with inline delivery audit view - Initial migration (Notification + NotificationDelivery tables) - Pytest test suite: idempotency, delivery dispatch, view auth/filtering Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
30 lines
997 B
Python
30 lines
997 B
Python
from django.contrib import admin
|
|
|
|
from .models import Notification, NotificationDelivery
|
|
|
|
|
|
class NotificationDeliveryInline(admin.TabularInline):
|
|
model = NotificationDelivery
|
|
extra = 0
|
|
readonly_fields = ["channel", "status", "sent_at", "error_detail", "created_at"]
|
|
|
|
|
|
@admin.register(Notification)
|
|
class NotificationAdmin(admin.ModelAdmin):
|
|
list_display = ["title", "event_type", "recipient", "is_read", "created_at"]
|
|
list_filter = ["event_type"]
|
|
search_fields = ["title", "recipient__email", "idempotency_key"]
|
|
readonly_fields = ["id", "idempotency_key", "created_at", "read_at"]
|
|
inlines = [NotificationDeliveryInline]
|
|
|
|
@admin.display(boolean=True)
|
|
def is_read(self, obj):
|
|
return obj.is_read
|
|
|
|
|
|
@admin.register(NotificationDelivery)
|
|
class NotificationDeliveryAdmin(admin.ModelAdmin):
|
|
list_display = ["notification", "channel", "status", "sent_at", "created_at"]
|
|
list_filter = ["channel", "status"]
|
|
readonly_fields = ["id", "created_at"]
|