Browse Source

notification fa language removed and runlanguage added

master
Mohsen Taba 1 week ago
parent
commit
0066c07de4
  1. 138
      apps/account/management/commands/seed_notification_templates.py
  2. 41
      apps/account/migrations/0008_remove_notificationtemplate_body_fa_and_more.py
  3. 8
      apps/account/models/notification.py
  4. 25
      apps/account/notification_service.py
  5. 4
      apps/account/tasks.py
  6. 22
      apps/account/tests/notifications/test_communication_notifications.py
  7. 24
      apps/account/tests/notifications/test_live_notifications.py
  8. 78
      apps/account/tests/notifications/test_notifications.py
  9. 24
      apps/account/tests/notifications/test_quiz_notifications.py
  10. 20
      apps/account/tests/notifications/test_retention_notifications.py
  11. 28
      apps/account/tests/notifications/test_transaction_notifications.py

138
apps/account/management/commands/seed_notification_templates.py

@ -2,153 +2,189 @@ from django.core.management.base import BaseCommand
from apps.account.models import NotificationTemplate
class Command(BaseCommand):
help = 'Seeds default NotificationTemplate records in the database.'
help = 'Seeds default NotificationTemplate records in the database with RU and EN translations.'
def handle(self, *args, **options):
default_templates = [
{
"notification_type": "course_access_granted",
"name": "دسترسی به دوره فعال شد",
"title_fa": "دسترسی به دوره فعال شد",
"name": "Доступ к курсу активирован",
"title_ru": "Доступ к курсу активирован",
"title_en": "Course Access Granted",
"body_fa": "دسترسی شما به دوره «{course_title}» فعال شد.",
"body_ru": "Ваш доступ к курсу «{course_title}» успешно активирован.",
"body_en": "Your access to the course '{course_title}' has been activated.",
"placeholders_info": "{course_title}, {course_name}, {course_slug}, {student_name}, {fullname}"
},
{
"notification_type": "course_registered",
"name": "ثبت‌نام دوره جدید",
"title_fa": "ثبت‌نام دوره موفقیت‌آمیز بود",
"name": "Регистрация на курс",
"title_ru": "Успешная регистрация на курс",
"title_en": "Course Registration Successful",
"body_fa": "ثبت‌نام شما در دوره «{course_title}» با موفقیت انجام شد.",
"body_ru": "Вы успешно зарегистрировались на курс «{course_title}».",
"body_en": "You have successfully registered for the course '{course_title}'.",
"placeholders_info": "{course_title}, {course_name}, {course_slug}, {student_name}, {fullname}"
},
{
"notification_type": "lesson_completed",
"name": "اتمام درس",
"title_fa": "درس به اتمام رسید",
"name": "Урок завершен",
"title_ru": "Урок завершен",
"title_en": "Lesson Completed",
"body_fa": "تبریک! شما درس «{lesson_title}» را به پایان رساندید.",
"body_ru": "Поздравляем! Вы завершили изучение урока «{lesson_title}».",
"body_en": "Congratulations! You completed the lesson '{lesson_title}'.",
"placeholders_info": "{course_title}, {course_name}, {lesson_title}, {student_name}, {fullname}"
},
{
"notification_type": "course_completed",
"name": "اتمام دوره",
"title_fa": "دوره به اتمام رسید",
"name": "Курс завершен",
"title_ru": "Курс завершен",
"title_en": "Course Completed",
"body_fa": "آفرین! شما دوره «{course_title}» را با موفقیت به پایان رساندید.",
"body_ru": "Отличная работа! Вы успешно завершили курс «{course_title}».",
"body_en": "Well done! You have completed the course '{course_title}'.",
"placeholders_info": "{course_title}, {course_name}, {course_slug}, {student_name}, {fullname}"
},
{
"notification_type": "new_course_weekly",
"name": "معرفی دوره جدید هفتگی",
"title_fa": "دوره جدید ثبت شد",
"name": "Новый курс за неделю",
"title_ru": "Добавлен новый курс",
"title_en": "New Course Registered",
"body_fa": "دوره جدید «{course_title}» ثبت شده است. همین حالا می‌توانید ثبت‌نام کنید.",
"body_ru": "Зарегистрирован новый курс «{course_title}». Вы можете записаться прямо сейчас.",
"body_en": "A new course '{course_title}' has been registered. You can enroll now.",
"placeholders_info": "{course_title}, {course_name}, {course_slug}"
},
{
"notification_type": "low_quiz_result",
"name": "پیشنهاد شرکت مجدد در آزمون",
"title_fa": "پیشنهاد شرکت مجدد در آزمون",
"name": "Предложение пересдать тест",
"title_ru": "Предложение пересдать тест",
"title_en": "Quiz Retake Suggestion",
"body_fa": "امتیاز شما در آزمون «{quiz_title}» کمتر از حد نصاب شده است. پیشنهاد می‌کنیم مجدداً در این آزمون شرکت کنید.",
"body_ru": "Вы набрали балл ниже проходного в тесте «{quiz_title}». Рекомендуем пройти его повторно.",
"body_en": "You scored below the passing threshold on quiz '{quiz_title}'. We suggest taking it again.",
"placeholders_info": "{quiz_title}, {student_name}, {fullname}"
},
{
"notification_type": "live_class_rescheduled",
"name": "تغییر زمان کلاس زنده",
"title_fa": "زمان کلاس زنده تغییر کرد",
"name": "Время живого урока изменено",
"title_ru": "Время живого урока изменено",
"title_en": "Live Class Rescheduled",
"body_fa": "زمان کلاس زنده برای دوره «{course_title}» تغییر یافته است.",
"body_ru": "Время проведения живого урока по курсу «{course_title}» было изменено.",
"body_en": "The live class for '{course_title}' has been rescheduled.",
"placeholders_info": "{course_title}, {course_name}, {session_subject}, {session_start_time}"
},
{
"notification_type": "live_class_cancelled",
"name": "لغو کلاس زنده",
"title_fa": "کلاس زنده لغو شد",
"name": "Живой урок отменен",
"title_ru": "Живой урок отменен",
"title_en": "Live Class Cancelled",
"body_fa": "کلاس زنده برای دوره «{course_title}» لغو شده است.",
"body_ru": "Живой урок по курсу «{course_title}» был отменен.",
"body_en": "The live class for '{course_title}' has been cancelled.",
"placeholders_info": "{course_title}, {course_name}, {session_subject}"
},
{
"notification_type": "live_recording_available",
"name": "ویدئو ضبط شده کلاس آماده است",
"title_fa": "ویدئو ضبط شده کلاس آماده است",
"name": "Доступна запись живого урока",
"title_ru": "Доступна запись урока",
"title_en": "Live Recording Available",
"body_fa": "ویدئو ضبط شده کلاس زنده برای دوره «{course_title}» آماده مشاهده است.",
"body_ru": "Запись живого урока по курсу «{course_title}» уже доступна для просмотра.",
"body_en": "The recorded video of the live class for '{course_title}' is now available.",
"placeholders_info": "{course_title}, {course_name}"
},
{
"notification_type": "missed_live_sessions",
"name": "غیبت متوالی کلاس زنده",
"title_fa": "غیبت متوالی در کلاس‌های زنده",
"name": "Пропуск живых уроков",
"title_ru": "Пропуск живых уроков",
"title_en": "Missed Live Sessions",
"body_fa": "شما ۲ جلسه زنده متوالی را در دوره «{course_title}» غایب بوده‌اید. توصیه می‌کنیم ویدئوهای ضبط شده را مشاهده کنید.",
"body_ru": "Вы пропустили 2 живых урока подряд по курсу «{course_title}». Рекомендуем посмотреть их в записи.",
"body_en": "You have missed 2 consecutive live sessions in the course '{course_title}'. We recommend watching the recordings.",
"placeholders_info": "{course_title}, {course_name}, {student_name}, {fullname}"
},
{
"notification_type": "payment_successful",
"name": "تراکنش موفق مالی",
"title_fa": "پرداخت موفقیت‌آمیز",
"name": "Оплата успешна",
"title_ru": "Оплата успешна",
"title_en": "Payment Successful",
"body_fa": "پرداخت شما برای دوره «{course_title}» با موفقیت انجام شد.",
"body_ru": "Ваша оплата за курс «{course_title}» успешно проведена.",
"body_en": "Your payment for course '{course_title}' was successful.",
"placeholders_info": "{course_title}, {course_name}, {student_name}, {fullname}"
},
{
"notification_type": "payment_failed",
"name": "تراکنش ناموفق مالی",
"title_fa": "خطا در پرداخت",
"name": "Ошибка оплаты",
"title_ru": "Ошибка оплаты",
"title_en": "Payment Failed",
"body_fa": "در پردازش پرداخت شما برای دوره «{course_title}» مشکلی رخ داد.",
"body_ru": "Произошла ошибка при обработке вашей оплаты за курс «{course_title}».",
"body_en": "We encountered an issue processing your payment for '{course_title}'.",
"placeholders_info": "{course_title}, {course_name}, {student_name}, {fullname}"
},
{
"notification_type": "refund_completed",
"name": "بازگشت وجه مالی",
"title_fa": "بازگشت وجه انجام شد",
"name": "Возврат средств",
"title_ru": "Возврат средств выполнен",
"title_en": "Refund Completed",
"body_fa": "بازگشت وجه برای دوره «{course_title}» انجام شد.",
"body_ru": "Возврат средств за курс «{course_title}» успешно завершен.",
"body_en": "A refund has been successfully completed for the course '{course_title}'.",
"placeholders_info": "{course_title}, {course_name}, {student_name}, {fullname}"
},
{
"notification_type": "student_inactivity",
"name": "عدم فعالیت کاربر",
"title_fa": "دلمان برایتان تنگ شده!",
"name": "Мы скучаем по вам",
"title_ru": "Мы скучаем по вам!",
"title_en": "We Miss You!",
"body_fa": "در یک هفته گذشته هیچ درسی را مطالعه نکرده‌اید. منتظرتان هستیم تا مسیر یادگیری خود را ادامه دهید.",
"body_ru": "Вы не изучали уроки за последнюю неделю. Ждем вас для продолжения обучения!",
"body_en": "You haven't taken any lessons in the last week. Come back and continue your learning journey!",
"placeholders_info": "{student_name}, {fullname}"
},
{
"notification_type": "teacher_reply",
"name": "پاسخ پیام استاد در چت",
"title_fa": "پاسخ جدید از استاد",
"name": "Ответ преподавателя",
"title_ru": "Новое сообщение от преподавателя",
"title_en": "New Message from Teacher",
"body_fa": "استاد به پیام شما پاسخ داده است.",
"body_ru": "Преподаватель ответил на ваше сообщение.",
"body_en": "The teacher has replied to your message.",
"placeholders_info": "{student_name}, {fullname}"
},
{
"notification_type": "teacher_reply_private",
"name": "Личное сообщение от преподавателя",
"title_ru": "Новое сообщение от преподавателя",
"title_en": "New Message from Teacher",
"body_ru": "Преподаватель ответил на ваше сообщение.",
"body_en": "The teacher has replied to your message.",
"placeholders_info": "{student_name}, {fullname}"
},
{
"notification_type": "teacher_reply_course",
"name": "Сообщение от преподавателя в чате курса",
"title_ru": "Сообщение от преподавателя в чате курса",
"title_en": "Teacher Post in Course Chat",
"body_ru": "Преподаватель опубликовал новое сообщение в чате курса.",
"body_en": "The teacher posted in the course chat.",
"placeholders_info": "{course_title}, {course_name}, {student_name}, {fullname}"
},
{
"notification_type": "support_reply",
"name": "پاسخ پیام پشتیبان در چت",
"title_fa": "پاسخ جدید از پشتیبان",
"name": "Ответ поддержки",
"title_ru": "Новое сообщение от поддержки",
"title_en": "New Message from Support",
"body_fa": "پشتیبان به پیام شما پاسخ داده است.",
"body_ru": "Служба поддержки ответила на ваше сообщение.",
"body_en": "The support agent has replied to your message.",
"placeholders_info": "{student_name}, {fullname}"
},
{
"notification_type": "certificate_issued",
"name": "Сертификат выдан",
"title_ru": "Сертификат выдан",
"title_en": "Certificate Issued",
"body_ru": "Ваш сертификат по курсу «{course_title}» успешно выпущен.",
"body_en": "Your certificate for the course '{course_title}' has been issued.",
"placeholders_info": "{course_title}, {course_name}, {student_name}, {fullname}"
},
{
"notification_type": "live_class_reminder",
"name": "Напоминание о живом уроке",
"title_ru": "Напоминание о живом уроке",
"title_en": "Live Class Reminder",
"body_ru": "Напоминаем, что живой урок «{session_subject}» начнется в {session_start_time}.",
"body_en": "Reminder: The live class '{session_subject}' will start at {session_start_time}.",
"placeholders_info": "{session_subject}, {session_start_time}, {student_name}, {fullname}"
}
]
@ -158,9 +194,9 @@ class Command(BaseCommand):
notification_type=item['notification_type'],
defaults={
'name': item['name'],
'title_fa': item['title_fa'],
'title_ru': item['title_ru'],
'title_en': item['title_en'],
'body_fa': item['body_fa'],
'body_ru': item['body_ru'],
'body_en': item['body_en'],
'placeholders_info': item['placeholders_info'],
'is_active': True

41
apps/account/migrations/0008_remove_notificationtemplate_body_fa_and_more.py

@ -0,0 +1,41 @@
# Generated by Django 4.2.30 on 2026-07-11 13:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('account', '0007_notificationtemplate_schedulednotification'),
]
operations = [
migrations.RemoveField(
model_name='notificationtemplate',
name='body_fa',
),
migrations.RemoveField(
model_name='notificationtemplate',
name='title_fa',
),
migrations.AddField(
model_name='notificationtemplate',
name='body_ru',
field=models.TextField(default='', max_length=1024, verbose_name='body (RU)'),
),
migrations.AddField(
model_name='notificationtemplate',
name='title_ru',
field=models.CharField(default='', max_length=255, verbose_name='title (RU)'),
),
migrations.AlterField(
model_name='notificationtemplate',
name='body_en',
field=models.TextField(default='', max_length=1024, verbose_name='body (EN)'),
),
migrations.AlterField(
model_name='notificationtemplate',
name='title_en',
field=models.CharField(default='', max_length=255, verbose_name='title (EN)'),
),
]

