From fb91383be520af5bd980f8f678625c876e796d64 Mon Sep 17 00:00:00 2001 From: dormantman Date: Wed, 25 Dec 2019 11:16:05 +0300 Subject: [PATCH 01/12] Added last subscribe/unsubscribe dates --- apps/notification/models.py | 8 +++++++- apps/notification/serializers/common.py | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/notification/models.py b/apps/notification/models.py index a7eebbe8..5a83a598 100644 --- a/apps/notification/models.py +++ b/apps/notification/models.py @@ -1,10 +1,12 @@ """Notification app models.""" from django.conf import settings from django.db import models +from django.utils.timezone import now from django.utils.translation import ugettext_lazy as _ + from account.models import User from utils.methods import generate_string_code -from utils.models import ProjectBaseMixin, TranslatedFieldsMixin, TJSONField +from utils.models import ProjectBaseMixin, TJSONField, TranslatedFieldsMixin class SubscriptionType(ProjectBaseMixin, TranslatedFieldsMixin): @@ -109,6 +111,9 @@ class Subscriber(ProjectBaseMixin): subscription_type = models.ForeignKey(SubscriptionType, on_delete=models.CASCADE, null=True, default=None) + subscribe_date = models.DateTimeField(_('Last subscribe date', blank=True, null=True, default=now)) + unsubscribe_date = models.DateTimeField(_('Last unsubscribe date'), blank=True, null=True, default=None) + objects = SubscriberManager.from_queryset(SubscriberQuerySet)() class Meta: @@ -125,6 +130,7 @@ class Subscriber(ProjectBaseMixin): def unsubscribe(self): """Unsubscribe user.""" + self.unsubscribe_date = now() self.state = self.UNUSABLE self.save() diff --git a/apps/notification/serializers/common.py b/apps/notification/serializers/common.py index 2f15b733..c7bfec42 100644 --- a/apps/notification/serializers/common.py +++ b/apps/notification/serializers/common.py @@ -35,6 +35,8 @@ class SubscribeSerializer(serializers.ModelSerializer): 'email', 'subscription_type', 'state', + 'subscribe_date', + 'unsubscribe_date' ) read_only_fields = ('state',) From eac841f41f79baf0b2024fc2c139ee825135735c Mon Sep 17 00:00:00 2001 From: dormantman Date: Fri, 27 Dec 2019 15:30:41 +0300 Subject: [PATCH 02/12] Added model Subscribe --- apps/notification/models.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/apps/notification/models.py b/apps/notification/models.py index 5a83a598..3a703f29 100644 --- a/apps/notification/models.py +++ b/apps/notification/models.py @@ -109,10 +109,7 @@ class Subscriber(ProjectBaseMixin): ) old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None) - subscription_type = models.ForeignKey(SubscriptionType, on_delete=models.CASCADE, null=True, default=None) - - subscribe_date = models.DateTimeField(_('Last subscribe date', blank=True, null=True, default=now)) - unsubscribe_date = models.DateTimeField(_('Last unsubscribe date'), blank=True, null=True, default=None) + subscription_types = models.ManyToManyField(SubscriptionType, through='Subscribe') objects = SubscriberManager.from_queryset(SubscriberQuerySet)() @@ -130,7 +127,6 @@ class Subscriber(ProjectBaseMixin): def unsubscribe(self): """Unsubscribe user.""" - self.unsubscribe_date = now() self.state = self.UNUSABLE self.save() @@ -147,3 +143,19 @@ class Subscriber(ProjectBaseMixin): url = settings.SITE_REDIRECT_URL_UNSUBSCRIBE query = f'?code={self.update_code}' return f'{schema}://{site_domain}{url}{query}' + + +class Subscribe(ProjectBaseMixin): + """Subscribe model.""" + + subscribe_date = models.DateTimeField(_('Last subscribe date'), blank=True, null=True, default=now) + unsubscribe_date = models.DateTimeField(_('Last unsubscribe date'), blank=True, null=True, default=None) + + subscriber = models.ForeignKey(Subscriber, on_delete=models.CASCADE) + subscription_type = models.ForeignKey(SubscriptionType, on_delete=models.CASCADE) + + class Meta: + """Meta class.""" + + verbose_name = _('Subscribe') + verbose_name_plural = _('Subscribes') From 7baf022a7737e1e43e2b1b11f7568c64991fa7ef Mon Sep 17 00:00:00 2001 From: dormantman Date: Fri, 27 Dec 2019 17:10:41 +0300 Subject: [PATCH 03/12] Added correct creating subscribe --- apps/account/serializers/common.py | 7 ++++ apps/notification/models.py | 13 +++++-- apps/notification/serializers/common.py | 51 ++++++++++++++++++------- apps/notification/urls/common.py | 1 + apps/notification/views/common.py | 26 ++++++++++--- 5 files changed, 76 insertions(+), 22 deletions(-) diff --git a/apps/account/serializers/common.py b/apps/account/serializers/common.py index 09136934..62113d9b 100644 --- a/apps/account/serializers/common.py +++ b/apps/account/serializers/common.py @@ -8,6 +8,7 @@ from rest_framework import serializers from rest_framework import validators as rest_validators from account import models, tasks +from notification.models import Subscriber from utils import exceptions as utils_exceptions from utils import methods as utils_methods @@ -46,6 +47,12 @@ class UserSerializer(serializers.ModelSerializer): 'newsletter', ] + def create(self, validated_data): + user = super(UserSerializer, self).create(validated_data) + validated_data['user'] = user + Subscriber.objects.make_subscriber(**validated_data) + return user + def validate_email(self, value): """Validate email value""" if value == self.instance.email: diff --git a/apps/notification/models.py b/apps/notification/models.py index 3a703f29..0d76c292 100644 --- a/apps/notification/models.py +++ b/apps/notification/models.py @@ -21,7 +21,7 @@ class SubscriberManager(models.Manager): """Extended manager for Subscriber model.""" def make_subscriber(self, email=None, user=None, ip_address=None, country_code=None, - locale=None, subscription_type=None, *args, **kwargs): + locale=None, subscription_types=None, *args, **kwargs): """Make subscriber and update info.""" # search existing object if not user: @@ -44,12 +44,12 @@ class SubscriberManager(models.Manager): obj.locale = locale obj.state = self.model.USABLE obj.update_code = generate_string_code() - obj.subscription_type = subscription_type + obj.subscription_types = subscription_types obj.save() else: obj = self.model.objects.create(user=user, email=email, ip_address=ip_address, country_code=country_code, locale=locale, - subscription_type=subscription_type) + subscription_types=subscription_types) return obj def associate_user(self, user): @@ -127,6 +127,13 @@ class Subscriber(ProjectBaseMixin): def unsubscribe(self): """Unsubscribe user.""" + + subscribes = Subscribe.objects.filter(subscriber=self) + self.subscription_types = [] + + subscribes.unsubscribe_date = now() + subscribes.save() + self.state = self.UNUSABLE self.save() diff --git a/apps/notification/serializers/common.py b/apps/notification/serializers/common.py index c7bfec42..d227deeb 100644 --- a/apps/notification/serializers/common.py +++ b/apps/notification/serializers/common.py @@ -1,6 +1,7 @@ """Notification app serializers.""" from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers + from notification import models from utils.methods import get_user_ip from utils.serializers import TranslatedField @@ -21,11 +22,11 @@ class SubscriptionTypeSerializer(serializers.ModelSerializer): ) -class SubscribeSerializer(serializers.ModelSerializer): - """Subscribe serializer.""" +class CreateSubscribeSerializer(serializers.ModelSerializer): + """Create Subscribe serializer.""" email = serializers.EmailField(required=False, source='send_to') - subscription_type = SubscriptionTypeSerializer(read_only=True) + subscription_types = SubscriptionTypeSerializer(read_only=True) class Meta: """Meta class.""" @@ -33,10 +34,9 @@ class SubscribeSerializer(serializers.ModelSerializer): model = models.Subscriber fields = ( 'email', - 'subscription_type', + 'subscription_types', 'state', - 'subscribe_date', - 'unsubscribe_date' + 'link_to_unsubscribe', ) read_only_fields = ('state',) @@ -47,6 +47,10 @@ class SubscribeSerializer(serializers.ModelSerializer): # validate email email = attrs.get('send_to') + + if attrs.get('email'): + email = attrs.get('email') + if user.is_authenticated: if email is not None and email != user.email: raise serializers.ValidationError(_('Does not match user email')) @@ -59,18 +63,39 @@ class SubscribeSerializer(serializers.ModelSerializer): attrs['country_code'] = request.country_code attrs['locale'] = request.locale attrs['ip_address'] = get_user_ip(request) + if user.is_authenticated: attrs['user'] = user - subscription_type_id = self.context.get('request').parser_context.get('kwargs').get("subscription_type_pk") - subscription_type_qs = models.SubscriptionType.objects.filter(id=subscription_type_id) - if not subscription_type_qs.exists(): - raise serializers.ValidationError({'detail': _(f'SubscriptionType not found.')}) - attrs["subscription_type"] = subscription_type_qs.first() + subscription_type_ids = self.context.get('request')\ + .parser_context.get('kwargs')\ + .get("subscription_types_pk") + + attrs['subscription_types'] = subscription_type_ids return attrs def create(self, validated_data): """Create obj.""" - subscriber = models.Subscriber.objects.make_subscriber(**validated_data) - return subscriber + return models.Subscriber.objects.make_subscriber(**validated_data) + + +class SubscribeSerializer(serializers.ModelSerializer): + """Subscribe serializer.""" + + email = serializers.EmailField(required=False, source='send_to') + subscription_types = SubscriptionTypeSerializer(read_only=True) + + class Meta: + """Meta class.""" + + model = models.Subscriber + fields = ( + 'email', + 'subscription_types', + 'state', + 'subscribe_date', + 'unsubscribe_date' + 'link_to_unsubscribe', + ) + read_only_fields = ('state',) diff --git a/apps/notification/urls/common.py b/apps/notification/urls/common.py index 0f7571f5..05e6f72d 100644 --- a/apps/notification/urls/common.py +++ b/apps/notification/urls/common.py @@ -5,6 +5,7 @@ from notification.views import common app_name = "notification" urlpatterns = [ + path('subscribe/', common.CreateSubscribeView.as_view(), name='create-subscribe'), path('subscribe/', common.SubscribeView.as_view(), name='subscribe'), path('subscribe-info/', common.SubscribeInfoAuthUserView.as_view(), name='check-code-auth'), path('subscribe-info//', common.SubscribeInfoView.as_view(), name='check-code'), diff --git a/apps/notification/views/common.py b/apps/notification/views/common.py index cba9c343..460c9444 100644 --- a/apps/notification/views/common.py +++ b/apps/notification/views/common.py @@ -2,6 +2,7 @@ from django.shortcuts import get_object_or_404 from rest_framework import generics, permissions from rest_framework.response import Response + from notification import models from notification.serializers import common as serializers @@ -10,7 +11,7 @@ class SubscribeView(generics.GenericAPIView): """Subscribe View.""" queryset = models.Subscriber.objects.all() - permission_classes = (permissions.AllowAny, ) + permission_classes = (permissions.AllowAny,) serializer_class = serializers.SubscribeSerializer def post(self, request, *args, **kwargs): @@ -20,12 +21,26 @@ class SubscribeView(generics.GenericAPIView): return Response(data=serializer.data) +class CreateSubscribeView(generics.GenericAPIView): + """Create subscribe View.""" + + queryset = models.Subscriber.objects.all() + permission_classes = (permissions.AllowAny,) + serializer_class = serializers.CreateSubscribeSerializer + + def post(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(data=serializer.data) + + class SubscribeInfoView(generics.RetrieveAPIView): """Subscribe info view.""" lookup_field = 'update_code' lookup_url_kwarg = 'code' - permission_classes = (permissions.AllowAny, ) + permission_classes = (permissions.AllowAny,) queryset = models.Subscriber.objects.all() serializer_class = serializers.SubscribeSerializer @@ -33,7 +48,7 @@ class SubscribeInfoView(generics.RetrieveAPIView): class SubscribeInfoAuthUserView(generics.ListAPIView): """Subscribe info auth user view.""" - permission_classes = (permissions.IsAuthenticated, ) + permission_classes = (permissions.IsAuthenticated,) serializer_class = serializers.SubscribeSerializer def get_queryset(self): @@ -47,7 +62,7 @@ class UnsubscribeView(generics.GenericAPIView): lookup_field = 'update_code' lookup_url_kwarg = 'code' - permission_classes = (permissions.AllowAny, ) + permission_classes = (permissions.AllowAny,) queryset = models.Subscriber.objects.all() serializer_class = serializers.SubscribeSerializer @@ -61,7 +76,7 @@ class UnsubscribeView(generics.GenericAPIView): class UnsubscribeAuthUserView(generics.GenericAPIView): """Unsubscribe auth user view.""" - permission_classes = (permissions.IsAuthenticated, ) + permission_classes = (permissions.IsAuthenticated,) queryset = models.Subscriber.objects.all() serializer_class = serializers.SubscribeSerializer @@ -78,4 +93,3 @@ class SubscriptionTypesView(generics.ListAPIView): permission_classes = (permissions.AllowAny,) queryset = models.SubscriptionType.objects.all() serializer_class = serializers.SubscriptionTypeSerializer - From 449e0675faac9d4e58f2bf5bb118237e0f7e1e16 Mon Sep 17 00:00:00 2001 From: dormantman Date: Fri, 27 Dec 2019 17:13:48 +0300 Subject: [PATCH 04/12] Added migrations --- .../migrations/0005_auto_20191227_1212.py | 38 +++++++++++++++++++ .../migrations/0006_auto_20191227_1216.py | 24 ++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 apps/notification/migrations/0005_auto_20191227_1212.py create mode 100644 apps/notification/migrations/0006_auto_20191227_1216.py diff --git a/apps/notification/migrations/0005_auto_20191227_1212.py b/apps/notification/migrations/0005_auto_20191227_1212.py new file mode 100644 index 00000000..7304dcdc --- /dev/null +++ b/apps/notification/migrations/0005_auto_20191227_1212.py @@ -0,0 +1,38 @@ +# Generated by Django 2.2.7 on 2019-12-27 12:12 + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('notification', '0004_auto_20191118_1307'), + ] + + operations = [ + migrations.RemoveField( + model_name='subscriber', + name='subscription_type', + ), + migrations.CreateModel( + name='Subscribe', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')), + ('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('subscriber', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='notification.Subscriber')), + ('subscription_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='notification.SubscriptionType')), + ], + options={ + 'verbose_name': 'Subscribe', + 'verbose_name_plural': 'Subscribes', + }, + ), + migrations.AddField( + model_name='subscriber', + name='subscription_types', + field=models.ManyToManyField(through='notification.Subscribe', to='notification.SubscriptionType'), + ), + ] diff --git a/apps/notification/migrations/0006_auto_20191227_1216.py b/apps/notification/migrations/0006_auto_20191227_1216.py new file mode 100644 index 00000000..61e918ef --- /dev/null +++ b/apps/notification/migrations/0006_auto_20191227_1216.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2.7 on 2019-12-27 12:16 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('notification', '0005_auto_20191227_1212'), + ] + + operations = [ + migrations.AddField( + model_name='subscribe', + name='subscribe_date', + field=models.DateTimeField(blank=True, default=django.utils.timezone.now, null=True, verbose_name='Last subscribe date'), + ), + migrations.AddField( + model_name='subscribe', + name='unsubscribe_date', + field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='Last unsubscribe date'), + ), + ] From fe330e42709b51f48a77082f43455ccd1a30db77 Mon Sep 17 00:00:00 2001 From: dormantman Date: Fri, 27 Dec 2019 17:31:03 +0300 Subject: [PATCH 05/12] Reformat structure --- apps/notification/models.py | 25 ++++++++++++++----------- apps/notification/serializers/common.py | 23 +++++++++++++++-------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/apps/notification/models.py b/apps/notification/models.py index 0d76c292..ededdb00 100644 --- a/apps/notification/models.py +++ b/apps/notification/models.py @@ -77,14 +77,6 @@ class SubscriberQuerySet(models.QuerySet): class Subscriber(ProjectBaseMixin): """Subscriber model.""" - UNUSABLE = 0 - USABLE = 1 - - STATE_CHOICES = ( - (UNUSABLE, _('Unusable')), - (USABLE, _('Usable')), - ) - user = models.ForeignKey( User, blank=True, @@ -98,7 +90,6 @@ class Subscriber(ProjectBaseMixin): ip_address = models.GenericIPAddressField(blank=True, null=True, default=None, verbose_name=_('IP address')) country_code = models.CharField(max_length=10, blank=True, null=True, default=None, verbose_name=_('Country code')) locale = models.CharField(blank=True, null=True, default=None, max_length=10, verbose_name=_('Locale identifier')) - state = models.PositiveIntegerField(choices=STATE_CHOICES, default=USABLE, verbose_name=_('State')) update_code = models.CharField( max_length=254, blank=True, @@ -128,13 +119,12 @@ class Subscriber(ProjectBaseMixin): def unsubscribe(self): """Unsubscribe user.""" - subscribes = Subscribe.objects.filter(subscriber=self) + subscribes = self.subscribe_objects self.subscription_types = [] subscribes.unsubscribe_date = now() subscribes.save() - self.state = self.UNUSABLE self.save() @property @@ -151,12 +141,25 @@ class Subscriber(ProjectBaseMixin): query = f'?code={self.update_code}' return f'{schema}://{site_domain}{url}{query}' + @property + def subscribe_objects(self): + return Subscribe.objects.filter(subscriber=self) + class Subscribe(ProjectBaseMixin): """Subscribe model.""" + UNUSABLE = 0 + USABLE = 1 + + STATE_CHOICES = ( + (UNUSABLE, _('Unusable')), + (USABLE, _('Usable')), + ) + subscribe_date = models.DateTimeField(_('Last subscribe date'), blank=True, null=True, default=now) unsubscribe_date = models.DateTimeField(_('Last unsubscribe date'), blank=True, null=True, default=None) + state = models.PositiveIntegerField(choices=STATE_CHOICES, default=USABLE, verbose_name=_('State')) subscriber = models.ForeignKey(Subscriber, on_delete=models.CASCADE) subscription_type = models.ForeignKey(SubscriptionType, on_delete=models.CASCADE) diff --git a/apps/notification/serializers/common.py b/apps/notification/serializers/common.py index d227deeb..21b85c2c 100644 --- a/apps/notification/serializers/common.py +++ b/apps/notification/serializers/common.py @@ -35,10 +35,8 @@ class CreateSubscribeSerializer(serializers.ModelSerializer): fields = ( 'email', 'subscription_types', - 'state', 'link_to_unsubscribe', ) - read_only_fields = ('state',) def validate(self, attrs): """Validate attrs.""" @@ -67,8 +65,8 @@ class CreateSubscribeSerializer(serializers.ModelSerializer): if user.is_authenticated: attrs['user'] = user - subscription_type_ids = self.context.get('request')\ - .parser_context.get('kwargs')\ + subscription_type_ids = self.context.get('request') \ + .parser_context.get('kwargs') \ .get("subscription_types_pk") attrs['subscription_types'] = subscription_type_ids @@ -80,11 +78,23 @@ class CreateSubscribeSerializer(serializers.ModelSerializer): return models.Subscriber.objects.make_subscriber(**validated_data) +class SubscribeObjectSerializer(serializers.ModelSerializer): + """Subscribe serializer.""" + + class Meta: + """Meta class.""" + + model = models.Subscriber + fields = () + read_only_fields = ('subscribe_date', 'unsubscribe_date', 'state',) + + class SubscribeSerializer(serializers.ModelSerializer): """Subscribe serializer.""" email = serializers.EmailField(required=False, source='send_to') subscription_types = SubscriptionTypeSerializer(read_only=True) + subscribe_objects = SubscribeObjectSerializer(read_only=True) class Meta: """Meta class.""" @@ -93,9 +103,6 @@ class SubscribeSerializer(serializers.ModelSerializer): fields = ( 'email', 'subscription_types', - 'state', - 'subscribe_date', - 'unsubscribe_date' 'link_to_unsubscribe', + 'subscribe_objects', ) - read_only_fields = ('state',) From a76efac4a8a995b35e628dd5b924b4f80a9d8657 Mon Sep 17 00:00:00 2001 From: dormantman Date: Fri, 27 Dec 2019 18:02:23 +0300 Subject: [PATCH 06/12] Added corrections migrate --- .../migrations/0007_auto_20191227_1426.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 apps/notification/migrations/0007_auto_20191227_1426.py diff --git a/apps/notification/migrations/0007_auto_20191227_1426.py b/apps/notification/migrations/0007_auto_20191227_1426.py new file mode 100644 index 00000000..503a2ac0 --- /dev/null +++ b/apps/notification/migrations/0007_auto_20191227_1426.py @@ -0,0 +1,22 @@ +# Generated by Django 2.2.7 on 2019-12-27 14:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notification', '0006_auto_20191227_1216'), + ] + + operations = [ + migrations.RemoveField( + model_name='subscriber', + name='state', + ), + migrations.AddField( + model_name='subscribe', + name='state', + field=models.PositiveIntegerField(choices=[(0, 'Unusable'), (1, 'Usable')], default=1, verbose_name='State'), + ), + ] From d9f1da1e49242807dd018c1ca7636a9337a6d7f4 Mon Sep 17 00:00:00 2001 From: dormantman Date: Fri, 27 Dec 2019 18:14:13 +0300 Subject: [PATCH 07/12] Fix unsubscribe --- apps/notification/models.py | 9 ++++----- apps/notification/views/common.py | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/notification/models.py b/apps/notification/models.py index ededdb00..2c3463a9 100644 --- a/apps/notification/models.py +++ b/apps/notification/models.py @@ -116,14 +116,13 @@ class Subscriber(ProjectBaseMixin): self.update_code = generate_string_code() return super(Subscriber, self).save(*args, **kwargs) - def unsubscribe(self): + def unsubscribe(self, query: dict): """Unsubscribe user.""" - subscribes = self.subscribe_objects - self.subscription_types = [] + subscription_types = query.get('subscription_types') - subscribes.unsubscribe_date = now() - subscribes.save() + subscribes = self.subscription_types.objects.filter(pk__in=[subscription_types]) + self.subscription_types = subscribes self.save() diff --git a/apps/notification/views/common.py b/apps/notification/views/common.py index 460c9444..a801bbdd 100644 --- a/apps/notification/views/common.py +++ b/apps/notification/views/common.py @@ -68,7 +68,7 @@ class UnsubscribeView(generics.GenericAPIView): def patch(self, request, *args, **kw): obj = self.get_object() - obj.unsubscribe() + obj.unsubscribe(request.query_params) serializer = self.get_serializer(instance=obj) return Response(data=serializer.data) @@ -83,7 +83,7 @@ class UnsubscribeAuthUserView(generics.GenericAPIView): def patch(self, request, *args, **kw): user = request.user obj = get_object_or_404(models.Subscriber, user=user) - obj.unsubscribe() + obj.unsubscribe(request.query_params) serializer = self.get_serializer(instance=obj) return Response(data=serializer.data) From 13022ed476fd31e5aff93cf93953bace7c819f43 Mon Sep 17 00:00:00 2001 From: dormantman Date: Mon, 30 Dec 2019 18:06:50 +0300 Subject: [PATCH 08/12] Fix subsribtions --- apps/news/tasks.py | 60 ++++++---- .../migrations/0008_remove_subscribe_state.py | 17 +++ apps/notification/models.py | 31 ++--- apps/notification/serializers/common.py | 2 +- apps/notification/tests.py | 113 ++++++++++++++---- 5 files changed, 151 insertions(+), 72 deletions(-) create mode 100644 apps/notification/migrations/0008_remove_subscribe_state.py diff --git a/apps/news/tasks.py b/apps/news/tasks.py index 7ff4d504..80a2a626 100644 --- a/apps/news/tasks.py +++ b/apps/news/tasks.py @@ -1,43 +1,51 @@ from datetime import datetime +from smtplib import SMTPException from celery import shared_task -from django.core.mail import send_mail -from notification.models import Subscriber -from news import models -from django.template.loader import render_to_string, get_template from django.conf import settings -from smtplib import SMTPException +from django.core.mail import send_mail from django.core.validators import EMPTY_VALUES +from django.template.loader import get_template, render_to_string + from main.models import SiteSettings +from news import models +from notification.models import Subscriber @shared_task def send_email_with_news(news_ids): - subscribers = Subscriber.objects.filter(state=Subscriber.USABLE) + subscribers = Subscriber.objects.all() sent_news = models.News.objects.filter(id__in=news_ids) + htmly = get_template(settings.NEWS_EMAIL_TEMPLATE) year = datetime.now().year + socials = list(SiteSettings.objects.with_country()) - socials = dict(zip(map(lambda s: s.country.code, socials), socials)) - for s in subscribers: - socials_for_subscriber = socials.get(s.country_code) + socials = dict(zip(map(lambda social: social.country.code, socials), socials)) + + for subscriber in subscribers: + socials_for_subscriber = socials.get(subscriber.country_code) try: - for n in sent_news: - context = {"title": n.title.get(s.locale), - "subtitle": n.subtitle.get(s.locale), - "description": n.description.get(s.locale), - "code": s.update_code, - "image_url": n.image_url if n.image_url not in EMPTY_VALUES else None, - "domain_uri": settings.DOMAIN_URI, - "slug": n.slug, - "country_code": s.country_code, - "twitter_page_url": socials_for_subscriber.twitter_page_url if socials_for_subscriber else '#', - "instagram_page_url": socials_for_subscriber.instagram_page_url if socials_for_subscriber else '#', - "facebook_page_url": socials_for_subscriber.facebook_page_url if socials_for_subscriber else '#', - "send_to": s.send_to, - "year": year} - send_mail("G&M News", render_to_string(settings.NEWS_EMAIL_TEMPLATE, context), - settings.EMAIL_HOST_USER, [s.send_to], fail_silently=False, - html_message=htmly.render(context)) + for new in sent_news: + context = { + "title": new.title.get(subscriber.locale), + "subtitle": new.subtitle.get(subscriber.locale), + "description": new.description.get(subscriber.locale), + "code": subscriber.update_code, + "image_url": new.image_url if new.image_url not in EMPTY_VALUES else None, + "domain_uri": settings.DOMAIN_URI, + "slug": new.slug, + "country_code": subscriber.country_code, + "twitter_page_url": socials_for_subscriber.twitter_page_url if socials_for_subscriber else '#', + "instagram_page_url": socials_for_subscriber.instagram_page_url if socials_for_subscriber else '#', + "facebook_page_url": socials_for_subscriber.facebook_page_url if socials_for_subscriber else '#', + "send_to": subscriber.send_to, + "year": year + } + send_mail( + "G&M News", render_to_string(settings.NEWS_EMAIL_TEMPLATE, context), + settings.EMAIL_HOST_USER, [subscriber.send_to], fail_silently=False, + html_message=htmly.render(context) + ) except SMTPException: continue diff --git a/apps/notification/migrations/0008_remove_subscribe_state.py b/apps/notification/migrations/0008_remove_subscribe_state.py new file mode 100644 index 00000000..5870e8cf --- /dev/null +++ b/apps/notification/migrations/0008_remove_subscribe_state.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.7 on 2019-12-30 15:04 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('notification', '0007_auto_20191227_1426'), + ] + + operations = [ + migrations.RemoveField( + model_name='subscribe', + name='state', + ), + ] diff --git a/apps/notification/models.py b/apps/notification/models.py index 2c3463a9..4bdabcae 100644 --- a/apps/notification/models.py +++ b/apps/notification/models.py @@ -42,7 +42,6 @@ class SubscriberManager(models.Manager): obj.ip_address = ip_address obj.country_code = country_code obj.locale = locale - obj.state = self.model.USABLE obj.update_code = generate_string_code() obj.subscription_types = subscription_types obj.save() @@ -64,16 +63,6 @@ class SubscriberManager(models.Manager): return obj -class SubscriberQuerySet(models.QuerySet): - """Extended queryset for Subscriber model.""" - - def by_usable(self, switcher=True): - if switcher: - return self.filter(state=self.model.USABLE) - else: - return self.filter(state=self.model.UNUSABLE) - - class Subscriber(ProjectBaseMixin): """Subscriber model.""" @@ -102,7 +91,7 @@ class Subscriber(ProjectBaseMixin): subscription_types = models.ManyToManyField(SubscriptionType, through='Subscribe') - objects = SubscriberManager.from_queryset(SubscriberQuerySet)() + objects = SubscriberManager() class Meta: """Meta class.""" @@ -121,9 +110,18 @@ class Subscriber(ProjectBaseMixin): subscription_types = query.get('subscription_types') + old_subscribes = self.subscription_types.objects.all() subscribes = self.subscription_types.objects.filter(pk__in=[subscription_types]) self.subscription_types = subscribes + new_ids = set(existing_answer.answer.id for existing_answer in subscribes) + old_subscribes_types = [sub for sub in old_subscribes if sub.id not in new_ids] + old_subscribes = Subscribe.objects.filter(subscriber=self, subscription_types__in=[old_subscribes_types]) + + for sub in old_subscribes: + sub.unsubscribe_date = now() + sub.save() + self.save() @property @@ -148,17 +146,8 @@ class Subscriber(ProjectBaseMixin): class Subscribe(ProjectBaseMixin): """Subscribe model.""" - UNUSABLE = 0 - USABLE = 1 - - STATE_CHOICES = ( - (UNUSABLE, _('Unusable')), - (USABLE, _('Usable')), - ) - subscribe_date = models.DateTimeField(_('Last subscribe date'), blank=True, null=True, default=now) unsubscribe_date = models.DateTimeField(_('Last unsubscribe date'), blank=True, null=True, default=None) - state = models.PositiveIntegerField(choices=STATE_CHOICES, default=USABLE, verbose_name=_('State')) subscriber = models.ForeignKey(Subscriber, on_delete=models.CASCADE) subscription_type = models.ForeignKey(SubscriptionType, on_delete=models.CASCADE) diff --git a/apps/notification/serializers/common.py b/apps/notification/serializers/common.py index 21b85c2c..3499fd2a 100644 --- a/apps/notification/serializers/common.py +++ b/apps/notification/serializers/common.py @@ -86,7 +86,7 @@ class SubscribeObjectSerializer(serializers.ModelSerializer): model = models.Subscriber fields = () - read_only_fields = ('subscribe_date', 'unsubscribe_date', 'state',) + read_only_fields = ('subscribe_date', 'unsubscribe_date',) class SubscribeSerializer(serializers.ModelSerializer): diff --git a/apps/notification/tests.py b/apps/notification/tests.py index 87264435..e65e049d 100644 --- a/apps/notification/tests.py +++ b/apps/notification/tests.py @@ -1,11 +1,15 @@ +from datetime import datetime, timedelta from http.cookies import SimpleCookie -from django.test import TestCase -from rest_framework.test import APITestCase from rest_framework import status +from rest_framework.test import APITestCase -from account.models import User -from notification.models import Subscriber +from account.models import User, Role, UserRole +from location.models import Country +from main.models import SiteSettings +from news.models import News, NewsType +from notification.models import Subscriber, SubscriptionType +from translation.models import Language class BaseTestCase(APITestCase): @@ -17,23 +21,22 @@ class BaseTestCase(APITestCase): self.user = User.objects.create_user(username=self.username, email=self.email, password=self.password) # get tokens tokens = User.create_jwt_tokens(self.user) - self.client.cookies = SimpleCookie({'access_token': tokens.get('access_token'), - 'refresh_token': tokens.get('refresh_token')}) + self.client.cookies = SimpleCookie({ + 'access_token': tokens.get('access_token'), + 'refresh_token': tokens.get('refresh_token') + }) class NotificationAnonSubscribeTestCase(APITestCase): def test_subscribe(self): - test_data = { - "email": "test@email.com", - "state": 1 + "email": "test@email.com" } response = self.client.post("/api/web/notifications/subscribe/", data=test_data, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.json()["email"], test_data["email"]) - self.assertEqual(response.json()["state"], test_data["state"]) class NotificationSubscribeTestCase(BaseTestCase): @@ -42,21 +45,17 @@ class NotificationSubscribeTestCase(BaseTestCase): super().setUp() self.test_data = { - "email": self.email, - "state": 1 + "email": self.email } def test_subscribe(self): - response = self.client.post("/api/web/notifications/subscribe/", data=self.test_data, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.json()["email"], self.email) - self.assertEqual(response.json()["state"], self.test_data["state"]) def test_subscribe_info_auth_user(self): - - Subscriber.objects.create(user=self.user, email=self.email, state=1) + Subscriber.objects.create(user=self.user, email=self.email) response = self.client.get("/api/web/notifications/subscribe-info/", data=self.test_data, format="json") @@ -66,13 +65,12 @@ class NotificationSubscribeTestCase(BaseTestCase): class NotificationSubscribeInfoTestCase(APITestCase): def test_subscribe_info(self): - self.username = 'sedragurda' self.password = 'sedragurdaredips19' self.email = 'sedragurda@desoz.com' self.user = User.objects.create_user(username=self.username, email=self.email, password=self.password) - test_subscriber = Subscriber.objects.create(user=self.user, email=self.email, state=1) + test_subscriber = Subscriber.objects.create(user=self.user, email=self.email) response = self.client.get(f"/api/web/notifications/subscribe-info/{test_subscriber.update_code}/") @@ -82,12 +80,10 @@ class NotificationSubscribeInfoTestCase(APITestCase): class NotificationUnsubscribeAuthUserTestCase(BaseTestCase): def test_unsubscribe_auth_user(self): - Subscriber.objects.create(user=self.user, email=self.email, state=1) self.test_data = { - "email": self.email, - "state": 1 + "email": self.email } response = self.client.patch("/api/web/notifications/unsubscribe/", data=self.test_data, format="json") @@ -104,13 +100,82 @@ class NotificationUnsubscribeTestCase(APITestCase): self.user = User.objects.create_user(username=self.username, email=self.email, password=self.password) self.test_data = { - "email": self.email, - "state": 1 + "email": self.email } - test_subscriber = Subscriber.objects.create(user=self.user, email=self.email, state=1) + test_subscriber = Subscriber.objects.create(user=self.user, email=self.email) response = self.client.patch(f"/api/web/notifications/unsubscribe/{test_subscriber.update_code}/", data=self.test_data, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) + + +class NotificationManySubscribeTestCase(APITestCase): + + def test_unsubscribe(self): + self.username = 'sedragurda' + self.password = 'sedragurdaredips19' + self.email = 'sedragurda@desoz.com' + self.user = User.objects.create_user( + username=self.username, email=self.email, password=self.password) + + # get tokens + tokens = User.create_jwt_tokens(self.user) + self.client.cookies = SimpleCookie( + {'access_token': tokens.get('access_token'), + 'refresh_token': tokens.get('refresh_token')}) + self.test_news_type = NewsType.objects.create(name="Test news type") + + self.lang, created = Language.objects.get_or_create( + title='Russia', + locale='ru-RU' + ) + + self.country_ru, created = Country.objects.get_or_create( + name={"en-GB": "Russian"} + ) + + self.site_ru, created = SiteSettings.objects.get_or_create( + subdomain='ru' + ) + + role = Role.objects.create( + role=Role.CONTENT_PAGE_MANAGER, + site_id=self.site_ru.id + ) + role.save() + + user_role = UserRole.objects.create( + user=self.user, + role=role + ) + user_role.save() + + self.test_news = News.objects.create( + created_by=self.user, modified_by=self.user, + title={"ru-RU": "Test news"}, + news_type=self.test_news_type, + description={"ru-RU": "Description test news"}, + end=datetime.now() + timedelta(hours=2), + state=News.PUBLISHED, + slugs={'en-GB': 'test-news-slug'}, + country=self.country_ru, + site=self.site_ru + ) + + self.test_subscribe_type = SubscriptionType.objects.create( + index_name='test_index_name', + name={"ru-RU": "Test subscription type"} + ) + + test_data = { + 'email': self.email, + 'subscription_types_pk': [ + self.test_subscribe_type + ] + } + + response = self.client.post("/api/web/notifications/subscribe/", data=test_data, format="json") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.json()["email"], test_data["email"]) \ No newline at end of file From 25e118cf0bbe576419567c792692691c5f6158da Mon Sep 17 00:00:00 2001 From: dormantman Date: Mon, 30 Dec 2019 18:13:06 +0300 Subject: [PATCH 09/12] Optimize --- apps/notification/models.py | 8 +++----- apps/notification/tests.py | 30 ++++++++++++++++++++++-------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/apps/notification/models.py b/apps/notification/models.py index 4bdabcae..fd386c2d 100644 --- a/apps/notification/models.py +++ b/apps/notification/models.py @@ -1,4 +1,5 @@ """Notification app models.""" + from django.conf import settings from django.db import models from django.utils.timezone import now @@ -116,11 +117,8 @@ class Subscriber(ProjectBaseMixin): new_ids = set(existing_answer.answer.id for existing_answer in subscribes) old_subscribes_types = [sub for sub in old_subscribes if sub.id not in new_ids] - old_subscribes = Subscribe.objects.filter(subscriber=self, subscription_types__in=[old_subscribes_types]) - - for sub in old_subscribes: - sub.unsubscribe_date = now() - sub.save() + old_subscribes = Subscribe.objects.filter(subscriber=self, subscription_types__in=old_subscribes_types) + old_subscribes.update(unsubscribe_date=now()) self.save() diff --git a/apps/notification/tests.py b/apps/notification/tests.py index e65e049d..410365ff 100644 --- a/apps/notification/tests.py +++ b/apps/notification/tests.py @@ -4,7 +4,7 @@ from http.cookies import SimpleCookie from rest_framework import status from rest_framework.test import APITestCase -from account.models import User, Role, UserRole +from account.models import Role, User, UserRole from location.models import Country from main.models import SiteSettings from news.models import News, NewsType @@ -22,9 +22,9 @@ class BaseTestCase(APITestCase): # get tokens tokens = User.create_jwt_tokens(self.user) self.client.cookies = SimpleCookie({ - 'access_token': tokens.get('access_token'), - 'refresh_token': tokens.get('refresh_token') - }) + 'access_token': tokens.get('access_token'), + 'refresh_token': tokens.get('refresh_token') + }) class NotificationAnonSubscribeTestCase(APITestCase): @@ -113,7 +113,7 @@ class NotificationUnsubscribeTestCase(APITestCase): class NotificationManySubscribeTestCase(APITestCase): - def test_unsubscribe(self): + def test_subscribe(self): self.username = 'sedragurda' self.password = 'sedragurdaredips19' self.email = 'sedragurda@desoz.com' @@ -123,8 +123,10 @@ class NotificationManySubscribeTestCase(APITestCase): # get tokens tokens = User.create_jwt_tokens(self.user) self.client.cookies = SimpleCookie( - {'access_token': tokens.get('access_token'), - 'refresh_token': tokens.get('refresh_token')}) + { + 'access_token': tokens.get('access_token'), + 'refresh_token': tokens.get('refresh_token') + }) self.test_news_type = NewsType.objects.create(name="Test news type") self.lang, created = Language.objects.get_or_create( @@ -178,4 +180,16 @@ class NotificationManySubscribeTestCase(APITestCase): response = self.client.post("/api/web/notifications/subscribe/", data=test_data, format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.json()["email"], test_data["email"]) \ No newline at end of file + self.assertEqual(response.json()["email"], test_data["email"]) + + def test_unsubscribe(self): + test_data = { + "email": self.email + } + + test_subscriber = Subscriber.objects.create(user=self.user, email=self.email) + + response = self.client.patch(f"/api/web/notifications/unsubscribe/{test_subscriber.update_code}/", + data=test_data, format="json") + + self.assertEqual(response.status_code, status.HTTP_200_OK) From 974e0ce8fe011758bd7e15c2c9052d74c0ad65ca Mon Sep 17 00:00:00 2001 From: dormantman Date: Mon, 30 Dec 2019 18:14:36 +0300 Subject: [PATCH 10/12] Added saving date --- apps/notification/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/notification/models.py b/apps/notification/models.py index fd386c2d..1f055f2e 100644 --- a/apps/notification/models.py +++ b/apps/notification/models.py @@ -119,6 +119,7 @@ class Subscriber(ProjectBaseMixin): old_subscribes_types = [sub for sub in old_subscribes if sub.id not in new_ids] old_subscribes = Subscribe.objects.filter(subscriber=self, subscription_types__in=old_subscribes_types) old_subscribes.update(unsubscribe_date=now()) + old_subscribes.save() self.save() From fc215129ed5e85c75153a088cfef747c150d61cc Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 30 Dec 2019 22:23:06 +0300 Subject: [PATCH 11/12] subscriptions work out --- apps/notification/models.py | 6 +++--- apps/notification/serializers/common.py | 9 ++------- apps/notification/urls/common.py | 1 - apps/notification/views/common.py | 22 +--------------------- 4 files changed, 6 insertions(+), 32 deletions(-) diff --git a/apps/notification/models.py b/apps/notification/models.py index 1f055f2e..c9e15efc 100644 --- a/apps/notification/models.py +++ b/apps/notification/models.py @@ -44,12 +44,12 @@ class SubscriberManager(models.Manager): obj.country_code = country_code obj.locale = locale obj.update_code = generate_string_code() - obj.subscription_types = subscription_types obj.save() + obj.subscription_types.set(subscription_types) else: obj = self.model.objects.create(user=user, email=email, ip_address=ip_address, - country_code=country_code, locale=locale, - subscription_types=subscription_types) + country_code=country_code, locale=locale) + obj.subscription_types.set(subscription_types) return obj def associate_user(self, user): diff --git a/apps/notification/serializers/common.py b/apps/notification/serializers/common.py index 3499fd2a..cc130f5c 100644 --- a/apps/notification/serializers/common.py +++ b/apps/notification/serializers/common.py @@ -11,6 +11,7 @@ class SubscriptionTypeSerializer(serializers.ModelSerializer): """Subscription type serializer.""" name_translated = TranslatedField() + class Meta: """Meta class.""" @@ -26,7 +27,7 @@ class CreateSubscribeSerializer(serializers.ModelSerializer): """Create Subscribe serializer.""" email = serializers.EmailField(required=False, source='send_to') - subscription_types = SubscriptionTypeSerializer(read_only=True) + subscription_types = serializers.PrimaryKeyRelatedField(many=True, queryset=models.SubscriptionType.objects.all()) class Meta: """Meta class.""" @@ -65,12 +66,6 @@ class CreateSubscribeSerializer(serializers.ModelSerializer): if user.is_authenticated: attrs['user'] = user - subscription_type_ids = self.context.get('request') \ - .parser_context.get('kwargs') \ - .get("subscription_types_pk") - - attrs['subscription_types'] = subscription_type_ids - return attrs def create(self, validated_data): diff --git a/apps/notification/urls/common.py b/apps/notification/urls/common.py index 05e6f72d..2ec6f152 100644 --- a/apps/notification/urls/common.py +++ b/apps/notification/urls/common.py @@ -6,7 +6,6 @@ app_name = "notification" urlpatterns = [ path('subscribe/', common.CreateSubscribeView.as_view(), name='create-subscribe'), - path('subscribe/', common.SubscribeView.as_view(), name='subscribe'), path('subscribe-info/', common.SubscribeInfoAuthUserView.as_view(), name='check-code-auth'), path('subscribe-info//', common.SubscribeInfoView.as_view(), name='check-code'), path('unsubscribe/', common.UnsubscribeAuthUserView.as_view(), name='unsubscribe-auth'), diff --git a/apps/notification/views/common.py b/apps/notification/views/common.py index a801bbdd..a6e41b29 100644 --- a/apps/notification/views/common.py +++ b/apps/notification/views/common.py @@ -7,33 +7,13 @@ from notification import models from notification.serializers import common as serializers -class SubscribeView(generics.GenericAPIView): - """Subscribe View.""" - - queryset = models.Subscriber.objects.all() - permission_classes = (permissions.AllowAny,) - serializer_class = serializers.SubscribeSerializer - - def post(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - serializer.save() - return Response(data=serializer.data) - - -class CreateSubscribeView(generics.GenericAPIView): +class CreateSubscribeView(generics.CreateAPIView): """Create subscribe View.""" queryset = models.Subscriber.objects.all() permission_classes = (permissions.AllowAny,) serializer_class = serializers.CreateSubscribeSerializer - def post(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - serializer.save() - return Response(data=serializer.data) - class SubscribeInfoView(generics.RetrieveAPIView): """Subscribe info view.""" From af8febbe45d8e62b88971a348b1c8b83a9fb2ff8 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 30 Dec 2019 23:41:58 +0300 Subject: [PATCH 12/12] subscription work out #2 --- apps/notification/models.py | 17 +++-------------- apps/notification/serializers/common.py | 2 +- apps/notification/views/common.py | 2 +- 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/apps/notification/models.py b/apps/notification/models.py index c9e15efc..d1ebf055 100644 --- a/apps/notification/models.py +++ b/apps/notification/models.py @@ -46,10 +46,12 @@ class SubscriberManager(models.Manager): obj.update_code = generate_string_code() obj.save() obj.subscription_types.set(subscription_types) + obj.subscribe_set.update(unsubscribe_date=None) else: obj = self.model.objects.create(user=user, email=email, ip_address=ip_address, country_code=country_code, locale=locale) obj.subscription_types.set(subscription_types) + obj.subscribe_set.update(unsubscribe_date=None) return obj def associate_user(self, user): @@ -108,20 +110,7 @@ class Subscriber(ProjectBaseMixin): def unsubscribe(self, query: dict): """Unsubscribe user.""" - - subscription_types = query.get('subscription_types') - - old_subscribes = self.subscription_types.objects.all() - subscribes = self.subscription_types.objects.filter(pk__in=[subscription_types]) - self.subscription_types = subscribes - - new_ids = set(existing_answer.answer.id for existing_answer in subscribes) - old_subscribes_types = [sub for sub in old_subscribes if sub.id not in new_ids] - old_subscribes = Subscribe.objects.filter(subscriber=self, subscription_types__in=old_subscribes_types) - old_subscribes.update(unsubscribe_date=now()) - old_subscribes.save() - - self.save() + self.subscribe_set.update(unsubscribe_date=now()) @property def send_to(self): diff --git a/apps/notification/serializers/common.py b/apps/notification/serializers/common.py index cc130f5c..036d8929 100644 --- a/apps/notification/serializers/common.py +++ b/apps/notification/serializers/common.py @@ -88,7 +88,7 @@ class SubscribeSerializer(serializers.ModelSerializer): """Subscribe serializer.""" email = serializers.EmailField(required=False, source='send_to') - subscription_types = SubscriptionTypeSerializer(read_only=True) + subscription_types = SubscriptionTypeSerializer(read_only=True, source='subscription_types_set') subscribe_objects = SubscribeObjectSerializer(read_only=True) class Meta: diff --git a/apps/notification/views/common.py b/apps/notification/views/common.py index a6e41b29..f7fbf9fe 100644 --- a/apps/notification/views/common.py +++ b/apps/notification/views/common.py @@ -37,7 +37,7 @@ class SubscribeInfoAuthUserView(generics.ListAPIView): return queryset.filter(user=user) -class UnsubscribeView(generics.GenericAPIView): +class UnsubscribeView(generics.UpdateAPIView): """Unsubscribe view.""" lookup_field = 'update_code'