From ad022cdf6a361ff61efbd1c98f44b2d69c474c95 Mon Sep 17 00:00:00 2001 From: Parsa Nazer Date: Mon, 17 Feb 2025 00:11:50 +0330 Subject: [PATCH] push notificantion test --- backend/.env.local | 4 +- backend/account/admin.py | 10 ++- .../migrations/0013_pushsubscription.py | 25 +++++++ backend/account/models.py | 65 ++++++++++++++++++- backend/account/serializers.py | 8 ++- backend/account/urls.py | 1 + backend/account/views.py | 18 ++++- backend/core/settings/base.py | 2 +- backend/order/models.py | 12 +++- 9 files changed, 136 insertions(+), 9 deletions(-) create mode 100644 backend/account/migrations/0013_pushsubscription.py diff --git a/backend/.env.local b/backend/.env.local index 064d665..657f88f 100644 --- a/backend/.env.local +++ b/backend/.env.local @@ -26,4 +26,6 @@ SITE_HEADER = 'فروشگاه هی ملز' ACCESS_TOKEN_LIFETIME = 5000 REFRESH_TOKEN_LIFETIME = 5000 -SMS_API_KEY = '' \ No newline at end of file +SMS_API_KEY = '' + +VAPID_PRIVATE_KEY = 'NajogmGTsGsZ_dfURrjUpgsm5fui-s5AzruBQgMh_I4' \ No newline at end of file diff --git a/backend/account/admin.py b/backend/account/admin.py index 53c231d..d4e4460 100644 --- a/backend/account/admin.py +++ b/backend/account/admin.py @@ -87,4 +87,12 @@ class AddressAdmin(ModelAdmin, ImportExportModelAdmin): } def address_display(self, obj): return obj.address[0:35] + '...' - address_display.short_description = 'ادرس' \ No newline at end of file + address_display.short_description = 'ادرس' + + +@admin.register(PushSubscription) +class PushSubscription(ModelAdmin, ImportExportModelAdmin): + import_form_class = ImportForm + export_form_class = ExportForm + compressed_fields = True + warn_unsaved_form = True \ No newline at end of file diff --git a/backend/account/migrations/0013_pushsubscription.py b/backend/account/migrations/0013_pushsubscription.py new file mode 100644 index 0000000..0d069e0 --- /dev/null +++ b/backend/account/migrations/0013_pushsubscription.py @@ -0,0 +1,25 @@ +# Generated by Django 5.1.2 on 2025-02-16 20:03 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0012_alter_user_video_uploader'), + ] + + operations = [ + migrations.CreateModel( + name='PushSubscription', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('endpoint', models.TextField()), + ('keys', models.JSONField()), + ('created_at', models.DateField(auto_now_add=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/backend/account/models.py b/backend/account/models.py index 0e5323b..910673a 100644 --- a/backend/account/models.py +++ b/backend/account/models.py @@ -7,7 +7,7 @@ from django.utils import timezone from rest_framework_simplejwt.token_blacklist.models import BlacklistedToken, OutstandingToken import hashlib from django.contrib import admin - +from django.conf import settings class UserManager(BaseUserManager): def create_user(self, phone, password=None): if not phone: @@ -135,4 +135,65 @@ class UserAddressModel(models.Model): class Meta: verbose_name = 'ادرس کاربر' - verbose_name_plural = 'ادرس های کاربر' \ No newline at end of file + verbose_name_plural = 'ادرس های کاربر' + + +import os +import json +from pywebpush import webpush, WebPushException + +class PushSubscription(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE) + endpoint = models.TextField() + keys = models.JSONField() + created_at = models.DateField(auto_now_add=True) + + def __str__(self): + return f'{self.user} push' + + def send_notif(self, title, body): + payload = { + "title": title, + "body": body, + "icon": '' + } + + try: + webpush( + subscription_info={ + "endpoint": self.endpoint, + "keys": self.keys + }, + data=json.dumps(payload), + vapid_private_key=settings.VAPID_PRIVATE_KEY, + vapid_claims={ + "sub": "mailto:admin@example.com" + } + ) + except WebPushException as ex: + print("Failed to send notification:", ex) + + @classmethod + def send_group_notification(cls, user, title, body): + payload = { + "title": title, + "body": body, + "icon": '' + } + + subscriptions = PushSubscription.objects.filter(user=user) + for sub in subscriptions: + try: + webpush( + subscription_info={ + "endpoint": sub.endpoint, + "keys": sub.keys + }, + data=json.dumps(payload), + vapid_private_key=settings.VAPID_PRIVATE_KEY, + vapid_claims={ + "sub": "mailto:admin@example.com" + } + ) + except WebPushException as ex: + print(f"Failed to send notification to {sub.user}:", ex) \ No newline at end of file diff --git a/backend/account/serializers.py b/backend/account/serializers.py index 5c17c8f..9927dd4 100644 --- a/backend/account/serializers.py +++ b/backend/account/serializers.py @@ -23,4 +23,10 @@ class UserAddressSerializer(serializers.ModelSerializer): user = self.context['request'].user if not user.is_authenticated: raise serializers.ValidationError("You must be logged in to perform this action.") - return data \ No newline at end of file + return data + + +class PushSubscriptionSerializer(serializers.ModelSerializer): + class Meta: + model = PushSubscription + fields = ('endpoint', 'keys') diff --git a/backend/account/urls.py b/backend/account/urls.py index 4b70939..c5ebbf4 100644 --- a/backend/account/urls.py +++ b/backend/account/urls.py @@ -12,4 +12,5 @@ urlpatterns = [ path('address/delete/', views.DeleteAddressView.as_view(), name='delete-address'), path('address/list', views.GetUserAddressesView.as_view(), name='list-addresses'), path('address/', views.GetIDUserAddressView.as_view(), name='get-ID-address'), + path('subscribe', views.SubscribeView.as_view(), name='subscibe') ] \ No newline at end of file diff --git a/backend/account/views.py b/backend/account/views.py index d48aa2a..29aac75 100644 --- a/backend/account/views.py +++ b/backend/account/views.py @@ -2,7 +2,7 @@ from django.shortcuts import render from rest_framework.views import APIView from rest_framework import generics, permissions, status from rest_framework.response import Response -from .serializers import ProfileSerializer, UserAddressSerializer, CustomTokenObtainPairSerializer +from .serializers import * from .models import UserAddressModel, User from rest_framework.permissions import IsAuthenticated, AllowAny from drf_spectacular.utils import extend_schema, OpenApiParameter @@ -171,4 +171,18 @@ class GetIDUserAddressView(generics.RetrieveAPIView): permission_classes = [permissions.IsAuthenticated] def get_queryset(self): - return UserAddressModel.objects.filter(user=self.request.user) \ No newline at end of file + return UserAddressModel.objects.filter(user=self.request.user) + + + +class SubscribeView(APIView): + serializer_class = PushSubscriptionSerializer + def post(self, request): + push_ser = self.serializer_class(data=request.data) + if push_ser.is_valid(): + PushSubscription.objects.update_or_create( + user=request.user, + defaults=(push_ser.validated_data) + ) + return Response(status=status.HTTP_201_CREATED) + return Response(status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file diff --git a/backend/core/settings/base.py b/backend/core/settings/base.py index 9e47af9..237019d 100644 --- a/backend/core/settings/base.py +++ b/backend/core/settings/base.py @@ -29,7 +29,7 @@ DEBUG = True BASE_DIR = Path(__file__).resolve().parent.parent.parent - +VAPID_PRIVATE_KEY = os.getenv('VAPID_PRIVATE_KEY') # Application definition diff --git a/backend/order/models.py b/backend/order/models.py index 9a5bc76..a3f3617 100644 --- a/backend/order/models.py +++ b/backend/order/models.py @@ -45,7 +45,17 @@ class OrderModel(models.Model): # def total_without_tax(self): # return sum(item.total() for item in self.items.all()) - + def save(self, *args, **kwargs): + if self.status == 'POSTED': + try: + push_object = PushSubscription.objects.get(user=self.user) + except: + print('object not found') + try: + push_object.send_notif('سفارش شما به پست شده تغییر کرد', 'سفارش شما به پست شده تغییر کرد') + except: + print('didnt send') + super().save(*args, **kwargs) def total_with_discount(self): total_with_item_discount = sum(item.total_with_discount() for item in self.items.all())