8
apps/account/models/notification.py

@ -36,10 +36,10 @@ class NotificationTemplate(models.Model):
name = models.CharField(max_length=100, verbose_name=_('name'))
is_active = models.BooleanField(default=True, verbose_name=_('is active'))
title_fa = models.CharField(max_length=255, verbose_name=_('title (FA)'))
title_en = models.CharField(max_length=255, verbose_name=_('title (EN)'))
body_fa = models.TextField(max_length=1024, verbose_name=_('body (FA)'))
body_en = models.TextField(max_length=1024, verbose_name=_('body (EN)'))
title_ru = models.CharField(max_length=255, default="", verbose_name=_('title (RU)'))
title_en = models.CharField(max_length=255, default="", verbose_name=_('title (EN)'))
body_ru = models.TextField(max_length=1024, default="", verbose_name=_('body (RU)'))
body_en = models.TextField(max_length=1024, default="", verbose_name=_('body (EN)'))
placeholders_info = models.CharField(max_length=255, blank=True, null=True, verbose_name=_('allowed placeholders description'))

25
apps/account/notification_service.py

@ -149,7 +149,7 @@ def resolve_notification_route(notification_type, data):
return f"/course_info/quiz?slug={slug}"
# Group 4: Chat Room path (/chat/chat_details?room_id=...)
elif notification_type in ['teacher_reply', 'support_reply']:
elif notification_type in ['teacher_reply', 'teacher_reply_private', 'teacher_reply_course', 'support_reply']:
room_id = data.get('room_id')
if room_id:
return f"/chat/chat_details?room_id={room_id}"
@ -167,13 +167,18 @@ def resolve_notification_route(notification_type, data):
return None
def create_and_send_notification(user, title_en, body_en, title_fa, body_fa, service='imam-javad',
data=None, notification_type=None, action='navigate', navigate_to=None):
def create_and_send_notification(user, title_en, body_en, title_fa=None, body_fa=None, service='imam-javad',
data=None, notification_type=None, action='navigate', navigate_to=None,
title_ru=None, body_ru=None):
"""
Creates a Notification record in the database and sends a push notification to FCM.
Loads and renders the NotificationTemplate from the database if present.
If the template is inactive, the notification is discarded.
"""
# Map Persian arguments to Russian to preserve backward compatibility with existing signals/tasks
title_ru = title_ru or title_fa
body_ru = body_ru or body_fa
# Auto-resolve notification type from data payload if not provided
if not notification_type and isinstance(data, dict):
notification_type = data.get('type')
@ -186,9 +191,9 @@ def create_and_send_notification(user, title_en, body_en, title_fa, body_fa, ser
logger.info(f"Notification type '{notification_type}' is disabled via database template settings.")
return None
# Override templates with database values
title_fa = template.title_fa
title_ru = template.title_ru
title_en = template.title_en
body_fa = template.body_fa
body_ru = template.body_ru
body_en = template.body_en
# 2. Render templates safely with dynamic context
@ -196,19 +201,19 @@ def create_and_send_notification(user, title_en, body_en, title_fa, body_fa, ser
safe_ctx = SafeDict(context)
try:
title_fa = title_fa.format(**safe_ctx) if title_fa else ""
title_ru = title_ru.format(**safe_ctx) if title_ru else ""
title_en = title_en.format(**safe_ctx) if title_en else ""
body_fa = body_fa.format(**safe_ctx) if body_fa else ""
body_ru = body_ru.format(**safe_ctx) if body_ru else ""
body_en = body_en.format(**safe_ctx) if body_en else ""
except Exception as e:
logger.error(f"Error formatting templates: {e}")
# Determine user localized text
lang = getattr(user, 'language', None)
lang_code = lang.code if lang and hasattr(lang, 'code') else 'fa'
lang_code = lang.code if lang and hasattr(lang, 'code') else 'ru'
title = title_fa if lang_code == 'fa' else title_en
message = body_fa if lang_code == 'fa' else body_en
title = title_ru if lang_code == 'ru' else title_en
message = body_ru if lang_code == 'ru' else body_en
# Auto-resolve GoRouter navigation route if not provided
if not navigate_to and notification_type:

4
apps/account/tasks.py

@ -437,8 +437,8 @@ def run_scheduled_notification_task(campaign_id):
user=user,
title_en=template.title_en,
body_en=template.body_en,
title_fa=template.title_fa,
body_fa=template.body_fa,
title_ru=template.title_ru,
body_ru=template.body_ru,
service='imam-javad',
data={'type': template.notification_type},
notification_type=template.notification_type

22
apps/account/tests/notifications/test_communication_notifications.py

@ -13,9 +13,9 @@ from apps.chat.models import RoomMessage, ChatMessage
class CommunicationNotificationsFlowTests(TestCase):
def setUp(self):
# Create languages
self.lang_fa, _ = Language.objects.update_or_create(
code='fa',
defaults={'name': 'Persian', 'status': True, 'countries': []}
self.lang_ru, _ = Language.objects.update_or_create(
code='ru',
defaults={'name': 'Russian', 'status': True, 'countries': []}
)
self.lang_en, _ = Language.objects.update_or_create(
code='en',
@ -33,7 +33,7 @@ class CommunicationNotificationsFlowTests(TestCase):
email='student_comm@example.com',
username='student_comm',
fullname='Comm Student',
language=self.lang_fa,
language=self.lang_ru,
fcm='token_comm_student'
)
@ -44,7 +44,7 @@ class CommunicationNotificationsFlowTests(TestCase):
fullname='Teacher Comm',
user_type='professor',
is_active=True,
language=self.lang_fa
language=self.lang_ru
)
# Create support user (admin)
@ -54,7 +54,7 @@ class CommunicationNotificationsFlowTests(TestCase):
fullname='Support Comm',
user_type='admin',
is_active=True,
language=self.lang_fa
language=self.lang_ru
)
# Create Course
@ -62,7 +62,7 @@ class CommunicationNotificationsFlowTests(TestCase):
self.course = Course.objects.create(
title=[
{"title": "Course 1", "language_code": "en"},
{"title": "دوره ۱", "language_code": "fa"}
{"title": "Курс 1", "language_code": "ru"}
],
slug=[{"title": "course-1", "language_code": "en"}],
category=self.category,
@ -105,7 +105,7 @@ class CommunicationNotificationsFlowTests(TestCase):
)
# Verify notification sent to the student
notif = Notification.objects.filter(user=self.student, title="پیام جدید از طرف استاد").first()
notif = Notification.objects.filter(user=self.student, title="Новое сообщение от преподавателя").first()
self.assertIsNotNone(notif)
self.assertIn("Hello from teacher", notif.message)
self.assertTrue(mock_send.called)
@ -129,7 +129,7 @@ class CommunicationNotificationsFlowTests(TestCase):
)
# Verify notification sent to the student
notif = Notification.objects.filter(user=self.student, title="پاسخ پشتیبانی").first()
notif = Notification.objects.filter(user=self.student, title="Новое сообщение от поддержки").first()
self.assertIsNotNone(notif)
self.assertIn("How can I help you?", notif.message)
self.assertTrue(mock_send.called)
@ -153,10 +153,10 @@ class CommunicationNotificationsFlowTests(TestCase):
)
# Verify notification sent to the student enrolled
notif = Notification.objects.filter(user=self.student, title="پیام استاد در گفتگوی دوره").first()
notif = Notification.objects.filter(user=self.student, title="Сообщение от преподавателя в чате курса").first()
self.assertIsNotNone(notif)
self.assertIn("homework", notif.message)
self.assertIn("دوره ۱", notif.message)
self.assertIn("Курс 1", notif.message)
self.assertTrue(mock_send.called)
@patch('apps.account.notification_service.send_notification')

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

