Browse Source

sceduled notifications added and set api for admin panel

master
Mohsen Taba 1 week ago
parent
commit
5e29c92ebc
  1. 55
      apps/account/migrations/0009_schedulednotification_body_en_and_more.py
  2. 12
      apps/account/models/notification.py
  3. 17
      apps/account/serializers/notification.py
  4. 47
      apps/account/tasks.py
  5. 1
      apps/account/urls.py
  6. 29
      apps/account/views/notification.py

55
apps/account/migrations/0009_schedulednotification_body_en_and_more.py

@ -0,0 +1,55 @@
# Generated by Django 4.2.30 on 2026-07-11 16:34
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('account', '0008_remove_notificationtemplate_body_fa_and_more'),
]
operations = [
migrations.AddField(
model_name='schedulednotification',
name='body_en',
field=models.TextField(blank=True, max_length=1024, null=True, verbose_name='body (EN)'),
),
migrations.AddField(
model_name='schedulednotification',
name='body_ru',
field=models.TextField(blank=True, max_length=1024, null=True, verbose_name='body (RU)'),
),
migrations.AddField(
model_name='schedulednotification',
name='send_to_all',
field=models.BooleanField(default=False, verbose_name='send to all active users'),
),
migrations.AddField(
model_name='schedulednotification',
name='title_en',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='title (EN)'),
),
migrations.AddField(
model_name='schedulednotification',
name='title_ru',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='title (RU)'),
),
migrations.AddField(
model_name='schedulednotification',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='scheduled_notifications', to=settings.AUTH_USER_MODEL, verbose_name='user'),
),
migrations.AlterField(
model_name='schedulednotification',
name='target_group',
field=models.CharField(blank=True, choices=[('all_students', 'All Students'), ('inactive_students', 'Students Inactive for > 7 Days'), ('all_professors', 'All Professors')], max_length=50, null=True, verbose_name='target group'),
),
migrations.AlterField(
model_name='schedulednotification',
name='template',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='account.notificationtemplate', verbose_name='template'),
),
]

12
apps/account/models/notification.py

@ -64,8 +64,16 @@ class ScheduledNotification(models.Model):
CRON = 'cron', _('Custom Cron Expression') CRON = 'cron', _('Custom Cron Expression')
name = models.CharField(max_length=100, verbose_name=_('campaign name')) name = models.CharField(max_length=100, verbose_name=_('campaign name'))
template = models.ForeignKey(NotificationTemplate, on_delete=models.CASCADE, verbose_name=_('template'))
target_group = models.CharField(max_length=50, choices=TargetGroupChoices.choices, verbose_name=_('target group'))
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')) is_active = models.BooleanField(default=True, verbose_name=_('is active'))
schedule_type = models.CharField(max_length=20, choices=ScheduleTypeChoices.choices, default=ScheduleTypeChoices.DAILY, verbose_name=_('schedule type')) schedule_type = models.CharField(max_length=20, choices=ScheduleTypeChoices.choices, default=ScheduleTypeChoices.DAILY, verbose_name=_('schedule type'))

17
apps/account/serializers/notification.py

@ -1,7 +1,7 @@
from rest_framework import serializers from rest_framework import serializers
from apps.account.models import Notification, NotificationTemplate
from apps.account.models import Notification, NotificationTemplate, ScheduledNotification
from apps.account.models import User from apps.account.models import User
@ -43,3 +43,18 @@ class NotificationTemplateSerializer(serializers.ModelSerializer):
'id', 'notification_type', 'name', 'is_active', 'id', 'notification_type', 'name', 'is_active',
'title_ru', 'title_en', 'body_ru', 'body_en', 'placeholders_info' 'title_ru', 'title_en', 'body_ru', 'body_en', 'placeholders_info'
] ]
class AdminScheduledNotificationSerializer(serializers.ModelSerializer):
user_fullname = serializers.CharField(source='user.fullname', read_only=True)
user_email = serializers.CharField(source='user.email', read_only=True)
template_name = serializers.CharField(source='template.name', read_only=True)
class Meta:
model = ScheduledNotification
fields = [
'id', 'name', 'template', 'template_name', 'target_group', 'send_to_all',
'user', 'user_fullname', 'user_email', 'title_ru', 'title_en',
'body_ru', 'body_en', 'is_active', 'schedule_type', 'time_of_day',
'days_of_week', 'days_of_month', 'custom_cron'
]

47
apps/account/tasks.py

