Notification app & subscription
This commit is contained in:
parent
62da2ee114
commit
2a8ffb16de
0
apps/notification/__init__.py
Normal file
0
apps/notification/__init__.py
Normal file
3
apps/notification/admin.py
Normal file
3
apps/notification/admin.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
7
apps/notification/apps.py
Normal file
7
apps/notification/apps.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
from django.apps import AppConfig
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class NotificationConfig(AppConfig):
|
||||
name = 'notification'
|
||||
verbose_name = _('notification')
|
||||
37
apps/notification/migrations/0001_initial.py
Normal file
37
apps/notification/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# Generated by Django 2.2.4 on 2019-08-30 11:22
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Subscriber',
|
||||
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')),
|
||||
('email', models.EmailField(blank=True, default=None, max_length=254, null=True, unique=True, verbose_name='Email')),
|
||||
('ip_address', models.GenericIPAddressField(blank=True, default=None, null=True, verbose_name='IP address')),
|
||||
('country_code', models.CharField(blank=True, default=None, max_length=10, null=True, verbose_name='Country code')),
|
||||
('locale', models.CharField(blank=True, default=None, max_length=10, null=True, verbose_name='Locale identifier')),
|
||||
('state', models.PositiveIntegerField(choices=[(0, 'Unusable'), (1, 'Usable')], default=1, verbose_name='State')),
|
||||
('update_code', models.CharField(blank=True, db_index=True, default=None, max_length=254, null=True, verbose_name='Token')),
|
||||
('user', models.OneToOneField(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='subscriber', to=settings.AUTH_USER_MODEL, verbose_name='User')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Subscriber',
|
||||
'verbose_name_plural': 'Subscribers',
|
||||
},
|
||||
),
|
||||
]
|
||||
0
apps/notification/migrations/__init__.py
Normal file
0
apps/notification/migrations/__init__.py
Normal file
124
apps/notification/models.py
Normal file
124
apps/notification/models.py
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
"""Notification app models."""
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
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
|
||||
|
||||
|
||||
# todo: associate user & subscriber after users registration
|
||||
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, *args, **kwargs):
|
||||
"""Make subscriber and update info."""
|
||||
# search existing object
|
||||
if not user:
|
||||
user = User.objects.filter(email=email).first()
|
||||
if user:
|
||||
obj = self.model.objects.filter(models.Q(user=user) | models.Q(
|
||||
email=user.email)).first()
|
||||
else:
|
||||
obj = self.model.objects.filter(email=email).first()
|
||||
|
||||
# update or create
|
||||
if obj:
|
||||
if user:
|
||||
obj.user = user
|
||||
obj.email = None
|
||||
else:
|
||||
obj.email = email
|
||||
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.save()
|
||||
else:
|
||||
obj = self.model.objects.create(user=user, email=email, ip_address=ip_address,
|
||||
country_code=country_code, locale=locale)
|
||||
return obj
|
||||
|
||||
def associate_user(self, user):
|
||||
"""Associate user."""
|
||||
obj = self.model.objects.filter(user=user).first()
|
||||
if obj is None:
|
||||
obj = self.model.objects.filter(email=user.email_confirmed, user__isnull=True).first()
|
||||
if obj:
|
||||
obj.user = user
|
||||
obj.email = None
|
||||
obj.save()
|
||||
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."""
|
||||
|
||||
UNUSABLE = 0
|
||||
USABLE = 1
|
||||
|
||||
STATE_CHOICES = (
|
||||
(UNUSABLE, _('Unusable')),
|
||||
(USABLE, _('Usable')),
|
||||
)
|
||||
|
||||
user = models.OneToOneField(User, blank=True, null=True, default=None,
|
||||
on_delete=models.SET_NULL, related_name='subscriber',
|
||||
verbose_name=_('User'))
|
||||
email = models.EmailField(blank=True, null=True, default=None, unique=True,
|
||||
verbose_name=_('Email'))
|
||||
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, null=True, default=None,
|
||||
db_index=True, verbose_name=_('Token'))
|
||||
|
||||
objects = SubscriberManager.from_queryset(SubscriberQuerySet)()
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
||||
verbose_name = _('Subscriber')
|
||||
verbose_name_plural = _('Subscribers')
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""Overrided save method."""
|
||||
if self.update_code is None:
|
||||
self.update_code = generate_string_code()
|
||||
return super(Subscriber, self).save(*args, **kwargs)
|
||||
|
||||
def unsubscribe(self):
|
||||
"""Unsubscribe user."""
|
||||
self.state = self.UNUSABLE
|
||||
self.save()
|
||||
|
||||
@property
|
||||
def send_to(self):
|
||||
"""Actual email."""
|
||||
return self.user.email if self.user else self.email
|
||||
|
||||
@property
|
||||
def link_to_unsubscribe(self):
|
||||
"""Link to unsubscribe."""
|
||||
schema = settings.SCHEMA_URI
|
||||
site_domain = settings.SITE_DOMAIN_URI
|
||||
url = settings.SITE_REDIRECT_URL_UNSUBSCRIBE
|
||||
query = f'?code={self.update_code}'
|
||||
return f'{schema}://{site_domain}{url}{query}'
|
||||
0
apps/notification/serializers/__init__.py
Normal file
0
apps/notification/serializers/__init__.py
Normal file
47
apps/notification/serializers/common.py
Normal file
47
apps/notification/serializers/common.py
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
"""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
|
||||
|
||||
|
||||
class SubscribeSerializer(serializers.ModelSerializer):
|
||||
"""Subscribe serializer."""
|
||||
|
||||
email = serializers.EmailField(required=False, source='send_to')
|
||||
state_display = serializers.CharField(source='get_state_display', read_only=True)
|
||||
|
||||
class Meta:
|
||||
"""Meta class."""
|
||||
|
||||
model = models.Subscriber
|
||||
fields = ('email', 'state', 'state_display')
|
||||
read_only_fields = ('state', 'state_display')
|
||||
|
||||
def validate(self, attrs):
|
||||
"""Validate attrs."""
|
||||
request = self.context.get('request')
|
||||
user = request.user
|
||||
|
||||
# validate email
|
||||
email = attrs.get('send_to')
|
||||
if user.is_authenticated:
|
||||
if email is not None and email != user.email:
|
||||
raise serializers.ValidationError(_('Does not match user email'))
|
||||
else:
|
||||
if email is None:
|
||||
raise serializers.ValidationError({'email': _('This field is required.')})
|
||||
|
||||
# append info
|
||||
attrs['email'] = email
|
||||
attrs['country_code'] = request.country_code
|
||||
attrs['locale'] = request.locale
|
||||
attrs['ip_address'] = get_user_ip(request)
|
||||
if user.is_authenticated:
|
||||
attrs['user'] = user
|
||||
return attrs
|
||||
|
||||
def create(self, validated_data):
|
||||
"""Create obj."""
|
||||
obj = models.Subscriber.objects.make_subscriber(**validated_data)
|
||||
return obj
|
||||
0
apps/notification/urls/__init__.py
Normal file
0
apps/notification/urls/__init__.py
Normal file
12
apps/notification/urls/common.py
Normal file
12
apps/notification/urls/common.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
"""Notification app common urlconf."""
|
||||
from django.urls import path
|
||||
from notification.views import common
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('subscribe/', common.SubscribeView.as_view(), name='subscribe'),
|
||||
path('subscribe-info/', common.SubscribeInfoAuthUserView.as_view(), name='check-code-auth'),
|
||||
path('subscribe-info/<code>/', common.SubscribeInfoView.as_view(), name='check-code'),
|
||||
path('unsubscribe/', common.UnsubscribeAuthUserView.as_view(), name='unsubscribe-auth'),
|
||||
path('unsubscribe/<code>/', common.UnsubscribeView.as_view(), name='unsubscribe'),
|
||||
]
|
||||
7
apps/notification/urls/web.py
Normal file
7
apps/notification/urls/web.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
"""Establishment app web urlconf."""
|
||||
from notification.urls.common import urlpatterns as common_urlpatterns
|
||||
|
||||
|
||||
urlpatterns = []
|
||||
|
||||
urlpatterns.extend(common_urlpatterns)
|
||||
0
apps/notification/views/__init__.py
Normal file
0
apps/notification/views/__init__.py
Normal file
78
apps/notification/views/common.py
Normal file
78
apps/notification/views/common.py
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
"""Notification app common views."""
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework import generics, permissions
|
||||
from rest_framework.response import Response
|
||||
from notification import models, throttling
|
||||
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 SubscribeInfoView(generics.RetrieveAPIView):
|
||||
"""Subscribe info view."""
|
||||
|
||||
lookup_field = 'update_code'
|
||||
lookup_url_kwarg = 'code'
|
||||
permission_classes = (permissions.AllowAny, )
|
||||
queryset = models.Subscriber.objects.all()
|
||||
serializer_class = serializers.SubscribeSerializer
|
||||
|
||||
|
||||
class SubscribeInfoAuthUserView(generics.RetrieveAPIView):
|
||||
"""Subscribe info auth user view."""
|
||||
|
||||
permission_classes = (permissions.IsAuthenticated, )
|
||||
queryset = models.Subscriber.objects.all()
|
||||
serializer_class = serializers.SubscribeSerializer
|
||||
|
||||
def get_object(self):
|
||||
user = self.request.user
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
filter_kwargs = {'user': user}
|
||||
obj = get_object_or_404(queryset, **filter_kwargs)
|
||||
self.check_object_permissions(self.request, obj)
|
||||
return obj
|
||||
|
||||
|
||||
class UnsubscribeView(generics.GenericAPIView):
|
||||
"""Unsubscribe view."""
|
||||
|
||||
lookup_field = 'update_code'
|
||||
lookup_url_kwarg = 'code'
|
||||
permission_classes = (permissions.AllowAny, )
|
||||
queryset = models.Subscriber.objects.all()
|
||||
serializer_class = serializers.SubscribeSerializer
|
||||
|
||||
def patch(self, request, *args, **kw):
|
||||
obj = self.get_object()
|
||||
obj.unsubscribe()
|
||||
serializer = self.get_serializer(instance=obj)
|
||||
return Response(data=serializer.data)
|
||||
|
||||
|
||||
class UnsubscribeAuthUserView(generics.GenericAPIView):
|
||||
"""Unsubscribe auth user view."""
|
||||
|
||||
permission_classes = (permissions.IsAuthenticated, )
|
||||
queryset = models.Subscriber.objects.all()
|
||||
serializer_class = serializers.SubscribeSerializer
|
||||
|
||||
def patch(self, request, *args, **kw):
|
||||
user = request.user
|
||||
obj = get_object_or_404(models.Subscriber, user=user)
|
||||
obj.unsubscribe()
|
||||
serializer = self.get_serializer(instance=obj)
|
||||
return Response(data=serializer.data)
|
||||
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
"""Utils app method."""
|
||||
import random
|
||||
import re
|
||||
import string
|
||||
|
||||
from django.conf import settings
|
||||
from django.http.request import HttpRequest
|
||||
|
|
@ -60,3 +61,20 @@ def svg_image_path(instance, filename):
|
|||
instance._meta.model_name,
|
||||
datetime.now().strftime(settings.REST_DATE_FORMAT),
|
||||
filename)
|
||||
|
||||
|
||||
def get_user_ip(request):
|
||||
"""Get user ip."""
|
||||
meta_dict = request.META
|
||||
x_forwarded_for = meta_dict.get('HTTP_X_FORWARDED_FOR')
|
||||
if x_forwarded_for:
|
||||
ip = x_forwarded_for.split(',')[0]
|
||||
else:
|
||||
ip = meta_dict.get('REMOTE_ADDR')
|
||||
return ip
|
||||
|
||||
|
||||
def generate_string_code(size=64,
|
||||
chars=string.ascii_lowercase + string.ascii_uppercase + string.digits):
|
||||
"""Generate string code."""
|
||||
return ''.join([random.SystemRandom().choice(chars) for _ in range(size)])
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ PROJECT_APPS = [
|
|||
'location.apps.LocationConfig',
|
||||
'main.apps.MainConfig',
|
||||
'news.apps.NewsConfig',
|
||||
'notification.apps.NotificationConfig',
|
||||
'partner.apps.PartnerConfig',
|
||||
'translation.apps.TranslationConfig',
|
||||
'configuration.apps.ConfigurationConfig',
|
||||
|
|
@ -300,7 +301,6 @@ CELERY_ACCEPT_CONTENT = ['application/json']
|
|||
CELERY_TASK_SERIALIZER = 'json'
|
||||
CELERY_RESULT_SERIALIZER = 'json'
|
||||
CELERY_TIMEZONE = TIME_ZONE
|
||||
USE_CELERY = False
|
||||
|
||||
# Django FCM (Firebase push notificatoins)
|
||||
FCM_DJANGO_SETTINGS = {
|
||||
|
|
@ -385,3 +385,6 @@ FILE_UPLOAD_PERMISSIONS = 0o644
|
|||
|
||||
|
||||
SOLO_CACHE_TIMEOUT = 300
|
||||
|
||||
# REDIRECT URL
|
||||
SITE_REDIRECT_URL_UNSUBSCRIBE = '/unsubscribe/'
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ ALLOWED_HOSTS = ['gm.id-east.ru', '95.213.204.126']
|
|||
|
||||
SEND_SMS = False
|
||||
SMS_CODE_SHOW = True
|
||||
USE_CELERY = False
|
||||
|
||||
SCHEMA_URI = 'http'
|
||||
DEFAULT_SUBDOMAIN = 'www'
|
||||
|
|
|
|||
|
|
@ -12,11 +12,6 @@ DEFAULT_SUBDOMAIN = 'www'
|
|||
SITE_DOMAIN_URI = 'testserver.com:8000'
|
||||
DOMAIN_URI = '0.0.0.0:8000'
|
||||
|
||||
# OTHER SETTINGS
|
||||
API_HOST = '0.0.0.0:8000'
|
||||
API_HOST_URL = 'http://%s' % API_HOST
|
||||
|
||||
|
||||
# CELERY
|
||||
BROKER_URL = 'amqp://rabbitmq:5672'
|
||||
CELERY_RESULT_BACKEND = BROKER_URL
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ urlpatterns = [
|
|||
path('collection/', include('collection.urls.web')),
|
||||
path('establishments/', include('establishment.urls.web')),
|
||||
path('news/', include('news.urls.web')),
|
||||
path('notifications/', include('notification.urls.web')),
|
||||
path('partner/', include('partner.urls.web')),
|
||||
path('location/', include('location.urls')),
|
||||
path('main/', include('main.urls')),
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user