@ -13,9 +13,9 @@ from apps.course.models.live_session import CourseLiveSession, LiveSessionRecord
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_ru, _ = Language.objects.update_or_create(
code='ru',
defaults={'name': 'Russian', 'status': True, 'countries': []}
)
self.lang_en, _ = Language.objects.update_or_create(
code='en',
@ -33,7 +33,7 @@ class LiveNotificationsFlowTests(TestCase):
email='student_live@example.com',
username='student_live',
fullname='Live Student',
language=self.lang_fa,
language=self.lang_ru,
fcm='token_live_student'
)
@ -42,7 +42,7 @@ class LiveNotificationsFlowTests(TestCase):
self.course = Course.objects.create(
title=[
{"title": "Course 1", "language_code": "en"},
{"title": "دوره ۱", "language_code": "fa"}
{"title": "Курс 1", "language_code": "ru"}
],
slug=[{"title": "course-1", "language_code": "en"}],
category=self.category,
@ -81,11 +81,11 @@ class LiveNotificationsFlowTests(TestCase):
self.session.is_cancelled = True
self.session.save()
# Check DB notification for Persian student
notif = Notification.objects.filter(user=self.student, title="کلاس زنده لغو شد").first()
# Check DB notification for Russian 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.assertIn("Курс 1", notif.message)
self.assertTrue(mock_send.called)
@patch('apps.account.notification_service.send_notification')
@ -98,10 +98,10 @@ class LiveNotificationsFlowTests(TestCase):
self.session.save()
# Check DB notification
notif = Notification.objects.filter(user=self.student, title="کلاس زنده تغییر زمان یافت").first()
notif = Notification.objects.filter(user=self.student, title="Время живого урока изменено").first()
self.assertIsNotNone(notif)
self.assertIn("Lesson One Live", notif.message)
self.assertIn("دوره ۱", notif.message)
self.assertIn("Курс 1", notif.message)
self.assertTrue(mock_send.called)
@patch('apps.account.notification_service.send_notification')
@ -115,8 +115,8 @@ class LiveNotificationsFlowTests(TestCase):
)
# Check DB notification
notif = Notification.objects.filter(user=self.student, title="ضبط کلاس زنده در دسترس است").first()
notif = Notification.objects.filter(user=self.student, title="Доступна запись урока").first()
self.assertIsNotNone(notif)
self.assertIn("Lesson One Live", notif.message)
self.assertIn("دوره ۱", notif.message)
self.assertIn("Курс 1", notif.message)
self.assertTrue(mock_send.called)

