Browse Source

live classes notifications logic implemented

master
Mohsen Taba 2 weeks ago
parent
commit
b2ac99af65
  1. 122
      apps/account/tests/notifications/test_live_notifications.py
  2. 98
      apps/course/signals.py

122
apps/account/tests/notifications/test_live_notifications.py

@ -0,0 +1,122 @@
import datetime
from django.utils import timezone
from django.test import TestCase
from unittest.mock import patch
from django.core.files.uploadedfile import SimpleUploadedFile
from dj_language.models import Language
from apps.account.models import StudentUser, Notification
from apps.course.models.course import Course, CourseCategory
from apps.course.models.participant import Participant
from apps.course.models.live_session import CourseLiveSession, LiveSessionRecording
class LiveNotificationsFlowTests(TestCase):
def setUp(self):
# Create languages
self.lang_fa, _ = Language.objects.update_or_create(
code='fa',
defaults={'name': 'Persian', 'status': True, 'countries': []}
)
self.lang_en, _ = Language.objects.update_or_create(
code='en',
defaults={'name': 'English', 'status': True, 'countries': []}
)
# Create category
self.category = CourseCategory.objects.create(
name=[{"title": "Category", "language_code": "en"}],
slug=[{"title": "category", "language_code": "en"}]
)
# Create student user
self.student = StudentUser.objects.create(
email='student_live@example.com',
username='student_live',
fullname='Live Student',
language=self.lang_fa,
fcm='token_live_student'
)
# Create Course
thumbnail = SimpleUploadedFile('thumb.jpg', b'filecontent', content_type='image/jpeg')
self.course = Course.objects.create(
title=[
{"title": "Course 1", "language_code": "en"},
{"title": "دوره ۱", "language_code": "fa"}
],
slug=[{"title": "course-1", "language_code": "en"}],
category=self.category,
thumbnail=thumbnail,
video_type=Course.VedioTypeChoices.YOUTUBE_LINK,
video_link='https://example.com/video',
is_online=False,
level=Course.LevelChoices.BEGINNER,
duration=10,
lessons_count=0,
description='Description',
short_description='Short description',
status=[{"title": "ongoing", "language_code": "en"}],
is_free=True,
)
# Enroll student in the course
self.participant = Participant.objects.create(
student=self.student,
course=self.course,
is_active=True
)
# Create Live Session
self.session = CourseLiveSession.objects.create(
course=self.course,
subject='Lesson One Live',
started_at=timezone.now() + datetime.timedelta(days=1),
is_cancelled=False,
reminder_sent=False
)
@patch('apps.account.notification_service.send_notification')
def test_live_class_cancelled_notification(self, mock_send):
# Event 1: LIVE Class Cancelled
self.session.is_cancelled = True
self.session.save()
# Check DB notification for Persian student
notif = Notification.objects.filter(user=self.student, title="کلاس زنده لغو شد").first()
self.assertIsNotNone(notif)
self.assertIn("Lesson One Live", notif.message)
self.assertIn("دوره ۱", notif.message)
self.assertTrue(mock_send.called)
@patch('apps.account.notification_service.send_notification')
def test_live_class_rescheduled_notification(self, mock_send):
# Event 2: LIVE Class Rescheduled
old_time = self.session.started_at
new_time = old_time + datetime.timedelta(hours=2)
self.session.started_at = new_time
self.session.save()
# Check DB notification
notif = Notification.objects.filter(user=self.student, title="کلاس زنده تغییر زمان یافت").first()
self.assertIsNotNone(notif)
self.assertIn("Lesson One Live", notif.message)
self.assertIn("دوره ۱", notif.message)
self.assertTrue(mock_send.called)
@patch('apps.account.notification_service.send_notification')
def test_live_recording_available_notification(self, mock_send):
# Event 3: LIVE Recording Available
# Create a LiveSessionRecording with a dummy file
recording_file = SimpleUploadedFile('rec.mp4', b'video_data', content_type='video/mp4')
recording = LiveSessionRecording.objects.create(
session=self.session,
file=recording_file
)
# Check DB notification
notif = Notification.objects.filter(user=self.student, title="ضبط کلاس زنده در دسترس است").first()
self.assertIsNotNone(notif)
self.assertIn("Lesson One Live", notif.message)
self.assertIn("دوره ۱", notif.message)
self.assertTrue(mock_send.called)

98
apps/course/signals.py

