Browse Source
notification templates and schedule models added and send notification service updated
master
notification templates and schedule models added and send notification service updated
master
8 changed files with 626 additions and 8 deletions
-
172apps/account/management/commands/seed_notification_templates.py
-
53apps/account/migrations/0007_notificationtemplate_schedulednotification.py
-
122apps/account/models/notification.py
-
138apps/account/notification_service.py
-
49apps/account/tasks.py
-
97apps/account/tests/notifications/test_notifications.py
-
2config/settings/base.py
-
1requirements.txt
@ -0,0 +1,172 @@ |
|||||
|
from django.core.management.base import BaseCommand |
||||
|
from apps.account.models import NotificationTemplate |
||||
|
|
||||
|
class Command(BaseCommand): |
||||
|
help = 'Seeds default NotificationTemplate records in the database.' |
||||
|
|
||||
|
def handle(self, *args, **options): |
||||
|
default_templates = [ |
||||
|
{ |
||||
|
"notification_type": "course_access_granted", |
||||
|
"name": "دسترسی به دوره فعال شد", |
||||
|
"title_fa": "دسترسی به دوره فعال شد", |
||||
|
"title_en": "Course Access Granted", |
||||
|
"body_fa": "دسترسی شما به دوره «{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": "ثبتنام دوره موفقیتآمیز بود", |
||||
|
"title_en": "Course Registration Successful", |
||||
|
"body_fa": "ثبتنام شما در دوره «{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": "درس به اتمام رسید", |
||||
|
"title_en": "Lesson Completed", |
||||
|
"body_fa": "تبریک! شما درس «{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": "دوره به اتمام رسید", |
||||
|
"title_en": "Course Completed", |
||||
|
"body_fa": "آفرین! شما دوره «{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": "دوره جدید ثبت شد", |
||||
|
"title_en": "New Course Registered", |
||||
|
"body_fa": "دوره جدید «{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": "پیشنهاد شرکت مجدد در آزمون", |
||||
|
"title_en": "Quiz Retake Suggestion", |
||||
|
"body_fa": "امتیاز شما در آزمون «{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": "زمان کلاس زنده تغییر کرد", |
||||
|
"title_en": "Live Class Rescheduled", |
||||
|
"body_fa": "زمان کلاس زنده برای دوره «{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": "کلاس زنده لغو شد", |
||||
|
"title_en": "Live Class Cancelled", |
||||
|
"body_fa": "کلاس زنده برای دوره «{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": "ویدئو ضبط شده کلاس آماده است", |
||||
|
"title_en": "Live Recording Available", |
||||
|
"body_fa": "ویدئو ضبط شده کلاس زنده برای دوره «{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": "غیبت متوالی در کلاسهای زنده", |
||||
|
"title_en": "Missed Live Sessions", |
||||
|
"body_fa": "شما ۲ جلسه زنده متوالی را در دوره «{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": "پرداخت موفقیتآمیز", |
||||
|
"title_en": "Payment Successful", |
||||
|
"body_fa": "پرداخت شما برای دوره «{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": "خطا در پرداخت", |
||||
|
"title_en": "Payment Failed", |
||||
|
"body_fa": "در پردازش پرداخت شما برای دوره «{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": "بازگشت وجه انجام شد", |
||||
|
"title_en": "Refund Completed", |
||||
|
"body_fa": "بازگشت وجه برای دوره «{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": "دلمان برایتان تنگ شده!", |
||||
|
"title_en": "We Miss You!", |
||||
|
"body_fa": "در یک هفته گذشته هیچ درسی را مطالعه نکردهاید. منتظرتان هستیم تا مسیر یادگیری خود را ادامه دهید.", |
||||
|
"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": "پاسخ جدید از استاد", |
||||
|
"title_en": "New Message from Teacher", |
||||
|
"body_fa": "استاد به پیام شما پاسخ داده است.", |
||||
|
"body_en": "The teacher has replied to your message.", |
||||
|
"placeholders_info": "{student_name}, {fullname}" |
||||
|
}, |
||||
|
{ |
||||
|
"notification_type": "support_reply", |
||||
|
"name": "پاسخ پیام پشتیبان در چت", |
||||
|
"title_fa": "پاسخ جدید از پشتیبان", |
||||
|
"title_en": "New Message from Support", |
||||
|
"body_fa": "پشتیبان به پیام شما پاسخ داده است.", |
||||
|
"body_en": "The support agent has replied to your message.", |
||||
|
"placeholders_info": "{student_name}, {fullname}" |
||||
|
} |
||||
|
] |
||||
|
|
||||
|
count = 0 |
||||
|
for item in default_templates: |
||||
|
obj, created = NotificationTemplate.objects.update_or_create( |
||||
|
notification_type=item['notification_type'], |
||||
|
defaults={ |
||||
|
'name': item['name'], |
||||
|
'title_fa': item['title_fa'], |
||||
|
'title_en': item['title_en'], |
||||
|
'body_fa': item['body_fa'], |
||||
|
'body_en': item['body_en'], |
||||
|
'placeholders_info': item['placeholders_info'], |
||||
|
'is_active': True |
||||
|
} |
||||
|
) |
||||
|
if created: |
||||
|
count += 1 |
||||
|
|
||||
|
self.stdout.write(self.style.SUCCESS(f"Seeding completed! Created {count} new templates, updated existing ones.")) |
||||
@ -0,0 +1,53 @@ |
|||||
|
# Generated by Django 4.2.30 on 2026-07-11 13:28 |
||||
|
|
||||
|
from django.db import migrations, models |
||||
|
import django.db.models.deletion |
||||
|
|
||||
|
|
||||
|
class Migration(migrations.Migration): |
||||
|
|
||||
|
dependencies = [ |
||||
|
('django_celery_beat', '0018_improve_crontab_helptext'), |
||||
|
('account', '0006_notification_action_notification_navigate_to_and_more'), |
||||
|
] |
||||
|
|
||||
|
operations = [ |
||||
|
migrations.CreateModel( |
||||
|
name='NotificationTemplate', |
||||
|
fields=[ |
||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
|
('notification_type', models.CharField(max_length=50, unique=True, verbose_name='notification type')), |
||||
|
('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)')), |
||||
|
('placeholders_info', models.CharField(blank=True, max_length=255, null=True, verbose_name='allowed placeholders description')), |
||||
|
], |
||||
|
options={ |
||||
|
'verbose_name': 'Notification Template', |
||||
|
'verbose_name_plural': 'Notification Templates', |
||||
|
}, |
||||
|
), |
||||
|
migrations.CreateModel( |
||||
|
name='ScheduledNotification', |
||||
|
fields=[ |
||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
|
('name', models.CharField(max_length=100, verbose_name='campaign name')), |
||||
|
('target_group', models.CharField(choices=[('all_students', 'All Students'), ('inactive_students', 'Students Inactive for > 7 Days'), ('all_professors', 'All Professors')], max_length=50, verbose_name='target group')), |
||||
|
('is_active', models.BooleanField(default=True, verbose_name='is active')), |
||||
|
('schedule_type', models.CharField(choices=[('daily', 'Daily'), ('weekly', 'Weekly'), ('monthly', 'Monthly'), ('cron', 'Custom Cron Expression')], default='daily', max_length=20, verbose_name='schedule type')), |
||||
|
('time_of_day', models.TimeField(default='09:00:00', verbose_name='time of day')), |
||||
|
('days_of_week', models.CharField(blank=True, help_text='Comma-separated days (1 for Mon, 6 for Sat, 0 for Sun). E.g. "6,0" for Sat and Sun.', max_length=50, null=True, verbose_name='days of week')), |
||||
|
('days_of_month', models.CharField(blank=True, help_text='Comma-separated days of month (1-31). E.g. "1" for first day of the month.', max_length=50, null=True, verbose_name='days of month')), |
||||
|
('custom_cron', models.CharField(blank=True, help_text='Standard 5-field cron expression: minute hour day_of_month month day_of_week', max_length=100, null=True, verbose_name='custom cron expression')), |
||||
|
('periodic_task', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='scheduled_notification', to='django_celery_beat.periodictask')), |
||||
|
('template', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='account.notificationtemplate', verbose_name='template')), |
||||
|
], |
||||
|
options={ |
||||
|
'verbose_name': 'Scheduled Notification', |
||||
|
'verbose_name_plural': 'Scheduled Notifications', |
||||
|
}, |
||||
|
), |
||||
|
] |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue