83 lines
2.8 KiB
Python
83 lines
2.8 KiB
Python
from celery import shared_task
|
|
from django.utils.timezone import now
|
|
|
|
|
|
@shared_task(name="notifications.deliver_notification", bind=True,
|
|
autoretry_for=(Exception,), retry_kwargs={"max_retries": 3, "countdown": 60})
|
|
def deliver_notification_task(self, delivery_id: str):
|
|
"""
|
|
Dispatch a single NotificationDelivery via its channel.
|
|
Marks status SENT on success or FAILED on final exhaustion.
|
|
"""
|
|
from .models import DeliveryChannel, DeliveryStatus, NotificationDelivery
|
|
|
|
try:
|
|
delivery = NotificationDelivery.objects.select_related(
|
|
"notification__recipient"
|
|
).get(pk=delivery_id)
|
|
except NotificationDelivery.DoesNotExist:
|
|
return {"error": "delivery not found", "id": delivery_id}
|
|
|
|
try:
|
|
if delivery.channel == DeliveryChannel.EMAIL:
|
|
_send_email(delivery)
|
|
# In-app channel: the Notification row IS the in-app message; nothing more to send.
|
|
|
|
delivery.status = DeliveryStatus.SENT
|
|
delivery.sent_at = now()
|
|
delivery.save(update_fields=["status", "sent_at"])
|
|
return {"delivery_id": delivery_id, "status": "sent"}
|
|
|
|
except Exception as exc:
|
|
delivery.error_detail = str(exc)
|
|
delivery.status = DeliveryStatus.FAILED
|
|
delivery.save(update_fields=["status", "error_detail"])
|
|
raise # triggers autoretry
|
|
|
|
|
|
def _send_email(delivery):
|
|
from django.core.mail import send_mail
|
|
from django.conf import settings
|
|
|
|
n = delivery.notification
|
|
recipient_email = n.recipient.email
|
|
if not recipient_email:
|
|
return
|
|
|
|
send_mail(
|
|
subject=n.title,
|
|
message=n.body,
|
|
from_email=getattr(settings, "DEFAULT_FROM_EMAIL", "no-reply@example.com"),
|
|
recipient_list=[recipient_email],
|
|
fail_silently=False,
|
|
)
|
|
|
|
|
|
@shared_task(name="notifications.send_course_due_reminders")
|
|
def send_course_due_reminders_task():
|
|
"""
|
|
Periodic task: notify learners of enrollments due within the configured
|
|
reminder window. Hook this into Celery Beat.
|
|
"""
|
|
from tracking.models import Enrollment
|
|
from .services import notify
|
|
|
|
# Enrollments that have a due_at field (future optional extension) or use
|
|
# completed_at is None as a proxy for "not yet done". For now we fire
|
|
# reminders only for enrollments that have a due_at set on the course.
|
|
# Since due_at is not yet on Course, this is a safe no-op stub.
|
|
# Replace the queryset below once Course.due_at is added.
|
|
qs = Enrollment.objects.none()
|
|
count = 0
|
|
for enrollment in qs:
|
|
notify(
|
|
recipient=enrollment.user,
|
|
event_type="course_due",
|
|
title=f"Course due soon: {enrollment.course.title}",
|
|
body="",
|
|
object_type="enrollment",
|
|
object_id=str(enrollment.pk),
|
|
)
|
|
count += 1
|
|
return {"reminders_sent": count}
|