@ -378,13 +378,107 @@ def check_course_completion(sender, instance, created, **kwargs):
@receiver(pre_save, sender=CourseLiveSession) @receiver(pre_save, sender=CourseLiveSession)
def handle_live_session_reschedule(sender, instance, **kwargs):
def handle_live_session_pre_save(sender, instance, **kwargs):
if instance.pk: if instance.pk:
try: try:
old = CourseLiveSession.objects.get(pk=instance.pk) old = CourseLiveSession.objects.get(pk=instance.pk)
instance._previous_is_cancelled = old.is_cancelled
instance._previous_started_at = old.started_at
# Reset reminder_sent if rescheduled to a future time
if old.started_at != instance.started_at: if old.started_at != instance.started_at:
if instance.started_at > timezone.now(): if instance.started_at > timezone.now():
instance.reminder_sent = False instance.reminder_sent = False
except CourseLiveSession.DoesNotExist: except CourseLiveSession.DoesNotExist:
pass
instance._previous_is_cancelled = None
instance._previous_started_at = None
else:
instance._previous_is_cancelled = None
instance._previous_started_at = None
@receiver(post_save, sender=CourseLiveSession)
def notify_live_session_changes(sender, instance, created, **kwargs):
# Event 1: LIVE Class Cancelled
if instance.is_cancelled:
was_cancelled = False
if created:
was_cancelled = True
elif hasattr(instance, '_previous_is_cancelled') and instance._previous_is_cancelled is False:
was_cancelled = True
if was_cancelled:
participants = Participant.objects.filter(course=instance.course, is_active=True).select_related('student')
course_title_en = get_localized_field('en', instance.course.title)
course_title_fa = get_localized_field('fa', instance.course.title)
for p in participants:
create_and_send_notification(
user=p.student,
title_en="LIVE Class Cancelled",
body_en=f"The live class '{instance.subject}' for '{course_title_en}' has been cancelled.",
title_fa="کلاس زنده لغو شد",
body_fa=f"کلاس زنده «{instance.subject}» برای دوره «{course_title_fa}» لغو شده است.",
service='imam-javad',
data={'type': 'live_class_cancelled', 'session_id': instance.id}
)
return
# Event 2: LIVE Class Rescheduled
if not created and hasattr(instance, '_previous_started_at') and instance._previous_started_at is not None:
if instance._previous_started_at != instance.started_at:
if not instance.is_cancelled:
participants = Participant.objects.filter(course=instance.course, is_active=True).select_related('student')
course_title_en = get_localized_field('en', instance.course.title)
course_title_fa = get_localized_field('fa', instance.course.title)
for p in participants:
create_and_send_notification(
user=p.student,
title_en="LIVE Class Rescheduled",
body_en=f"The live class '{instance.subject}' for '{course_title_en}' has been rescheduled to {instance.started_at.strftime('%Y-%m-%d %H:%M')}.",
title_fa="کلاس زنده تغییر زمان یافت",
body_fa=f"زمان کلاس زنده «{instance.subject}» برای دوره «{course_title_fa}» به {instance.started_at.strftime('%Y-%m-%d %H:%M')} تغییر یافته است.",
service='imam-javad',
data={'type': 'live_class_rescheduled', 'session_id': instance.id}
)
@receiver(pre_save, sender=LiveSessionRecording)
def store_recording_previous_file(sender, instance, **kwargs):
if instance.pk:
try:
old = LiveSessionRecording.objects.get(pk=instance.pk)
instance._previous_file = old.file
except LiveSessionRecording.DoesNotExist:
instance._previous_file = None
else:
instance._previous_file = None
@receiver(post_save, sender=LiveSessionRecording)
def notify_recording_available(sender, instance, created, **kwargs):
if instance.file:
was_uploaded = False
if created:
was_uploaded = True
elif hasattr(instance, '_previous_file') and not instance._previous_file:
was_uploaded = True
if was_uploaded:
session = instance.session
if not session:
return
participants = Participant.objects.filter(course=session.course, is_active=True).select_related('student')
course_title_en = get_localized_field('en', session.course.title)
course_title_fa = get_localized_field('fa', session.course.title)
for p in participants:
create_and_send_notification(
user=p.student,
title_en="LIVE Recording Available",
body_en=f"The recording for '{session.subject}' in '{course_title_en}' is now available.",
title_fa="ضبط کلاس زنده در دسترس است",
body_fa=f"ضبط کلاس زنده «{session.subject}» برای دوره «{course_title_fa}» اکنون در آرشیو در دسترس است.",
service='imam-javad',
data={'type': 'live_recording_available', 'recording_id': instance.id}
)
Loading…
Cancel
Save