@ -409,25 +409,38 @@ def run_scheduled_notification_task(campaign_id):
from datetime import timedelta from datetime import timedelta
campaign = ScheduledNotification.objects.filter(id=campaign_id).first() campaign = ScheduledNotification.objects.filter(id=campaign_id).first()
if not campaign or not campaign.is_active or not campaign.template.is_active:
if not campaign or not campaign.is_active or (campaign.template and not campaign.template.is_active):
logger.info(f"[Scheduled Notification Task] Campaign {campaign_id} not found or disabled.") logger.info(f"[Scheduled Notification Task] Campaign {campaign_id} not found or disabled.")
return return
template = campaign.template template = campaign.template
title_en = campaign.title_en or (template.title_en if template else "")
body_en = campaign.body_en or (template.body_en if template else "")
title_ru = campaign.title_ru or (template.title_ru if template else "")
body_ru = campaign.body_ru or (template.body_ru if template else "")
notification_type = template.notification_type if template else "custom"
User = get_user_model() User = get_user_model()
users = User.objects.filter(is_active=True) users = User.objects.filter(is_active=True)
# 1. Filter target audience # 1. Filter target audience
if campaign.target_group == ScheduledNotification.TargetGroupChoices.INACTIVE_STUDENTS:
seven_days_ago = timezone.now() - timedelta(days=7)
users = users.filter(
user_type='student',
last_login__lt=seven_days_ago
)
elif campaign.target_group == ScheduledNotification.TargetGroupChoices.ALL_STUDENTS:
users = users.filter(user_type='student')
elif campaign.target_group == ScheduledNotification.TargetGroupChoices.ALL_PROFESSORS:
users = users.filter(user_type='professor')
if campaign.send_to_all:
pass
elif campaign.user:
users = users.filter(id=campaign.user_id)
else:
if campaign.target_group == ScheduledNotification.TargetGroupChoices.INACTIVE_STUDENTS:
seven_days_ago = timezone.now() - timedelta(days=7)
users = users.filter(
user_type='student',
last_login__lt=seven_days_ago
)
elif campaign.target_group == ScheduledNotification.TargetGroupChoices.ALL_STUDENTS:
users = users.filter(user_type='student')
elif campaign.target_group == ScheduledNotification.TargetGroupChoices.ALL_PROFESSORS:
users = users.filter(user_type='professor')
else:
users = users.none()
logger.info(f"[Scheduled Notification Task] Sending campaign '{campaign.name}' to {users.count()} users...") logger.info(f"[Scheduled Notification Task] Sending campaign '{campaign.name}' to {users.count()} users...")
@ -435,13 +448,13 @@ def run_scheduled_notification_task(campaign_id):
for user in users: for user in users:
create_and_send_notification( create_and_send_notification(
user=user, user=user,
title_en=template.title_en,
body_en=template.body_en,
title_ru=template.title_ru,
body_ru=template.body_ru,
title_en=title_en,
body_en=body_en,
title_ru=title_ru,
body_ru=body_ru,
service='imam-javad', service='imam-javad',
data={'type': template.notification_type},
notification_type=template.notification_type
data={'type': notification_type},
notification_type=notification_type
) )

1
apps/account/urls.py

@ -12,6 +12,7 @@ admin_router.register(r'users', views.AdminUserViewSet, basename='admin-users')
admin_router.register(r'directory-users', views.AdminUserDirectoryViewSet, basename='admin-directory-users') admin_router.register(r'directory-users', views.AdminUserDirectoryViewSet, basename='admin-directory-users')
admin_router.register(r'notifications', views.AdminNotificationViewSet, basename='admin-notifications') admin_router.register(r'notifications', views.AdminNotificationViewSet, basename='admin-notifications')
admin_router.register(r'notification-templates', views.AdminNotificationTemplateViewSet, basename='admin-notification-templates') admin_router.register(r'notification-templates', views.AdminNotificationTemplateViewSet, basename='admin-notification-templates')
admin_router.register(r'scheduled-notifications', views.AdminScheduledNotificationViewSet, basename='admin-scheduled-notifications')
# Hide admin viewsets from swagger # Hide admin viewsets from swagger
for prefix, viewset, basename in admin_router.registry: for prefix, viewset, basename in admin_router.registry:

29
apps/account/views/notification.py

@ -4,8 +4,8 @@ from rest_framework.authentication import TokenAuthentication
from drf_yasg.utils import swagger_auto_schema from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi from drf_yasg import openapi
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from apps.account.serializers import NotificationSerializer, NotificationSendSerializer, AdminNotificationSerializer, NotificationTemplateSerializer
from apps.account.models import Notification, User, NotificationTemplate
from apps.account.serializers import NotificationSerializer, NotificationSendSerializer, AdminNotificationSerializer, NotificationTemplateSerializer, AdminScheduledNotificationSerializer
from apps.account.models import Notification, User, NotificationTemplate, ScheduledNotification
from apps.account.tasks import send_notification from apps.account.tasks import send_notification
from utils.pagination import StandardResultsSetPagination from utils.pagination import StandardResultsSetPagination
from django.db.models import Q from django.db.models import Q
@ -395,3 +395,28 @@ class AdminNotificationTemplateViewSet(ModelViewSet):
def destroy(self, request, *args, **kwargs): def destroy(self, request, *args, **kwargs):
return Response({'error': 'Method not allowed.'}, status=status.HTTP_405_METHOD_NOT_ALLOWED) return Response({'error': 'Method not allowed.'}, status=status.HTTP_405_METHOD_NOT_ALLOWED)
class AdminScheduledNotificationViewSet(ModelViewSet):
permission_classes = [IsSuperAdmin]
authentication_classes = [TokenAuthentication]
serializer_class = AdminScheduledNotificationSerializer
pagination_class = StandardResultsSetPagination
def get_queryset(self):
queryset = ScheduledNotification.objects.all().select_related('template', 'user')
search = self.request.query_params.get('search', None)
if search:
queryset = queryset.filter(
Q(name__icontains=search) |
Q(title_ru__icontains=search) |
Q(title_en__icontains=search) |
Q(template__name__icontains=search)
)
is_active_param = self.request.query_params.get('is_active', None)
if is_active_param is not None:
queryset = queryset.filter(is_active=(is_active_param.lower() == 'true'))
return queryset.order_by('-id')
Loading…
Cancel
Save