From af8d212b3ed3d08546bbc7d8b5ed90a5f29ab13b Mon Sep 17 00:00:00 2001 From: behruz-dev Date: Thu, 30 Oct 2025 16:17:25 +0500 Subject: [PATCH] notification: add notification history --- core/apps/notifications/admin/__init__.py | 3 +- .../admin/notification_history.py | 6 +++ .../migrations/0003_notificationhistory.py | 33 ++++++++++++++ core/apps/notifications/models/__init__.py | 3 +- .../models/notification_history.py | 17 ++++++++ .../serializers/notification_history.py | 11 +++++ .../tasks/create_notification_history.py | 21 +++++++++ core/apps/notifications/urls.py | 4 ++ .../notifications/utils/send_notification.py | 10 +++-- .../views/notification_history.py | 43 +++++++++++++++++++ 10 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 core/apps/notifications/admin/notification_history.py create mode 100644 core/apps/notifications/migrations/0003_notificationhistory.py create mode 100644 core/apps/notifications/models/notification_history.py create mode 100644 core/apps/notifications/serializers/notification_history.py create mode 100644 core/apps/notifications/tasks/create_notification_history.py create mode 100644 core/apps/notifications/views/notification_history.py diff --git a/core/apps/notifications/admin/__init__.py b/core/apps/notifications/admin/__init__.py index b70bf7e..ca4adba 100644 --- a/core/apps/notifications/admin/__init__.py +++ b/core/apps/notifications/admin/__init__.py @@ -1 +1,2 @@ -from .notification import * \ No newline at end of file +from .notification import * +from .notification_history import * \ No newline at end of file diff --git a/core/apps/notifications/admin/notification_history.py b/core/apps/notifications/admin/notification_history.py new file mode 100644 index 0000000..7d55c90 --- /dev/null +++ b/core/apps/notifications/admin/notification_history.py @@ -0,0 +1,6 @@ +from django.contrib import admin + +from core.apps.notifications.models.notification_history import NotificationHistory + + +admin.site.register(NotificationHistory) \ No newline at end of file diff --git a/core/apps/notifications/migrations/0003_notificationhistory.py b/core/apps/notifications/migrations/0003_notificationhistory.py new file mode 100644 index 0000000..717622a --- /dev/null +++ b/core/apps/notifications/migrations/0003_notificationhistory.py @@ -0,0 +1,33 @@ +# Generated by Django 5.2.4 on 2025-10-30 16:16 + +import django.db.models.deletion +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notifications', '0002_notification_type_alter_notification_unique_together'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='NotificationHistory', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('title', models.CharField(max_length=200)), + ('body', models.TextField()), + ('is_read', models.BooleanField(default=False)), + ('data', models.JSONField(blank=True, null=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notification_histories', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/core/apps/notifications/models/__init__.py b/core/apps/notifications/models/__init__.py index b70bf7e..ca4adba 100644 --- a/core/apps/notifications/models/__init__.py +++ b/core/apps/notifications/models/__init__.py @@ -1 +1,2 @@ -from .notification import * \ No newline at end of file +from .notification import * +from .notification_history import * \ No newline at end of file diff --git a/core/apps/notifications/models/notification_history.py b/core/apps/notifications/models/notification_history.py new file mode 100644 index 0000000..24768e8 --- /dev/null +++ b/core/apps/notifications/models/notification_history.py @@ -0,0 +1,17 @@ +from django.db import models + +from core.apps.shared.models import BaseModel +from core.apps.accounts.models import User + + +class NotificationHistory(BaseModel): + title = models.CharField(max_length=200) + body = models.TextField() + user = models.ForeignKey( + User, on_delete=models.CASCADE, related_name='notification_histories' + ) + is_read = models.BooleanField(default=False) + data = models.JSONField(null=True, blank=True) + + def __str__(self): + return f'Notification to {self.user}: {self.title}' \ No newline at end of file diff --git a/core/apps/notifications/serializers/notification_history.py b/core/apps/notifications/serializers/notification_history.py new file mode 100644 index 0000000..64b9a25 --- /dev/null +++ b/core/apps/notifications/serializers/notification_history.py @@ -0,0 +1,11 @@ +from rest_framework import serializers + +from core.apps.notifications.models import NotificationHistory + + +class NotificationHistorySerializer(serializers.ModelSerializer): + class Meta: + model = NotificationHistory + fields = [ + 'id', 'title', 'body', 'data', 'is_read' + ] \ No newline at end of file diff --git a/core/apps/notifications/tasks/create_notification_history.py b/core/apps/notifications/tasks/create_notification_history.py new file mode 100644 index 0000000..99a8464 --- /dev/null +++ b/core/apps/notifications/tasks/create_notification_history.py @@ -0,0 +1,21 @@ +from celery import shared_task + +from django.shortcuts import get_object_or_404 + +from core.apps.notifications.models import NotificationHistory +from core.apps.accounts.models import User + + +@shared_task +def create_history(users, title, body, data=None): + histories = [] + for user in users: + histories.append(NotificationHistory( + title=title, + user=user, + body=body, + data=data, + is_read=False + )) + + NotificationHistory.objects.bulk_create(histories) diff --git a/core/apps/notifications/urls.py b/core/apps/notifications/urls.py index 2bdceed..9fea8cb 100644 --- a/core/apps/notifications/urls.py +++ b/core/apps/notifications/urls.py @@ -1,7 +1,11 @@ from django.urls import path from core.apps.notifications.views import notification +from core.apps.notifications.views import notification_history + urlpatterns = [ path('device/register/', notification.RegisterExpoPushToken.as_view()), + path('history/', notification_history.NotificationHistoryListApiView.as_view()), + path('history//read/', notification_history.NotificationHistoryUpdateApiView.as_view()), ] \ No newline at end of file diff --git a/core/apps/notifications/utils/send_notification.py b/core/apps/notifications/utils/send_notification.py index da9df89..74f7d6a 100644 --- a/core/apps/notifications/utils/send_notification.py +++ b/core/apps/notifications/utils/send_notification.py @@ -1,18 +1,20 @@ import requests from firebase_admin import messaging -from django.conf import settings - from core.apps.notifications.models import Notification +from core.apps.notifications.tasks.create_notification_history import create_history + EXPO_API_URL = "https://exp.host/--/api/v2/push/send" def send_notification(token, title, body, data=None): tokens = list(Notification.objects.exclude(token=token).values_list("token", flat=True)) + users = list(Notification.objects.exclude(token=token).values_list("user", flat=True)) + create_history.delay(users, title, body, data) if not tokens: return {"error": "No tokens found"} - + messages = [ { "to": token, @@ -38,6 +40,8 @@ def send_notification(token, title, body, data=None): def send_web_notification(token, title, body, data=None): tokens = list(Notification.objects.exclude(token=token).values_list('token', flat=True)) + users = list(Notification.objects.exclude(token=token).values_list("user", flat=True)) + create_history.delay(users, title, body, data) if not tokens: return diff --git a/core/apps/notifications/views/notification_history.py b/core/apps/notifications/views/notification_history.py new file mode 100644 index 0000000..2be30c8 --- /dev/null +++ b/core/apps/notifications/views/notification_history.py @@ -0,0 +1,43 @@ +from django.shortcuts import get_object_or_404 + +from rest_framework import generics, views +from rest_framework.response import Response + +from core.apps.accounts.permissions.permissions import HasRolePermission +from core.apps.notifications.models import NotificationHistory +from core.apps.notifications.serializers.notification_history import NotificationHistorySerializer + + + +class NotificationHistoryListApiView(generics.GenericAPIView): + serializer_class = NotificationHistorySerializer + permission_classes = [HasRolePermission] + + def get_queryset(self): + return NotificationHistory.objects.filter(user=self.request.user).order_by('-created_at') + + def get(self, request): + queryset = self.get_queryset() + is_read = request.query_params.get('is_read') + if is_read: + if is_read.lower() == 'true': + queryset = queryset.filter(is_read=True) + if is_read.lower() == 'false': + queryset = queryset.filter(is_read=False) + page = self.paginate_queryset(queryset) + if page is not None: + serializer = self.serializer_class(page, many=True) + return self.get_paginated_response(serializer.data) + return None + + +class NotificationHistoryUpdateApiView(views.APIView): + permission_classes = [HasRolePermission] + + def post(self, request, id): + obj = get_object_or_404(NotificationHistory, id=id) + if obj.is_read: + return Response({'success': False, "message": 'already readed'}, status=400) + obj.is_read = True + obj.save() + return Response({'success': True, "message": 'readed'}, status=200)