diff --git a/apps/account/management/commands/seed_notification_templates.py b/apps/account/management/commands/seed_notification_templates.py index 14d472c..19c1695 100644 --- a/apps/account/management/commands/seed_notification_templates.py +++ b/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 diff --git a/apps/account/migrations/0008_remove_notificationtemplate_body_fa_and_more.py b/apps/account/migrations/0008_remove_notificationtemplate_body_fa_and_more.py new file mode 100644 index 0000000..bbb4cca --- /dev/null +++ b/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)'), + ), + ] diff --git a/apps/account/models/notification.py b/apps/account/models/notification.py index 2e67ed2..029479d 100644 --- a/apps/account/models/notification.py +++ b/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')) diff --git a/apps/account/notification_service.py b/apps/account/notification_service.py index b34c991..f46991a 100644 --- a/apps/account/notification_service.py +++ b/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: diff --git a/apps/account/tasks.py b/apps/account/tasks.py index d7ea486..4c73752 100644 --- a/apps/account/tasks.py +++ b/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 diff --git a/apps/account/tests/notifications/test_communication_notifications.py b/apps/account/tests/notifications/test_communication_notifications.py index 4d027cc..23299fb 100644 --- a/apps/account/tests/notifications/test_communication_notifications.py +++ b/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') diff --git a/apps/account/tests/notifications/test_live_notifications.py b/apps/account/tests/notifications/test_live_notifications.py index 30c4da6..8e78098 100644 --- a/apps/account/tests/notifications/test_live_notifications.py +++ b/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) diff --git a/apps/account/tests/notifications/test_notifications.py b/apps/account/tests/notifications/test_notifications.py index 1aec6ea..02ea0e0 100644 --- a/apps/account/tests/notifications/test_notifications.py +++ b/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" ) diff --git a/apps/account/tests/notifications/test_quiz_notifications.py b/apps/account/tests/notifications/test_quiz_notifications.py index 2d9fcf3..8a27ef7 100644 --- a/apps/account/tests/notifications/test_quiz_notifications.py +++ b/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()) diff --git a/apps/account/tests/notifications/test_retention_notifications.py b/apps/account/tests/notifications/test_retention_notifications.py index 0a519f0..3f197da 100644 --- a/apps/account/tests/notifications/test_retention_notifications.py +++ b/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) diff --git a/apps/account/tests/notifications/test_transaction_notifications.py b/apps/account/tests/notifications/test_transaction_notifications.py index c9123b2..4e838a2 100644 --- a/apps/account/tests/notifications/test_transaction_notifications.py +++ b/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)