From fb91383be520af5bd980f8f678625c876e796d64 Mon Sep 17 00:00:00 2001 From: dormantman Date: Wed, 25 Dec 2019 11:16:05 +0300 Subject: [PATCH 01/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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 a8f053a6465b2f74caf59f887ca1515cffbd68e1 Mon Sep 17 00:00:00 2001 From: dormantman Date: Wed, 25 Dec 2019 11:16:05 +0300 Subject: [PATCH 12/30] 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 08f675cdbc51e3481e3a030327e1b769cfe4b8e8 Mon Sep 17 00:00:00 2001 From: dormantman Date: Fri, 27 Dec 2019 15:30:41 +0300 Subject: [PATCH 13/30] 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 ec00b3a24f164a4283394bbfd8403d614f944e49 Mon Sep 17 00:00:00 2001 From: dormantman Date: Fri, 27 Dec 2019 17:10:41 +0300 Subject: [PATCH 14/30] 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 8108471c8e764d640f7857b1ff5b76d584a850a0 Mon Sep 17 00:00:00 2001 From: dormantman Date: Fri, 27 Dec 2019 17:13:48 +0300 Subject: [PATCH 15/30] 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 54d1b7742f9aedc172751c74c2fa8fd6830a18fd Mon Sep 17 00:00:00 2001 From: dormantman Date: Fri, 27 Dec 2019 17:31:03 +0300 Subject: [PATCH 16/30] 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 8567a818c75f5f3df1205f6b062fa40345a16744 Mon Sep 17 00:00:00 2001 From: dormantman Date: Fri, 27 Dec 2019 18:02:23 +0300 Subject: [PATCH 17/30] 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 e33418dfe63a9ddad29fff9095b7e0bc200fcaf2 Mon Sep 17 00:00:00 2001 From: dormantman Date: Fri, 27 Dec 2019 18:14:13 +0300 Subject: [PATCH 18/30] 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 e772663f44502a09bb1bbfa3b744b1595d993d58 Mon Sep 17 00:00:00 2001 From: dormantman Date: Mon, 30 Dec 2019 18:06:50 +0300 Subject: [PATCH 19/30] 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 797d3a3973b8c863014bbe631868da1be179d282 Mon Sep 17 00:00:00 2001 From: dormantman Date: Mon, 30 Dec 2019 18:13:06 +0300 Subject: [PATCH 20/30] 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 93aea70d293d454d85984a54f25a7ece2165aa9c Mon Sep 17 00:00:00 2001 From: dormantman Date: Mon, 30 Dec 2019 18:14:36 +0300 Subject: [PATCH 21/30] 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 3a6476fb064fef8c3f054f289bc72c1216a35b29 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 30 Dec 2019 22:23:06 +0300 Subject: [PATCH 22/30] 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 23/30] 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' From 7edd836a75f3609f857f5a9a64f55ada60bb4433 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 31 Dec 2019 01:22:45 +0300 Subject: [PATCH 24/30] helping hand representation --- apps/news/tasks.py | 2 ++ apps/notification/models.py | 1 + apps/notification/urls/back.py | 1 + apps/notification/views/common.py | 1 + project/urls/back.py | 1 + 5 files changed, 6 insertions(+) create mode 100644 apps/notification/urls/back.py diff --git a/apps/news/tasks.py b/apps/news/tasks.py index 80a2a626..c08ee8c7 100644 --- a/apps/news/tasks.py +++ b/apps/news/tasks.py @@ -26,6 +26,8 @@ def send_email_with_news(news_ids): for subscriber in subscribers: socials_for_subscriber = socials.get(subscriber.country_code) try: + # TODO: вот тут надо учесть, подписки на какие страны есть у юзера активные (нулл время отписки) и не посылать лишнего + # TODO: обрати внимание на кол-во запросов в БД плс. они пишутся в консоль for new in sent_news: context = { "title": new.title.get(subscriber.locale), diff --git a/apps/notification/models.py b/apps/notification/models.py index d1ebf055..3a9dbbf2 100644 --- a/apps/notification/models.py +++ b/apps/notification/models.py @@ -15,6 +15,7 @@ class SubscriptionType(ProjectBaseMixin, TranslatedFieldsMixin): name = TJSONField(blank=True, null=True, default=None, verbose_name=_('name'), help_text='{"en-GB":"some text"}') + # TODO: не хватает связи со страной. ForeignKey # todo: associate user & subscriber after users registration diff --git a/apps/notification/urls/back.py b/apps/notification/urls/back.py new file mode 100644 index 00000000..2012ebe1 --- /dev/null +++ b/apps/notification/urls/back.py @@ -0,0 +1 @@ +urlpatterns = [] \ No newline at end of file diff --git a/apps/notification/views/common.py b/apps/notification/views/common.py index f7fbf9fe..257c3286 100644 --- a/apps/notification/views/common.py +++ b/apps/notification/views/common.py @@ -28,6 +28,7 @@ class SubscribeInfoView(generics.RetrieveAPIView): class SubscribeInfoAuthUserView(generics.ListAPIView): """Subscribe info auth user view.""" + # TODO: тут пользователь должен видеть свои подписки. проверь плс, что работает permission_classes = (permissions.IsAuthenticated,) serializer_class = serializers.SubscribeSerializer diff --git a/project/urls/back.py b/project/urls/back.py index e7e2b43b..8245e492 100644 --- a/project/urls/back.py +++ b/project/urls/back.py @@ -10,6 +10,7 @@ urlpatterns = [ path('gallery/', include(('gallery.urls', 'gallery'), namespace='gallery')), path('location/', include('location.urls.back')), path('news/', include('news.urls.back')), + path('notifications/', include(('notification.urls.back', 'notification'), namespace='notification')), path('review/', include('review.urls.back')), path('tags/', include(('tag.urls.back', 'tag'), namespace='tag')), path('products/', include(('product.urls.back', 'product'), namespace='product')), From d4e993abee95434d08986898cef4046c7d82cc45 Mon Sep 17 00:00:00 2001 From: dormantman Date: Tue, 31 Dec 2019 05:13:36 +0300 Subject: [PATCH 25/30] Added country field --- .../0009_subscriptiontype_country.py | 20 +++++++++++++++++++ .../migrations/0010_auto_20191231_0135.py | 19 ++++++++++++++++++ apps/notification/models.py | 9 ++++----- apps/notification/serializers/common.py | 9 +++++---- 4 files changed, 48 insertions(+), 9 deletions(-) create mode 100644 apps/notification/migrations/0009_subscriptiontype_country.py create mode 100644 apps/notification/migrations/0010_auto_20191231_0135.py diff --git a/apps/notification/migrations/0009_subscriptiontype_country.py b/apps/notification/migrations/0009_subscriptiontype_country.py new file mode 100644 index 00000000..9af323dc --- /dev/null +++ b/apps/notification/migrations/0009_subscriptiontype_country.py @@ -0,0 +1,20 @@ +# Generated by Django 2.2.7 on 2019-12-31 01:31 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('location', '0033_merge_20191224_0920'), + ('notification', '0008_remove_subscribe_state'), + ] + + operations = [ + migrations.AddField( + model_name='subscriptiontype', + name='country', + field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='location.Country', verbose_name='Country'), + ), + ] diff --git a/apps/notification/migrations/0010_auto_20191231_0135.py b/apps/notification/migrations/0010_auto_20191231_0135.py new file mode 100644 index 00000000..7b83a738 --- /dev/null +++ b/apps/notification/migrations/0010_auto_20191231_0135.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.7 on 2019-12-31 01:35 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('notification', '0009_subscriptiontype_country'), + ] + + operations = [ + migrations.AlterField( + model_name='subscriptiontype', + name='country', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='location.Country', verbose_name='country'), + ), + ] diff --git a/apps/notification/models.py b/apps/notification/models.py index 3a9dbbf2..2f0ba500 100644 --- a/apps/notification/models.py +++ b/apps/notification/models.py @@ -6,6 +6,7 @@ from django.utils.timezone import now from django.utils.translation import ugettext_lazy as _ from account.models import User +from location.models import Country from utils.methods import generate_string_code from utils.models import ProjectBaseMixin, TJSONField, TranslatedFieldsMixin @@ -15,7 +16,9 @@ class SubscriptionType(ProjectBaseMixin, TranslatedFieldsMixin): name = TJSONField(blank=True, null=True, default=None, verbose_name=_('name'), help_text='{"en-GB":"some text"}') - # TODO: не хватает связи со страной. ForeignKey + country = models.ForeignKey(Country, on_delete=models.PROTECT, + blank=True, null=True, default=None, + verbose_name=_('country')) # todo: associate user & subscriber after users registration @@ -127,10 +130,6 @@ 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.""" diff --git a/apps/notification/serializers/common.py b/apps/notification/serializers/common.py index 036d8929..4a9e64f5 100644 --- a/apps/notification/serializers/common.py +++ b/apps/notification/serializers/common.py @@ -2,6 +2,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers +from location.serializers import CountrySimpleSerializer from notification import models from utils.methods import get_user_ip from utils.serializers import TranslatedField @@ -11,6 +12,7 @@ class SubscriptionTypeSerializer(serializers.ModelSerializer): """Subscription type serializer.""" name_translated = TranslatedField() + country = CountrySimpleSerializer() class Meta: """Meta class.""" @@ -20,6 +22,7 @@ class SubscriptionTypeSerializer(serializers.ModelSerializer): 'id', 'index_name', 'name_translated', + 'country', ) @@ -80,7 +83,7 @@ class SubscribeObjectSerializer(serializers.ModelSerializer): """Meta class.""" model = models.Subscriber - fields = () + fields = ('subscriber', ) read_only_fields = ('subscribe_date', 'unsubscribe_date',) @@ -88,8 +91,7 @@ class SubscribeSerializer(serializers.ModelSerializer): """Subscribe serializer.""" email = serializers.EmailField(required=False, source='send_to') - subscription_types = SubscriptionTypeSerializer(read_only=True, source='subscription_types_set') - subscribe_objects = SubscribeObjectSerializer(read_only=True) + subscription_types = SubscriptionTypeSerializer(many=True, read_only=True) class Meta: """Meta class.""" @@ -99,5 +101,4 @@ class SubscribeSerializer(serializers.ModelSerializer): 'email', 'subscription_types', 'link_to_unsubscribe', - 'subscribe_objects', ) From 241012da424217f4dff895e352e8312ea1a0a58f Mon Sep 17 00:00:00 2001 From: dormantman Date: Tue, 31 Dec 2019 05:29:48 +0300 Subject: [PATCH 26/30] Added prefetch optimize --- apps/news/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/news/tasks.py b/apps/news/tasks.py index c08ee8c7..4a980144 100644 --- a/apps/news/tasks.py +++ b/apps/news/tasks.py @@ -20,7 +20,7 @@ def send_email_with_news(news_ids): htmly = get_template(settings.NEWS_EMAIL_TEMPLATE) year = datetime.now().year - socials = list(SiteSettings.objects.with_country()) + socials = list(SiteSettings.objects.with_country().select_related('country')) socials = dict(zip(map(lambda social: social.country.code, socials), socials)) for subscriber in subscribers: From 54c3e452e20a00b406c3cad966a32fa2492a51ab Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 31 Dec 2019 09:20:50 +0300 Subject: [PATCH 27/30] bo search news --- apps/news/urls/back.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/news/urls/back.py b/apps/news/urls/back.py index 3aabeeac..54f40513 100644 --- a/apps/news/urls/back.py +++ b/apps/news/urls/back.py @@ -2,17 +2,17 @@ from django.urls import path from news import views +from search_indexes.views import NewsDocumentViewSet app_name = 'news' urlpatterns = [ path('', views.NewsBackOfficeLCView.as_view(), name='list-create'), - path('/', views.NewsBackOfficeRUDView.as_view(), - name='retrieve-update-destroy'), - path('/gallery/', views.NewsBackOfficeGalleryListView.as_view(), - name='gallery-list'), + path('/', views.NewsBackOfficeRUDView.as_view(), name='retrieve-update-destroy'), + path('/gallery/', views.NewsBackOfficeGalleryListView.as_view(), name='gallery-list'), path('/gallery//', views.NewsBackOfficeGalleryCreateDestroyView.as_view(), name='gallery-create-destroy'), path('/carousels/', views.NewsCarouselCreateDestroyView.as_view(), name='create-destroy-carousels'), path('/clone/', views.NewsCloneView.as_view(), name='clone-news-item'), + path('search/', NewsDocumentViewSet.as_view({'get': 'list'}), name='search-news'), ] From 140cc6cfa5f63e56aae42bcf592cddf63d33a8ad Mon Sep 17 00:00:00 2001 From: dormantman Date: Tue, 31 Dec 2019 16:09:04 +0300 Subject: [PATCH 28/30] Reformat sending mail structure --- apps/news/tasks.py | 19 +++++++++++++------ apps/notification/tests.py | 10 ++++++++-- apps/notification/views/common.py | 1 - 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/apps/news/tasks.py b/apps/news/tasks.py index 4a980144..0267fcf4 100644 --- a/apps/news/tasks.py +++ b/apps/news/tasks.py @@ -9,12 +9,14 @@ from django.template.loader import get_template, render_to_string from main.models import SiteSettings from news import models -from notification.models import Subscriber +from notification.models import Subscribe @shared_task def send_email_with_news(news_ids): - subscribers = Subscriber.objects.all() + subscribes = Subscribe.objects.all() \ + .prefetch_related('subscriber') \ + .prefetch_related('subscription_type') sent_news = models.News.objects.filter(id__in=news_ids) htmly = get_template(settings.NEWS_EMAIL_TEMPLATE) @@ -23,11 +25,16 @@ def send_email_with_news(news_ids): socials = list(SiteSettings.objects.with_country().select_related('country')) socials = dict(zip(map(lambda social: social.country.code, socials), socials)) - for subscriber in subscribers: - socials_for_subscriber = socials.get(subscriber.country_code) + for subscribe in subscribes.filter(unsubscribe_date=None): + country = subscribe.subscription_type.country + + if country is None: + continue + + socials_for_subscriber = socials.get(country.code) + subscriber = subscribe.subscriber + try: - # TODO: вот тут надо учесть, подписки на какие страны есть у юзера активные (нулл время отписки) и не посылать лишнего - # TODO: обрати внимание на кол-во запросов в БД плс. они пишутся в консоль for new in sent_news: context = { "title": new.title.get(subscriber.locale), diff --git a/apps/notification/tests.py b/apps/notification/tests.py index 410365ff..cf380410 100644 --- a/apps/notification/tests.py +++ b/apps/notification/tests.py @@ -80,7 +80,7 @@ class NotificationSubscribeInfoTestCase(APITestCase): class NotificationUnsubscribeAuthUserTestCase(BaseTestCase): def test_unsubscribe_auth_user(self): - Subscriber.objects.create(user=self.user, email=self.email, state=1) + Subscriber.objects.create(user=self.user, email=self.email) self.test_data = { "email": self.email @@ -174,7 +174,7 @@ class NotificationManySubscribeTestCase(APITestCase): test_data = { 'email': self.email, 'subscription_types_pk': [ - self.test_subscribe_type + self.test_subscribe_type.id ] } @@ -183,6 +183,12 @@ class NotificationManySubscribeTestCase(APITestCase): self.assertEqual(response.json()["email"], test_data["email"]) 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) + test_data = { "email": self.email } diff --git a/apps/notification/views/common.py b/apps/notification/views/common.py index 257c3286..f7fbf9fe 100644 --- a/apps/notification/views/common.py +++ b/apps/notification/views/common.py @@ -28,7 +28,6 @@ class SubscribeInfoView(generics.RetrieveAPIView): class SubscribeInfoAuthUserView(generics.ListAPIView): """Subscribe info auth user view.""" - # TODO: тут пользователь должен видеть свои подписки. проверь плс, что работает permission_classes = (permissions.IsAuthenticated,) serializer_class = serializers.SubscribeSerializer From 33a616ab96a9a099177597c4b504bf303ef953ab Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 9 Jan 2020 13:08:59 +0300 Subject: [PATCH 29/30] fix transfer translation for news --- apps/news/transfer_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/news/transfer_data.py b/apps/news/transfer_data.py index c057da84..f4e16739 100644 --- a/apps/news/transfer_data.py +++ b/apps/news/transfer_data.py @@ -145,7 +145,7 @@ def add_tags(): ) if created: text_value = ' '.join(new_tag.value.split('_')) - translation = SiteInterfaceDictionary(page={'en-GB': text_value}, keywords=f'tag.{new_tag.category}.{new_tag.value}') + translation = SiteInterfaceDictionary(text={'en-GB': text_value}, keywords=f'tag.{new_tag.category}.{new_tag.value}') translation.save() new_tag.translation = translation new_tag.save() From fa8b4d6d979d613d67423eee3b7c42ada1398567 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 9 Jan 2020 13:48:38 +0300 Subject: [PATCH 30/30] chosen tags for recipes --- apps/tag/filters.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/tag/filters.py b/apps/tag/filters.py index a731272e..28c3db33 100644 --- a/apps/tag/filters.py +++ b/apps/tag/filters.py @@ -10,10 +10,12 @@ class TagsBaseFilterSet(filters.FilterSet): # Object type choices NEWS = 'news' ESTABLISHMENT = 'establishment' + RECIPES = 'recipe' TYPE_CHOICES = ( (NEWS, 'News'), (ESTABLISHMENT, 'Establishment'), + (RECIPES, 'Recipe'), ) type = filters.MultipleChoiceFilter(choices=TYPE_CHOICES, @@ -91,5 +93,7 @@ class TagsFilterSet(TagsBaseFilterSet): if self.ESTABLISHMENT in value: queryset = queryset.for_establishments().filter(category__value_type='list').filter(value__in=settings.ESTABLISHMENT_CHOSEN_TAGS).distinct( 'value') + if self.RECIPES in value: + queryset = queryset.for_news().filter(value__in=settings.RECIPES_CHOSEN_TAGS).distinct('value') return queryset