You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

190 lines
8.6 KiB

from django.db import models
from django.utils.translation import gettext_lazy as _
class Notification(models.Model):
class ServiceChoices(models.TextChoices):
IMAM_JAVAD = 'imam-javad', _('Imam Javad')
DOBOODI = 'doboodi', _('Doboodi')
title = models.CharField(max_length=255, verbose_name=_('title'))
message = models.TextField(max_length=512, verbose_name=_('message'))
user = models.ForeignKey("account.User", on_delete=models.CASCADE, verbose_name=_('user'), related_name='notifications')
is_read = models.BooleanField(default=False, verbose_name=_('is read'))
service = models.CharField(
max_length=20,
choices=ServiceChoices.choices,
default=ServiceChoices.IMAM_JAVAD,
verbose_name=_('service')
)
notification_type = models.CharField(max_length=50, null=True, blank=True, verbose_name=_('notification type'))
action = models.CharField(max_length=50, default='navigate', verbose_name=_('action'))
navigate_to = models.CharField(max_length=255, null=True, blank=True, verbose_name=_('navigate to'))
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at'), null=True)
updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at'), null=True)
class Meta:
verbose_name = _('Notification')
verbose_name_plural = _('Notifications')
def __str__(self):
return self.title
class NotificationTemplate(models.Model):
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_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'))
class Meta:
verbose_name = _('Notification Template')
verbose_name_plural = _('Notification Templates')
def __str__(self):
return f"{self.name} ({self.notification_type})"
class ScheduledNotification(models.Model):
class TargetGroupChoices(models.TextChoices):
ALL_STUDENTS = 'all_students', _('All Students')
INACTIVE_STUDENTS = 'inactive_students', _('Students Inactive for > 7 Days')
ALL_PROFESSORS = 'all_professors', _('All Professors')
class ScheduleTypeChoices(models.TextChoices):
DO_NOT_REPEAT = 'do_not_repeat', _('Do Not Repeat')
DAILY = 'daily', _('Daily')
WEEKLY = 'weekly', _('Weekly')
MONTHLY = 'monthly', _('Monthly')
CRON = 'cron', _('Custom Cron Expression')
name = models.CharField(max_length=100, verbose_name=_('campaign name'))
template = models.ForeignKey(NotificationTemplate, on_delete=models.CASCADE, null=True, blank=True, verbose_name=_('template'))
target_group = models.CharField(max_length=50, choices=TargetGroupChoices.choices, null=True, blank=True, verbose_name=_('target group'))
send_to_all = models.BooleanField(default=False, verbose_name=_('send to all active users'))
user = models.ForeignKey("account.User", on_delete=models.CASCADE, null=True, blank=True, verbose_name=_('user'), related_name='scheduled_notifications')
title_ru = models.CharField(max_length=255, blank=True, null=True, verbose_name=_('title (RU)'))
title_en = models.CharField(max_length=255, blank=True, null=True, verbose_name=_('title (EN)'))
body_ru = models.TextField(max_length=1024, blank=True, null=True, verbose_name=_('body (RU)'))
body_en = models.TextField(max_length=1024, blank=True, null=True, verbose_name=_('body (EN)'))
is_active = models.BooleanField(default=True, verbose_name=_('is active'))
schedule_type = models.CharField(max_length=20, choices=ScheduleTypeChoices.choices, default=ScheduleTypeChoices.DO_NOT_REPEAT, verbose_name=_('schedule type'))
scheduled_time = models.DateTimeField(null=True, blank=True, verbose_name=_('scheduled time'))
time_of_day = models.TimeField(default="09:00:00", verbose_name=_('time of day'))
days_of_week = models.CharField(max_length=50, blank=True, null=True, help_text=_('Comma-separated days (1 for Mon, 6 for Sat, 0 for Sun). E.g. "6,0" for Sat and Sun.'), verbose_name=_('days of week'))
days_of_month = models.CharField(max_length=50, blank=True, null=True, help_text=_('Comma-separated days of month (1-31). E.g. "1" for first day of the month.'), verbose_name=_('days of month'))
custom_cron = models.CharField(max_length=100, blank=True, null=True, help_text=_('Standard 5-field cron expression: minute hour day_of_month month day_of_week'), verbose_name=_('custom cron expression'))
periodic_task = models.OneToOneField(
'django_celery_beat.PeriodicTask',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='scheduled_notification'
)
class Meta:
verbose_name = _('Scheduled Notification')
verbose_name_plural = _('Scheduled Notifications')
def __str__(self):
return self.name
def save(self, *args, **kwargs):
from django_celery_beat.models import CrontabSchedule, ClockedSchedule, PeriodicTask
import json
from django.conf import settings
schedule = None
clocked = None
one_off = False
if self.schedule_type == self.ScheduleTypeChoices.DO_NOT_REPEAT:
if self.scheduled_time:
clocked, _ = ClockedSchedule.objects.get_or_create(
clocked_time=self.scheduled_time
)
one_off = True
else:
if self.periodic_task:
task = self.periodic_task
self.periodic_task = None
super().save(*args, **kwargs)
task.delete()
return
else:
super().save(*args, **kwargs)
return
else:
# Calculate cron fields based on schedule_type
minute = str(self.time_of_day.minute)
hour = str(self.time_of_day.hour)
day_of_month = '*'
month_of_year = '*'
day_of_week = '*'
if self.schedule_type == self.ScheduleTypeChoices.DAILY:
pass
elif self.schedule_type == self.ScheduleTypeChoices.WEEKLY:
day_of_week = self.days_of_week or '*'
elif self.schedule_type == self.ScheduleTypeChoices.MONTHLY:
day_of_month = self.days_of_month or '1'
elif self.schedule_type == self.ScheduleTypeChoices.CRON:
if self.custom_cron:
parts = self.custom_cron.split()
if len(parts) >= 5:
minute, hour, day_of_month, month_of_year, day_of_week = parts[:5]
schedule, _ = CrontabSchedule.objects.get_or_create(
minute=minute,
hour=hour,
day_of_week=day_of_week,
day_of_month=day_of_month,
month_of_year=month_of_year,
timezone=getattr(settings, 'CELERY_TIMEZONE', 'Asia/Tehran')
)
task_name = f"Scheduled Notification: {self.name}"
task_kwargs = json.dumps({
'campaign_id': self.id
})
if self.periodic_task:
task = self.periodic_task
task.name = task_name
task.crontab = schedule
task.clocked = clocked
task.one_off = one_off
task.enabled = self.is_active
task.kwargs = task_kwargs
task.save()
else:
task = PeriodicTask.objects.create(
name=task_name,
task='apps.account.tasks.run_scheduled_notification_task',
crontab=schedule,
clocked=clocked,
one_off=one_off,
enabled=self.is_active,
kwargs=task_kwargs
)
self.periodic_task = task
super().save(*args, **kwargs)
def delete(self, *args, **kwargs):
if self.periodic_task:
self.periodic_task.delete()
super().delete(*args, **kwargs)