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