78
apps/account/tests/notifications/test_notifications.py

@ -16,9 +16,9 @@ from apps.account.tasks import check_live_class_reminders_task, check_new_course
class NotificationFlowTests(TestCase):
def setUp(self):
# Create languages
self.lang_fa, _ = Language.objects.update_or_create(
code='fa',
defaults={'name': 'Persian', 'status': True, 'countries': []}
self.lang_ru, _ = Language.objects.update_or_create(
code='ru',
defaults={'name': 'Russian', 'status': True, 'countries': []}
)
self.lang_en, _ = Language.objects.update_or_create(
code='en',
@ -36,7 +36,7 @@ class NotificationFlowTests(TestCase):
email='student_test@example.com',
username='student_test',
fullname='Test Student',
language=self.lang_fa, # Persian template check
language=self.lang_ru, # Russian template check
fcm='token_student'
)
@ -49,12 +49,12 @@ class NotificationFlowTests(TestCase):
fcm='token_student_en'
)
def _create_course(self, slug, title_en, title_fa, status='ongoing'):
def _create_course(self, slug, title_en, title_ru, status='ongoing'):
thumbnail = SimpleUploadedFile('thumb.jpg', b'filecontent', content_type='image/jpeg')
return Course.objects.create(
title=[
{"title": title_en, "language_code": "en"},
{"title": title_fa, "language_code": "fa"}
{"title": title_ru, "language_code": "ru"}
],
slug=[{"title": slug, "language_code": "en"}],
category=self.category,
@ -77,19 +77,19 @@ class NotificationFlowTests(TestCase):
@patch('apps.account.notification_service.send_notification')
def test_course_access_granted_notification(self, mock_send):
# Event 1: Course Access Granted
course = self._create_course('course-access', 'Course 1', 'دوره ۱')
course = self._create_course('course-access', 'Course 1', 'Курс 1')
# Create participant (active=True by default)
Participant.objects.create(student=self.student, course=course)
# Verify notification created in DB (in Persian)
notif_fa = Notification.objects.filter(user=self.student).first()
self.assertIsNotNone(notif_fa)
self.assertEqual(notif_fa.title, "دسترسی به دوره فعال شد")
self.assertIn("دوره ۱", notif_fa.message)
self.assertEqual(notif_fa.notification_type, "course_access_granted")
self.assertEqual(notif_fa.action, "navigate")
self.assertEqual(notif_fa.navigate_to, "/course_info?slug=course-access")
# Verify notification created in DB (in Russian)
notif_ru = Notification.objects.filter(user=self.student).first()
self.assertIsNotNone(notif_ru)
self.assertEqual(notif_ru.title, "Доступ к курсу активирован")
self.assertIn("Курс 1", notif_ru.message)
self.assertEqual(notif_ru.notification_type, "course_access_granted")
self.assertEqual(notif_ru.action, "navigate")
self.assertEqual(notif_ru.navigate_to, "/course_info?slug=course-access")
# Create participant for English student
Participant.objects.create(student=self.student_en, course=course)
@ -113,7 +113,7 @@ class NotificationFlowTests(TestCase):
@patch('apps.account.notification_service.send_notification')
def test_live_class_reminder_notification(self, mock_send):
# Event 2: LIVE Class Reminder
course = self._create_course('course-live', 'Course 2', 'دوره ۲')
course = self._create_course('course-live', 'Course 2', 'Курс 2')
Participant.objects.create(student=self.student, course=course)
# Create a live session starting in 2 hours
@ -132,9 +132,9 @@ class NotificationFlowTests(TestCase):
session.refresh_from_db()
self.assertTrue(session.reminder_sent)
notif = Notification.objects.filter(user=self.student, title="یادآوری کلاس زنده").first()
notif = Notification.objects.filter(user=self.student, title="Напоминание о живом уроке").first()
self.assertIsNotNone(notif)
self.assertIn("دوره ۲", notif.message)
self.assertIn("Live Lesson 1", notif.message)
# Test rescheduling resets reminder_sent
session.started_at = timezone.now() + datetime.timedelta(hours=5)
@ -146,7 +146,7 @@ class NotificationFlowTests(TestCase):
@patch('apps.account.notification_service.send_notification')
def test_course_completed_notification(self, mock_send):
# Event 3: Course Completed
course = self._create_course('course-complete', 'Course 3', 'دوره ۳')
course = self._create_course('course-complete', 'Course 3', 'Курс 3')
Participant.objects.create(student=self.student, course=course)
chapter = CourseChapter.objects.create(
@ -165,18 +165,18 @@ class NotificationFlowTests(TestCase):
# Complete first lesson
LessonCompletion.objects.create(student=self.student, course_lesson=cl1)
self.assertFalse(Notification.objects.filter(user=self.student, title="دوره به پایان رسید").exists())
self.assertFalse(Notification.objects.filter(user=self.student, title="Курс завершен").exists())
# Complete second (last) lesson
LessonCompletion.objects.create(student=self.student, course_lesson=cl2)
# Verify notification triggered
self.assertTrue(Notification.objects.filter(user=self.student, title="دوره به پایان رسید").exists())
self.assertTrue(Notification.objects.filter(user=self.student, title="Курс завершен").exists())
@patch('apps.account.notification_service.send_notification')
def test_certificate_issued_notification(self, mock_send):
# Event 4: Certificate Issued
course = self._create_course('course-cert', 'Course 4', 'دوره ۴')
course = self._create_course('course-cert', 'Course 4', 'Курс 4')
cert = Certificate.objects.create(
student=self.student,
@ -185,14 +185,14 @@ class NotificationFlowTests(TestCase):
)
# Verify no notification yet
self.assertFalse(Notification.objects.filter(user=self.student, title="گواهی صادر شد").exists())
self.assertFalse(Notification.objects.filter(user=self.student, title="Сертификат выдан").exists())
# Approve certificate
cert.status = 'approved'
cert.save()
# Verify notification triggered
self.assertTrue(Notification.objects.filter(user=self.student, title="گواهی صادر شد").exists())
self.assertTrue(Notification.objects.filter(user=self.student, title="Сертификат выдан").exists())
@patch('apps.account.notification_service.send_notification')
def test_new_course_registration_notification(self, mock_send):
@ -203,20 +203,20 @@ class NotificationFlowTests(TestCase):
fullname='Recipient Student',
user_type='student',
is_active=True,
language=self.lang_fa,
language=self.lang_ru,
fcm='token_recipient'
)
course = self._create_course('course-new', 'New Course A', 'دوره جدید الف', status='registering')
course = self._create_course('course-new', 'New Course A', 'Новый курс А', status='registering')
check_new_course_registration_task()
course.refresh_from_db()
self.assertTrue(course.notified_new_registration)
notif = Notification.objects.filter(user=student_recipient, title="ثبت‌نام دوره جدید").first()
notif = Notification.objects.filter(user=student_recipient, title="Добавлен новый курс").first()
self.assertIsNotNone(notif)
self.assertIn("دوره جدید الف", notif.message)
self.assertIn("Новый курс А", notif.message)
@patch('apps.account.notification_service.send_notification')
def test_notification_template_customization_and_deactivation(self, mock_send):
@ -225,14 +225,14 @@ class NotificationFlowTests(TestCase):
template = NotificationTemplate.objects.create(
notification_type="custom_test_type",
name="Test Template",
title_fa="سلام {student_name} به دوره {course_title}",
title_ru="Привет {student_name} к курсу {course_title}",
title_en="Hello {student_name} to course {course_title}",
body_fa="توضیحات فارسی {student_name}",
body_ru="Русское описание {student_name}",
body_en="English description {student_name}",
is_active=True
)
course = self._create_course('test-tpl-course', 'Tpl Course', 'دوره تی‌پی‌ال')
course = self._create_course('test-tpl-course', 'Tpl Course', 'Курс Шаблон')
# 2. Trigger notification and verify custom text formatting
from apps.account.notification_service import create_and_send_notification
@ -240,8 +240,8 @@ class NotificationFlowTests(TestCase):
user=self.student,
title_en="Default Title EN",
body_en="Default Body EN",
title_fa="Default Title FA",
body_fa="Default Body FA",
title_ru="Default Title RU",
body_ru="Default Body RU",
service='imam-javad',
data={'type': 'custom_test_type', 'course_id': course.id}
)
@ -249,8 +249,8 @@ class NotificationFlowTests(TestCase):
notif = Notification.objects.filter(user=self.student).order_by('-id').first()
self.assertIsNotNone(notif)
# Verify it loaded from template and formatted using course name & student name
self.assertEqual(notif.title, f"سلام {self.student.fullname} به دوره دوره تی‌پی‌ال")
self.assertEqual(notif.message, f"توضیحات فارسی {self.student.fullname}")
self.assertEqual(notif.title, f"Привет {self.student.fullname} к курсу Курс Шаблон")
self.assertEqual(notif.message, f"Русское описание {self.student.fullname}")
# 3. Disable template and verify notification is discarded
template.is_active = False
@ -261,8 +261,8 @@ class NotificationFlowTests(TestCase):
user=self.student,
title_en="Default Title EN",
body_en="Default Body EN",
title_fa="Default Title FA",
body_fa="Default Body FA",
title_ru="Default Title RU",
body_ru="Default Body RU",
service='imam-javad',
data={'type': 'custom_test_type', 'course_id': course.id}
)
@ -277,9 +277,9 @@ class NotificationFlowTests(TestCase):
template = NotificationTemplate.objects.create(
notification_type="sched_test",
name="Scheduled Test",
title_fa="تست زمان‌بندی",
title_ru="Тест планирования",
title_en="Scheduled Test",
body_fa="تست",
body_ru="Тест",
body_en="Test"
)

24
apps/account/tests/notifications/test_quiz_notifications.py

@ -13,9 +13,9 @@ from apps.account.tasks import check_low_quiz_results_task
class QuizNotificationFlowTests(TestCase):
def setUp(self):
# Create languages
self.lang_fa, _ = Language.objects.update_or_create(
code='fa',
defaults={'name': 'Persian', 'status': True, 'countries': []}
self.lang_ru, _ = Language.objects.update_or_create(
code='ru',
defaults={'name': 'Russian', 'status': True, 'countries': []}
)
self.lang_en, _ = Language.objects.update_or_create(
code='en',
@ -33,7 +33,7 @@ class QuizNotificationFlowTests(TestCase):
email='student_quiz@example.com',
username='student_quiz',
fullname='Quiz Student',
language=self.lang_fa,
language=self.lang_ru,
fcm='token_quiz_student'
)
@ -42,7 +42,7 @@ class QuizNotificationFlowTests(TestCase):
self.course = Course.objects.create(
title=[
{"title": "Course 1", "language_code": "en"},
{"title": "دوره ۱", "language_code": "fa"}
{"title": "Курс 1", "language_code": "ru"}
],
slug=[{"title": "course-1", "language_code": "en"}],
category=self.category,
@ -64,7 +64,7 @@ class QuizNotificationFlowTests(TestCase):
course=self.course,
title=[
{"title": "Quiz A", "language_code": "en"},
{"title": "آزمون الف", "language_code": "fa"}
{"title": "Тест А", "language_code": "ru"}
],
each_question_timing=60,
status=True
@ -88,10 +88,10 @@ class QuizNotificationFlowTests(TestCase):
# Run periodic task
check_low_quiz_results_task()
# Verify reminder sent to the student (Persian template check)
notif = Notification.objects.filter(user=self.student, title="پیشنهاد شرکت مجدد در آزمون").first()
# Verify reminder sent to the student (Russian template check)
notif = Notification.objects.filter(user=self.student, title="Предложение пересдать тест").first()
self.assertIsNotNone(notif)
self.assertIn("آزمون الف", notif.message)
self.assertIn("Тест А", notif.message)
self.assertTrue(mock_send.called)
@patch('apps.account.notification_service.send_notification')
@ -110,7 +110,7 @@ class QuizNotificationFlowTests(TestCase):
check_low_quiz_results_task()
self.assertFalse(Notification.objects.filter(user=self.student, title="پیشنهاد شرکت مجدد در آزمون").exists())
self.assertFalse(Notification.objects.filter(user=self.student, title="Предложение пересдать тест").exists())
self.assertFalse(mock_send.called)
@patch('apps.account.notification_service.send_notification')
@ -142,7 +142,7 @@ class QuizNotificationFlowTests(TestCase):
check_low_quiz_results_task()
# Should not suggest retake since they passed subsequently
self.assertFalse(Notification.objects.filter(user=self.student, title="پیشنهاد شرکت مجدد در آزمون").exists())
self.assertFalse(Notification.objects.filter(user=self.student, title="Предложение пересдать тест").exists())
@patch('apps.account.notification_service.send_notification')
def test_no_reminder_if_already_retaken_even_if_failed(self, mock_send):
@ -173,4 +173,4 @@ class QuizNotificationFlowTests(TestCase):
check_low_quiz_results_task()
self.assertFalse(Notification.objects.filter(user=self.student, title="پیشنهاد شرکت مجدد در آزمون").exists())
self.assertFalse(Notification.objects.filter(user=self.student, title="Предложение пересдать тест").exists())

20
apps/account/tests/notifications/test_retention_notifications.py

@ -15,9 +15,9 @@ from apps.account.tasks import check_inactive_students_task
class RetentionNotificationsFlowTests(TestCase):
def setUp(self):
# Create languages
self.lang_fa, _ = Language.objects.update_or_create(
code='fa',
defaults={'name': 'Persian', 'status': True, 'countries': []}
self.lang_ru, _ = Language.objects.update_or_create(
code='ru',
defaults={'name': 'Russian', 'status': True, 'countries': []}
)
self.lang_en, _ = Language.objects.update_or_create(
code='en',
@ -35,7 +35,7 @@ class RetentionNotificationsFlowTests(TestCase):
email='student_ret@example.com',
username='student_ret',
fullname='Ret Student',
language=self.lang_fa,
language=self.lang_ru,
fcm='token_ret_student'
)
@ -44,7 +44,7 @@ class RetentionNotificationsFlowTests(TestCase):
self.course = Course.objects.create(
title=[
{"title": "Course 1", "language_code": "en"},
{"title": "دوره ۱", "language_code": "fa"}
{"title": "Курс 1", "language_code": "ru"}
],
slug=[{"title": "course-1", "language_code": "en"}],
category=self.category,
@ -80,7 +80,7 @@ class RetentionNotificationsFlowTests(TestCase):
check_inactive_students_task()
# Check DB notification
notif = Notification.objects.filter(user=self.student, title="دلمان برایتان تنگ شده!").first()
notif = Notification.objects.filter(user=self.student, title="Мы скучаем по вам!").first()
self.assertIsNotNone(notif)
self.assertTrue(mock_send.called)
@ -93,7 +93,7 @@ class RetentionNotificationsFlowTests(TestCase):
check_inactive_students_task()
self.assertFalse(Notification.objects.filter(user=self.student, title="دلمان برایتان تنگ شده!").exists())
self.assertFalse(Notification.objects.filter(user=self.student, title="Мы скучаем по вам!").exists())
self.assertFalse(mock_send.called)
@patch('apps.account.notification_service.send_notification')
@ -117,7 +117,7 @@ class RetentionNotificationsFlowTests(TestCase):
check_inactive_students_task()
self.assertFalse(Notification.objects.filter(user=self.student, title="دلمان برایتان تنگ شده!").exists())
self.assertFalse(Notification.objects.filter(user=self.student, title="Мы скучаем по вам!").exists())
self.assertFalse(mock_send.called)
@patch('apps.account.notification_service.send_notification')
@ -148,7 +148,7 @@ class RetentionNotificationsFlowTests(TestCase):
s2.save()
# Check DB notification
notif = Notification.objects.filter(user=self.student, title="جای شما در کلاس زنده خالی بود!").first()
notif = Notification.objects.filter(user=self.student, title="Пропуск живых уроков").first()
self.assertIsNotNone(notif)
self.assertIn("دوره ۱", notif.message)
self.assertIn("Курс 1", notif.message)
self.assertTrue(mock_send.called)

28
apps/account/tests/notifications/test_transaction_notifications.py

@ -12,9 +12,9 @@ from apps.transaction.models import TransactionParticipant
class TransactionNotificationsFlowTests(TestCase):
def setUp(self):
# Create languages
self.lang_fa, _ = Language.objects.update_or_create(
code='fa',
defaults={'name': 'Persian', 'status': True, 'countries': []}
self.lang_ru, _ = Language.objects.update_or_create(
code='ru',
defaults={'name': 'Russian', 'status': True, 'countries': []}
)
self.lang_en, _ = Language.objects.update_or_create(
code='en',
@ -32,7 +32,7 @@ class TransactionNotificationsFlowTests(TestCase):
email='student_tx@example.com',
username='student_tx',
fullname='Tx Student',
language=self.lang_fa,
language=self.lang_ru,
fcm='token_tx_student'
)
@ -41,7 +41,7 @@ class TransactionNotificationsFlowTests(TestCase):
self.course = Course.objects.create(
title=[
{"title": "Course 1", "language_code": "en"},
{"title": "دوره ۱", "language_code": "fa"}
{"title": "Курс 1", "language_code": "ru"}
],
slug=[{"title": "course-1", "language_code": "en"}],
category=self.category,
@ -72,10 +72,10 @@ class TransactionNotificationsFlowTests(TestCase):
self.tx.status = TransactionParticipant.TransactionStatus.SUCCESS
self.tx.save()
# Check DB notification (in Persian)
notif = Notification.objects.filter(user=self.student, title="پرداخت موفقیت‌آمیز").first()
# Check DB notification (in Russian)
notif = Notification.objects.filter(user=self.student, title="Оплата успешна").first()
self.assertIsNotNone(notif)
self.assertIn("دوره ۱", notif.message)
self.assertIn("Курс 1", notif.message)
self.assertTrue(mock_send.called)
@patch('apps.account.notification_service.send_notification')
@ -84,10 +84,10 @@ class TransactionNotificationsFlowTests(TestCase):
self.tx.status = TransactionParticipant.TransactionStatus.FAILED
self.tx.save()
# Check DB notification (in Persian)
notif = Notification.objects.filter(user=self.student, title="خطا در پرداخت").first()
# Check DB notification (in Russian)
notif = Notification.objects.filter(user=self.student, title="Ошибка оплаты").first()
self.assertIsNotNone(notif)
self.assertIn("دوره ۱", notif.message)
self.assertIn("Курс 1", notif.message)
self.assertTrue(mock_send.called)
@patch('apps.account.notification_service.send_notification')
@ -96,8 +96,8 @@ class TransactionNotificationsFlowTests(TestCase):
self.tx.status = TransactionParticipant.TransactionStatus.REFUNDED
self.tx.save()
# Check DB notification (in Persian)
notif = Notification.objects.filter(user=self.student, title="بازگشت وجه انجام شد").first()
# Check DB notification (in Russian)
notif = Notification.objects.filter(user=self.student, title="Возврат средств выполнен").first()
self.assertIsNotNone(notif)
self.assertIn("دوره ۱", notif.message)
self.assertIn("Курс 1", notif.message)
self.assertTrue(mock_send.called)
Loading…
Cancel
Save