From 5ac1715adb283d858684c4f902bd06064e7c0417 Mon Sep 17 00:00:00 2001 From: Sayyid Hamid Mahdavi Date: Tue, 2 Sep 2025 20:12:20 +0330 Subject: [PATCH] bulk push --- apps/emails/__init__.py | 0 apps/emails/admin.py | 11 ++++++ apps/emails/apps.py | 6 +++ apps/emails/migrations/__init__.py | 0 apps/emails/models.py | 37 +++++++++++++++++++ apps/emails/serializers.py | 14 +++++++ apps/emails/tasks.py | 12 ++++++ apps/emails/tests.py | 3 ++ apps/emails/urls.py | 59 ++++++++++++++++++++++++++++++ apps/emails/views.py | 46 +++++++++++++++++++++++ apps/push_notifications/models.py | 2 +- templates/admin/custom_form.html | 12 ++++++ 12 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 apps/emails/__init__.py create mode 100644 apps/emails/admin.py create mode 100644 apps/emails/apps.py create mode 100644 apps/emails/migrations/__init__.py create mode 100644 apps/emails/models.py create mode 100644 apps/emails/serializers.py create mode 100644 apps/emails/tasks.py create mode 100644 apps/emails/tests.py create mode 100644 apps/emails/urls.py create mode 100644 apps/emails/views.py create mode 100644 templates/admin/custom_form.html diff --git a/apps/emails/__init__.py b/apps/emails/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/emails/admin.py b/apps/emails/admin.py new file mode 100644 index 0000000..34ac502 --- /dev/null +++ b/apps/emails/admin.py @@ -0,0 +1,11 @@ +from django.contrib import admin + +from .models import Email + + + + + + +admin.site.register(Email) + diff --git a/apps/emails/apps.py b/apps/emails/apps.py new file mode 100644 index 0000000..3ddc8ff --- /dev/null +++ b/apps/emails/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class EmailsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.emails' diff --git a/apps/emails/migrations/__init__.py b/apps/emails/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/emails/models.py b/apps/emails/models.py new file mode 100644 index 0000000..19dc24c --- /dev/null +++ b/apps/emails/models.py @@ -0,0 +1,37 @@ +from django.db import models +from oauth2_provider.settings import oauth2_settings + +from apps.users.models import User +from utils.models import BaseModel +from django.utils.translation import gettext_lazy as _ + + +class EmailQuerySet(models.QuerySet): + pass + + +class Email(BaseModel): + class StateChoices(models.IntegerChoices): + INIT = 0, _('init') + START = 1, _('Start') + DONE = 2, _('done') + TIMEOUT = 3, _('timeout') + FAILED = 4, _('failed') + + user = models.ForeignKey(User, on_delete=models.PROTECT) + application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT, + related_name='+', null=True, blank=True) + + title = models.CharField(max_length=128) + message = models.TextField() + priority = models.IntegerField(default=5) + extras = models.JSONField(default=dict) + stats = models.IntegerField(default=StateChoices.INIT, choices=StateChoices.choices) + + # bulk = models.ForeignKey("BulkEmail", null=True, blank=True, on_delete=models.PROTECT) + + objects = EmailQuerySet.as_manager() + + def send_email(self): + from apps.emails.tasks import send_email + send_email.delay(str(self.uuid)) diff --git a/apps/emails/serializers.py b/apps/emails/serializers.py new file mode 100644 index 0000000..779935c --- /dev/null +++ b/apps/emails/serializers.py @@ -0,0 +1,14 @@ +from rest_framework import serializers + +from .models import Email + + +class EmailSerializer(serializers.ModelSerializer): + class Meta: + model = Email + fields = ( + "user", + "application" + ) + + read_only_fields = ("user","application") diff --git a/apps/emails/tasks.py b/apps/emails/tasks.py new file mode 100644 index 0000000..3ef3fe2 --- /dev/null +++ b/apps/emails/tasks.py @@ -0,0 +1,12 @@ +from main import celery_app +from utils.clients import gotify + + +@celery_app.task +def send_email(push_message_uuid): + from .models import Email + email = Email.objects.get(uuid=push_message_uuid) + email.send_email() + + email.state = email.StateChoices.DONE + email.save() diff --git a/apps/emails/tests.py b/apps/emails/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/apps/emails/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/emails/urls.py b/apps/emails/urls.py new file mode 100644 index 0000000..8293d59 --- /dev/null +++ b/apps/emails/urls.py @@ -0,0 +1,59 @@ +from rest_framework import routers +from rest_framework.routers import DefaultRouter, DynamicRoute, Route +from . import views +from django.urls import NoReverseMatch, path, re_path + + +app_name = 'push_notifications' + + +class ApplicationRouter(DefaultRouter): + + routes = [ + # List route. + Route( + url=r'^{prefix}/{trailing_slash}$', + mapping={ + 'get': 'list', + 'post': 'create' + }, + name='{basename}-list', + detail=False, + initkwargs={'suffix': 'List'} + ), + # Dynamically generated list routes. Generated using + # @action(detail=False) decorator on methods of the viewset. + DynamicRoute( + url=r'^{prefix}//{url_path}{trailing_slash}$', + name='{basename}-{url_name}', + detail=False, + initkwargs={} + ), + # # Detail route. + Route( + url=r'^{prefix}//{lookup}{trailing_slash}$', + mapping={ + 'get': 'retrieve', + 'put': 'update', + 'patch': 'partial_update', + 'delete': 'destroy' + }, + name='{basename}-detail', + detail=True, + initkwargs={'suffix': 'Instance'} + ), + # # Dynamically generated detail routes. Generated using + # # @action(detail=True) decorator on methods of the viewset. + DynamicRoute( + url=r'^{prefix}//{lookup}/{url_path}{trailing_slash}$', + name='{basename}-{url_name}', + detail=True, + initkwargs={} + ), + ] + + +application_router = DefaultRouter() +application_router.register('application', views.ApplicationPushUserViewSet, basename='application') + +urlpatterns = application_router.urls diff --git a/apps/emails/views.py b/apps/emails/views.py new file mode 100644 index 0000000..ed336ca --- /dev/null +++ b/apps/emails/views.py @@ -0,0 +1,46 @@ +from django.shortcuts import render + +from django.http import Http404 +from django.shortcuts import render +from oauth2_provider.contrib.rest_framework import TokenMatchesOASRequirements +from rest_framework import viewsets, mixins +from rest_framework.generics import get_object_or_404 +from rest_framework.viewsets import GenericViewSet + +from .models import Email +from .serializers import EmailSerializer +from apps.gooyal_oauth2.models import Application +from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements +from apps.gooyal_oauth2.utils import get_application +from ..users.models import User + + + +class ApplicationEmailViewSet( + # mixins.RetrieveModelMixin, + mixins.ListModelMixin, + # mixins.UpdateModelMixin, + # mixins.DestroyModelMixin, + mixins.CreateModelMixin, + GenericViewSet +): + queryset = Email.objects.all() + serializer_class = EmailSerializer + permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements] + required_alternate_scopes = { + "GET": [["notifications.application.email:submit_email"]], + "POST": [["notifications.application.email:submit_email"]], + } + + def get_object(self): + queryset = self.get_queryset() + user_uuid = self.kwargs.get('user_uuid') + push_message = get_object_or_404(queryset, user=user_uuid) + return push_message + + def perform_create(self, serializer): + user_uuid = self.kwargs.get('user_uuid') + user = get_object_or_404(User, uuid=user_uuid) + application = get_application(self.request) + instance = serializer.save(user=user, application=application) + instance.send_email() diff --git a/apps/push_notifications/models.py b/apps/push_notifications/models.py index a5ac60e..42fc57e 100644 --- a/apps/push_notifications/models.py +++ b/apps/push_notifications/models.py @@ -83,7 +83,7 @@ class BulkPushMessage(BaseModel): def bulk_push_task(self): from .tasks import bulk_push - bulk_push(str(self.pk)) + bulk_push.delay(str(self.pk)) def push_to_all(self, data: dict): to_start = BulkPushMessage.objects.filter(pk=self.pk, state=self.StateChoices.INIT).update(state=self.StateChoices.START) diff --git a/templates/admin/custom_form.html b/templates/admin/custom_form.html new file mode 100644 index 0000000..48a7f68 --- /dev/null +++ b/templates/admin/custom_form.html @@ -0,0 +1,12 @@ +{% extends "admin/change_list.html" %} +hehe +{% block content %} +
+
+ {% csrf_token %} + {{ form.as_p }} + +
+
+{# {{ block.super }}#} +{% endblock %} \ No newline at end of file