Merge remote-tracking branch 'origin/develop' into feature/backoffice

This commit is contained in:
Dmitriy Kuzmenko 2019-09-12 18:27:04 +03:00
commit 10d9b52bec
38 changed files with 465 additions and 589 deletions

3
.gitignore vendored
View File

@ -19,3 +19,6 @@ logs/
/datadir/
/_files/
/geoip_db/
# dev
./docker-compose.override.yml

View File

@ -15,11 +15,13 @@ class UserAdmin(BaseUserAdmin):
list_filter = ('is_active', 'is_staff', 'is_superuser', 'email_confirmed',
'groups',)
search_fields = ('email', 'first_name', 'last_name')
readonly_fields = ('last_login', 'date_joined',)
readonly_fields = ('last_login', 'date_joined', 'image_preview', 'cropped_image_preview')
fieldsets = (
(None, {'fields': ('email', 'password',)}),
(_('Personal info'), {
'fields': ('username', 'first_name', 'last_name', 'image')}),
'fields': ('username', 'first_name', 'last_name',
'image_url', 'image_preview',
'cropped_image_url', 'cropped_image_preview',)}),
(_('Subscription'), {
'fields': (
'newsletter',
@ -47,11 +49,14 @@ class UserAdmin(BaseUserAdmin):
short_name.short_description = _('Name')
def image_preview(self, obj):
"""Get user image preview"""
return obj.image_tag
@admin.register(models.ResetPasswordToken)
class ResetPasswordToken(admin.ModelAdmin):
"""Model admin for ResetPasswordToken"""
list_display = ('id', 'user', 'expiry_datetime')
list_filter = ('expiry_datetime', 'user')
search_fields = ('user', )
readonly_fields = ('user', 'key', )
image_preview.short_description = 'Image preview'
def cropped_image_preview(self, obj):
"""Get user cropped image preview"""
return obj.cropped_image_tag
cropped_image_preview.short_description = 'Cropped image preview'

View File

@ -0,0 +1,16 @@
# Generated by Django 2.2.4 on 2019-09-12 11:42
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('account', '0005_user_cropped_image'),
]
operations = [
migrations.DeleteModel(
name='ResetPasswordToken',
),
]

View File

@ -0,0 +1,21 @@
# Generated by Django 2.2.4 on 2019-09-12 13:23
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('account', '0006_delete_resetpasswordtoken'),
]
operations = [
migrations.RemoveField(
model_name='user',
name='cropped_image',
),
migrations.RemoveField(
model_name='user',
name='image',
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 2.2.4 on 2019-09-12 13:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('account', '0007_auto_20190912_1323'),
]
operations = [
migrations.AddField(
model_name='user',
name='cropped_image_url',
field=models.URLField(blank=True, default=None, null=True, verbose_name='Cropped image URL path'),
),
migrations.AddField(
model_name='user',
name='image_url',
field=models.URLField(blank=True, default=None, null=True, verbose_name='Image URL path'),
),
]

View File

@ -7,13 +7,12 @@ from django.db import models
from django.template.loader import render_to_string
from django.utils import timezone
from django.utils.encoding import force_bytes
from django.utils.html import mark_safe
from django.utils.http import urlsafe_base64_encode
from django.utils.translation import ugettext_lazy as _
from easy_thumbnails.fields import ThumbnailerImageField
from rest_framework.authtoken.models import Token
from authorization.models import Application
from utils.methods import image_path
from utils.models import GMTokenGenerator
from utils.models import ImageMixin, ProjectBaseMixin, PlatformMixin
from utils.tokens import GMRefreshToken
@ -52,16 +51,13 @@ class UserQuerySet(models.QuerySet):
return self.filter(oauth2_provider_refreshtoken__token=token,
oauth2_provider_refreshtoken__expires__gt=timezone.now())
def by_username(self, username: str):
"""Filter users by username."""
return self.filter(username=username)
class User(ImageMixin, AbstractUser):
class User(AbstractUser):
"""Base user model."""
cropped_image = ThumbnailerImageField(upload_to=image_path,
blank=True, null=True, default=None,
verbose_name=_('Crop image'))
image_url = models.URLField(verbose_name=_('Image URL path'),
blank=True, null=True, default=None)
cropped_image_url = models.URLField(verbose_name=_('Cropped image URL path'),
blank=True, null=True, default=None)
email = models.EmailField(_('email address'), blank=True,
null=True, default=None)
email_confirmed = models.BooleanField(_('email status'), default=False)
@ -98,9 +94,9 @@ class User(ImageMixin, AbstractUser):
self.is_active = switcher
self.save()
def create_jwt_tokens(self, source: int):
def create_jwt_tokens(self, source: int = None):
"""Create JWT tokens for user"""
token = GMRefreshToken.for_user_by_source(self, source)
token = GMRefreshToken.for_user(self, source)
return {
'access_token': str(token.access_token),
'refresh_token': str(token),
@ -151,7 +147,7 @@ class User(ImageMixin, AbstractUser):
@property
def reset_password_token(self):
"""Make a token for finish signup."""
return GMTokenGenerator(purpose=GMTokenGenerator.RESET_PASSWORD).make_token(self)
return password_token_generator.make_token(self)
@property
def get_user_uidb64(self):
@ -159,112 +155,43 @@ class User(ImageMixin, AbstractUser):
return urlsafe_base64_encode(force_bytes(self.pk))
@property
def confirm_email_template(self):
"""Get confirm email template"""
return render_to_string(
template_name=settings.CONFIRM_EMAIL_TEMPLATE,
context={'token': self.confirm_email_token,
'uidb64': self.get_user_uidb64,
'domain_uri': settings.DOMAIN_URI,
'site_name': settings.SITE_NAME})
def base_template(self):
"""Base email template"""
return {'domain_uri': settings.DOMAIN_URI,
'uidb64': self.get_user_uidb64,
'site_name': settings.SITE_NAME}
@property
def change_email_template(self):
"""Get change email template"""
return render_to_string(
template_name=settings.CHANGE_EMAIL_TEMPLATE,
context={'token': self.change_email_token,
'uidb64': self.get_user_uidb64,
'domain_uri': settings.DOMAIN_URI,
'site_name': settings.SITE_NAME})
class ResetPasswordTokenQuerySet(models.QuerySet):
"""Reset password token query set"""
def expired(self):
"""Show only expired"""
return self.filter(expiry_datetime__lt=timezone.now())
def valid(self):
"""Show only valid"""
return self.filter(expiry_datetime__gt=timezone.now())
def by_user(self, user):
"""Show obj by user"""
return self.filter(user=user)
class ResetPasswordToken(PlatformMixin, ProjectBaseMixin):
"""Reset password model"""
user = models.ForeignKey(User,
related_name='password_reset_tokens',
on_delete=models.CASCADE,
verbose_name=_('The User which is associated to '
'this password reset token'))
# Key field, though it is not the primary key of the model
key = models.CharField(max_length=255,
verbose_name=_('Key'))
ip_address = models.GenericIPAddressField(default='',
blank=True, null=True,
verbose_name=_('The IP address of this session'))
expiry_datetime = models.DateTimeField(blank=True, null=True,
verbose_name=_('Expiration datetime'))
objects = ResetPasswordTokenQuerySet.as_manager()
class Meta:
verbose_name = _("Password Reset Token")
verbose_name_plural = _("Password Reset Tokens")
def __str__(self):
return "Password reset token for user {user}".format(user=self.user)
def save(self, *args, **kwargs):
"""Override save method"""
if not self.expiry_datetime:
self.expiry_datetime = (
timezone.now() +
timezone.timedelta(hours=self.get_resetting_token_expiration)
)
if not self.key:
self.key = self.generate_token
return super(ResetPasswordToken, self).save(*args, **kwargs)
def image_tag(self):
return mark_safe(f'<img src="{self.image_url}" />')
@property
def get_resetting_token_expiration(self):
"""Get resetting token expiration"""
return settings.RESETTING_TOKEN_EXPIRATION
def cropped_image_tag(self):
return mark_safe(f'<img src="{self.cropped_image_url}" />')
@property
def is_valid(self):
"""Check if valid token or not"""
return timezone.now() > self.expiry_datetime
@property
def generate_token(self):
"""Generates a pseudo random code"""
return password_token_generator.make_token(self.user)
@property
def reset_password_template(self):
def reset_password_template(self, country_code):
"""Get reset password template"""
context = {'token': self.reset_password_token,
'country_code': country_code}
context.update(self.base_template)
return render_to_string(
template_name=settings.RESETTING_TOKEN_TEMPLATE,
context={'token': self.key,
'uidb64': self.user.get_user_uidb64,
'domain_uri': settings.DOMAIN_URI,
'site_name': settings.SITE_NAME})
context=context)
@staticmethod
def token_is_valid(user, token):
"""Check if token is valid"""
return password_token_generator.check_token(user, token)
def confirm_email_template(self, country_code):
"""Get confirm email template"""
context = {'token': self.confirm_email_token,
'country_code': country_code}
context.update(self.base_template)
return render_to_string(
template_name=settings.CONFIRM_EMAIL_TEMPLATE,
context=context)
def overdue(self):
"""Overdue instance"""
self.expiry_datetime = timezone.now()
self.save()
def change_email_template(self, country_code):
"""Get change email template"""
context = {'token': self.change_email_token,
'country_code': country_code}
context.update(self.base_template)
return render_to_string(
template_name=settings.CHANGE_EMAIL_TEMPLATE,
context=context)

View File

@ -4,9 +4,11 @@ from django.contrib.auth import password_validation as password_validators
from fcm_django.models import FCMDevice
from rest_framework import exceptions
from rest_framework import serializers
from rest_framework import validators as rest_validators
from account import models, tasks
from utils import exceptions as utils_exceptions
from utils import methods as utils_methods
# User serializers
@ -17,12 +19,16 @@ class UserSerializer(serializers.ModelSerializer):
fullname = serializers.CharField(source='get_full_name', read_only=True)
# REQUEST
username = serializers.CharField(required=False)
username = serializers.CharField(
validators=(rest_validators.UniqueValidator(queryset=models.User.objects.all()),),
required=False)
first_name = serializers.CharField(required=False, write_only=True)
last_name = serializers.CharField(required=False, write_only=True)
image = serializers.ImageField(required=False)
cropped_image = serializers.ImageField(required=False)
email = serializers.EmailField(required=False)
image_url = serializers.URLField(required=False)
cropped_image_url = serializers.URLField(required=False)
email = serializers.EmailField(
validators=(rest_validators.UniqueValidator(queryset=models.User.objects.all()),),
required=False)
newsletter = serializers.BooleanField(required=False)
class Meta:
@ -32,8 +38,8 @@ class UserSerializer(serializers.ModelSerializer):
'first_name',
'last_name',
'fullname',
'cropped_image',
'image',
'cropped_image_url',
'image_url',
'email',
'email_confirmed',
'newsletter',
@ -42,33 +48,31 @@ class UserSerializer(serializers.ModelSerializer):
def validate_email(self, value):
"""Validate email value"""
if value == self.instance.email:
raise serializers.ValidationError()
raise serializers.ValidationError(detail='Equal email address.')
return value
def validate_username(self, value):
"""Validate username"""
if models.User.objects.by_username(username=value).exists():
raise serializers.ValidationError()
"""Custom username validation"""
valid = utils_methods.username_validator(username=value)
if not valid:
raise utils_exceptions.NotValidUsernameError()
return value
def validate(self, attrs):
if ('cropped_image' in attrs or 'image' in attrs) and \
('cropped_image' not in attrs or 'image' not in attrs):
raise utils_exceptions.UserUpdateUploadImageError()
return attrs
def update(self, instance, validated_data):
"""
Override update method
"""
if 'email' in validated_data:
validated_data['email_confirmed'] = False
"""Override update method"""
instance = super().update(instance, validated_data)
# Send verification link on user email for change email address
if settings.USE_CELERY:
tasks.confirm_new_email_address.delay(instance.id)
else:
tasks.confirm_new_email_address(instance.id)
if 'email' in validated_data:
instance.email_confirmed = False
instance.save()
# Send verification link on user email for change email address
if settings.USE_CELERY:
tasks.change_email_address.delay(
user_id=instance.id,
country_code=self.context.get('request').country_code)
else:
tasks.change_email_address(
user_id=instance.id,
country_code=self.context.get('request').country_code)
return instance
@ -156,7 +160,7 @@ class ConfirmEmailSerializer(serializers.ModelSerializer):
"""Override validate method"""
email_confirmed = self.instance.email_confirmed
if email_confirmed:
raise serializers.ValidationError()
raise utils_exceptions.EmailConfirmedError()
return attrs
def update(self, instance, validated_data):

View File

@ -1,61 +1,49 @@
"""Serializers for account web"""
from django.conf import settings
from django.contrib.auth import password_validation as password_validators
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from account import models, tasks
from account import models
from utils import exceptions as utils_exceptions
from utils.methods import username_validator
class PasswordResetSerializer(serializers.ModelSerializer):
class PasswordResetSerializer(serializers.Serializer):
"""Serializer from model PasswordReset"""
username_or_email = serializers.CharField(required=False,
write_only=True,)
class Meta:
"""Meta class"""
model = models.ResetPasswordToken
fields = (
'username_or_email',
)
@property
def request(self):
"""Get request from context"""
return self.context.get('request')
def validate(self, attrs):
"""Override validate method"""
user = self.context.get('request').user
user = self.request.user
username_or_email = attrs.get('username_or_email')
if user.is_anonymous:
username_or_email = attrs.get('username_or_email')
if not user.is_authenticated:
if not username_or_email:
raise serializers.ValidationError(_('Username or Email not requested'))
# Check user in DB
user_qs = models.User.objects.filter(Q(email=username_or_email) |
Q(username=username_or_email))
if user_qs.exists():
attrs['user'] = user_qs.first()
raise serializers.ValidationError(_('username or email not in request body.'))
filters = {}
if username_validator(username_or_email):
filters.update({'username__icontains': username_or_email})
else:
raise utils_exceptions.UserNotFoundError()
else:
attrs['user'] = user
filters.update({'email__icontains': username_or_email})
if filters:
filters.update({'is_active': True})
user_qs = models.User.objects.filter(**filters)
if not user_qs.exists():
raise utils_exceptions.UserNotFoundError()
user = user_qs.first()
attrs['user'] = user
return attrs
def create(self, validated_data, *args, **kwargs):
"""Override create method"""
user = validated_data.pop('user')
ip_address = self.context.get('request').META.get('REMOTE_ADDR')
obj = models.ResetPasswordToken.objects.create(
user=user,
ip_address=ip_address,
source=models.ResetPasswordToken.WEB
)
if settings.USE_CELERY:
tasks.send_reset_password_email.delay(obj.id)
else:
tasks.send_reset_password_email(obj.id)
return obj
class PasswordResetConfirmSerializer(serializers.ModelSerializer):
"""Serializer for model User"""
@ -64,30 +52,24 @@ class PasswordResetConfirmSerializer(serializers.ModelSerializer):
class Meta:
"""Meta class"""
model = models.ResetPasswordToken
model = models.User
fields = ('password', )
def validate(self, attrs):
"""Override validate method"""
user = self.instance.user
password = attrs.get('password')
def validate_password(self, value):
"""Password validation method."""
try:
# Compare new password with the old ones
if user.check_password(raw_password=password):
if self.instance.check_password(raw_password=value):
raise utils_exceptions.PasswordsAreEqual()
# Validate password
password_validators.validate_password(password=password)
password_validators.validate_password(password=value)
except serializers.ValidationError as e:
raise serializers.ValidationError(str(e))
else:
return attrs
return value
def update(self, instance, validated_data):
"""Override update method"""
# Update user password from instance
instance.user.set_password(validated_data.get('password'))
instance.user.save()
# Overdue instance
instance.overdue()
instance.set_password(validated_data.get('password'))
instance.save()
return instance

View File

@ -11,26 +11,37 @@ logger = logging.getLogger(__name__)
@shared_task
def send_reset_password_email(request_id):
def send_reset_password_email(user_id, country_code):
"""Send email to user for reset password."""
try:
obj = models.ResetPasswordToken.objects.get(id=request_id)
user = obj.user
user = models.User.objects.get(id=user_id)
user.send_email(subject=_('Password resetting'),
message=obj.reset_password_template)
message=user.reset_password_template(country_code))
except:
logger.error(f'METHOD_NAME: {send_reset_password_email.__name__}\n'
f'DETAIL: Exception occurred for ResetPasswordToken instance: '
f'{request_id}')
f'DETAIL: Exception occurred for reset password: '
f'{user_id}')
@shared_task
def confirm_new_email_address(user_id):
def confirm_new_email_address(user_id, country_code):
"""Send email to user new email."""
try:
user = models.User.objects.get(id=user_id)
user.send_email(subject=_('Validate new email address'),
message=user.change_email_template)
message=user.confirm_email_template(country_code))
except:
logger.error(f'METHOD_NAME: {confirm_new_email_address.__name__}\n'
f'DETAIL: Exception occurred for user: {user_id}')
@shared_task
def change_email_address(user_id, country_code):
"""Send email to user new email."""
try:
user = models.User.objects.get(id=user_id)
user.send_email(subject=_('Validate new email address'),
message=user.change_email_template(country_code))
except:
logger.error(f'METHOD_NAME: {change_email_address.__name__}\n'
f'DETAIL: Exception occurred for user: {user_id}')

View File

@ -8,7 +8,5 @@ app_name = 'account'
urlpatterns = [
path('user/', views.UserRetrieveUpdateView.as_view(), name='user-retrieve-update'),
path('change-password/', views.ChangePasswordView.as_view(), name='change-password'),
path('change-email/confirm/<uidb64>/<token>/', views.ChangeEmailConfirmView.as_view(),
name='change-email-confirm'),
path('confirm-email/', views.ConfirmEmailView.as_view(), name='confirm-email'),
path('email/confirm/<uidb64>/<token>/', views.ConfirmEmailView.as_view(), name='confirm-email'),
]

View File

@ -8,10 +8,8 @@ app_name = 'account'
urlpatterns_api = [
path('reset-password/', views.PasswordResetView.as_view(), name='password-reset'),
path('form/reset-password/<uidb64>/<token>/', views.FormPasswordResetConfirmView.as_view(),
name='form-password-reset-confirm'),
path('form/reset-password/success/', views.FormPasswordResetSuccessView.as_view(),
name='form-password-reset-success'),
path('reset-password/confirm/<uidb64>/<token>/', views.PasswordResetConfirmView.as_view(),
name='password-reset-confirm'),
]
urlpatterns = urlpatterns_api + \

View File

@ -11,8 +11,7 @@ from account import models
from account.serializers import common as serializers
from utils import exceptions as utils_exceptions
from utils.models import GMTokenGenerator
from utils.views import (JWTUpdateAPIView,
JWTGenericViewMixin)
from utils.views import JWTGenericViewMixin
# User views
@ -25,7 +24,7 @@ class UserRetrieveUpdateView(generics.RetrieveUpdateAPIView):
return self.request.user
class ChangePasswordView(JWTUpdateAPIView):
class ChangePasswordView(generics.GenericAPIView):
"""Change password view"""
serializer_class = serializers.ChangePasswordSerializer
queryset = models.User.objects.active()
@ -39,13 +38,13 @@ class ChangePasswordView(JWTUpdateAPIView):
return Response(status=status.HTTP_200_OK)
class ConfirmEmailView(JWTGenericViewMixin):
class SendConfirmationEmailView(JWTGenericViewMixin):
"""Confirm email view."""
serializer_class = serializers.ConfirmEmailSerializer
queryset = models.User.objects.all()
def patch(self, request, *args, **kwargs):
"""Implement POST-method"""
"""Implement PATCH-method"""
# Get user instance
instance = self.request.user
@ -55,7 +54,7 @@ class ConfirmEmailView(JWTGenericViewMixin):
return Response(status=status.HTTP_200_OK)
class ChangeEmailConfirmView(JWTGenericViewMixin):
class ConfirmEmailView(JWTGenericViewMixin):
"""View for confirm changing email"""
permission_classes = (permissions.AllowAny,)
@ -68,12 +67,18 @@ class ChangeEmailConfirmView(JWTGenericViewMixin):
user_qs = models.User.objects.filter(pk=uid)
if user_qs.exists():
user = user_qs.first()
if not GMTokenGenerator(GMTokenGenerator.CHANGE_EMAIL).check_token(
if not GMTokenGenerator(GMTokenGenerator.CONFIRM_EMAIL).check_token(
user, token):
raise utils_exceptions.NotValidTokenError()
# Approve email status
user.confirm_email()
return Response(status=status.HTTP_200_OK)
# Create tokens
tokens = user.create_jwt_tokens()
return self._put_cookies_in_response(
cookies=self._put_data_in_cookies(
access_token=tokens.get('access_token'),
refresh_token=tokens.get('refresh_token')),
response=Response(status=status.HTTP_200_OK))
else:
raise utils_exceptions.UserNotFoundError()

View File

@ -1,46 +1,43 @@
"""Web account views"""
from django.conf import settings
from django.contrib.auth.tokens import default_token_generator
from django.core.exceptions import ValidationError
from django.http import HttpResponseRedirect
from django.contrib.auth.tokens import default_token_generator as password_token_generator
from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy
from django.utils.decorators import method_decorator
from django.utils.encoding import force_text
from django.utils.http import urlsafe_base64_decode
from django.utils.translation import gettext_lazy as _
from django.views.decorators.cache import never_cache
from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic.edit import FormView
from rest_framework import permissions
from rest_framework import status
from rest_framework import views
from rest_framework import permissions, status, generics
from rest_framework.response import Response
from account import models
from account.forms import SetPasswordForm
from account import tasks, models
from account.serializers import web as serializers
from utils import exceptions as utils_exceptions
from utils.models import GMTokenGenerator
from utils.views import (JWTCreateAPIView,
JWTGenericViewMixin)
from utils.views import JWTGenericViewMixin
class PasswordResetView(JWTCreateAPIView):
class PasswordResetView(generics.GenericAPIView):
"""View for resetting user password"""
permission_classes = (permissions.AllowAny, )
serializer_class = serializers.PasswordResetSerializer
queryset = models.ResetPasswordToken.objects.valid()
def post(self, request, *args, **kwargs):
"""Override create method"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
if serializer.validated_data.get('user'):
user = serializer.validated_data.pop('user')
if settings.USE_CELERY:
tasks.send_reset_password_email.delay(user_id=user.id,
country_code=self.request.country_code)
else:
tasks.send_reset_password_email(user_id=user.id,
country_code=self.request.country_code)
return Response(status=status.HTTP_200_OK)
class PasswordResetConfirmView(JWTGenericViewMixin):
"""View for confirmation new password"""
serializer_class = serializers.PasswordResetConfirmSerializer
permission_classes = (permissions.AllowAny,)
def get_queryset(self):
"""Override get_queryset method"""
return models.ResetPasswordToken.objects.valid()
queryset = models.User.objects.active()
def get_object(self):
"""Override get_object method
@ -51,128 +48,27 @@ class PasswordResetConfirmView(JWTGenericViewMixin):
user_id = force_text(urlsafe_base64_decode(uidb64))
token = self.kwargs.get('token')
filter_kwargs = {'key': token, 'user_id': user_id}
obj = get_object_or_404(queryset, **filter_kwargs)
obj = get_object_or_404(queryset, id=user_id)
if not GMTokenGenerator(GMTokenGenerator.RESET_PASSWORD).check_token(
user=obj.user, token=token):
raise utils_exceptions.NotValidAccessTokenError()
if not password_token_generator.check_token(user=obj, token=token):
raise utils_exceptions.NotValidTokenError()
# May raise a permission denied
self.check_object_permissions(self.request, obj)
return obj
def put(self, request, *args, **kwargs):
"""Implement PUT method"""
def patch(self, request, *args, **kwargs):
"""Implement PATCH method"""
instance = self.get_object()
serializer = self.get_serializer(instance=instance,
data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(status=status.HTTP_200_OK)
# Form view
class PasswordContextMixin:
extra_context = None
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'title': self.title,
**(self.extra_context or {})
})
return context
class FormPasswordResetSuccessView(views.APIView):
"""View for successful reset password"""
permission_classes = (permissions.AllowAny, )
def get(self, request, *args, **kwargs):
"""Implement GET-method"""
return Response(status=status.HTTP_200_OK)
class FormPasswordResetConfirmView(PasswordContextMixin, FormView):
INTERNAL_RESET_URL_TOKEN = 'set-password'
INTERNAL_RESET_SESSION_TOKEN = '_password_reset_token'
form_class = SetPasswordForm
post_reset_login = False
post_reset_login_backend = None
success_url = reverse_lazy('web:account:form-password-reset-success')
template_name = settings.CONFIRMATION_PASSWORD_RESET_TEMPLATE
title = _('Enter new password')
token_generator = default_token_generator
@method_decorator(sensitive_post_parameters())
@method_decorator(never_cache)
def dispatch(self, *args, **kwargs):
assert 'uidb64' in kwargs and 'token' in kwargs
self.validlink = False
self.user = self.get_user(kwargs['uidb64'])
if self.user is not None:
token = kwargs['token']
if token == self.INTERNAL_RESET_URL_TOKEN:
session_token = self.request.session.get(self.INTERNAL_RESET_SESSION_TOKEN)
if self.token_generator.check_token(self.user, session_token):
# If the token is valid, display the password reset form.
self.validlink = True
return super().dispatch(*args, **kwargs)
else:
if self.token_generator.check_token(self.user, token):
# Store the token in the session and redirect to the
# password reset form at a URL without the token. That
# avoids the possibility of leaking the token in the
# HTTP Referer header.
self.request.session[self.INTERNAL_RESET_SESSION_TOKEN] = token
redirect_url = self.request.path.replace(token, self.INTERNAL_RESET_URL_TOKEN)
return HttpResponseRedirect(redirect_url)
# Display the "Password reset unsuccessful" page.
return self.render_to_response(self.get_context_data())
def get_user(self, uidb64):
try:
# urlsafe_base64_decode() decodes to bytestring
uid = urlsafe_base64_decode(uidb64).decode()
user = models.User.objects.get(pk=uid)
except (TypeError, ValueError, OverflowError, models.User.DoesNotExist, ValidationError):
user = None
return user
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.user
return kwargs
def form_valid(self, form):
# Saving form
form.save()
user = form.user
# Expire user tokens
user.expire_access_tokens()
user.expire_refresh_tokens()
# Pop session token
del self.request.session[self.INTERNAL_RESET_SESSION_TOKEN]
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.validlink:
context['validlink'] = True
else:
context.update({
'form': None,
'title': _('Password reset unsuccessful'),
'validlink': False,
})
return context
# Create tokens
tokens = instance.create_jwt_tokens()
return self._put_cookies_in_response(
cookies=self._put_data_in_cookies(
access_token=tokens.get('access_token'),
refresh_token=tokens.get('refresh_token')),
response=Response(status=status.HTTP_200_OK))

View File

@ -38,8 +38,8 @@ class Application(PlatformMixin, AbstractApplication):
class JWTAccessTokenManager(models.Manager):
"""Manager for AccessToken model."""
def add_to_db(self, user, access_token: AccessToken,
refresh_token: RefreshToken):
def make(self, user, access_token: AccessToken, refresh_token: RefreshToken):
"""Create generated tokens to DB"""
refresh_token_qs = JWTRefreshToken.objects.filter(user=user,
jti=refresh_token.payload.get('jti'))
@ -106,18 +106,17 @@ class JWTAccessToken(ProjectBaseMixin):
class JWTRefreshTokenManager(models.Manager):
"""Manager for model RefreshToken."""
def add_to_db(self, user, token: RefreshToken, source: int):
"""Added generated refresh token to db"""
def make(self, user, token: RefreshToken, source: int):
"""Make method"""
jti = token[settings.SIMPLE_JWT.get('JTI_CLAIM')]
exp = token['exp']
obj = self.model(
user=user,
jti=jti,
source=source,
created_at=token.current_time,
expires_at=utils.datetime_from_epoch(exp),
)
expires_at=utils.datetime_from_epoch(exp))
if source:
obj.source = source
obj.save()
return obj

View File

@ -19,14 +19,9 @@ from utils.tokens import GMRefreshToken
class SignupSerializer(serializers.ModelSerializer):
"""Signup serializer serializer mixin"""
# REQUEST
username = serializers.CharField(
validators=(rest_validators.UniqueValidator(queryset=account_models.User.objects.all()),),
write_only=True
)
username = serializers.CharField(write_only=True)
password = serializers.CharField(write_only=True)
email = serializers.EmailField(
validators=(rest_validators.UniqueValidator(queryset=account_models.User.objects.all()),),
write_only=True)
email = serializers.EmailField(write_only=True)
newsletter = serializers.BooleanField(write_only=True)
class Meta:
@ -38,34 +33,46 @@ class SignupSerializer(serializers.ModelSerializer):
'newsletter'
)
def validate_username(self, data):
def validate_username(self, value):
"""Custom username validation"""
valid = utils_methods.username_validator(username=data)
valid = utils_methods.username_validator(username=value)
if not valid:
raise utils_exceptions.NotValidUsernameError()
return data
if account_models.User.objects.filter(username__icontains=value).exists():
raise serializers.ValidationError()
return value
def validate_password(self, data):
def validate_email(self, value):
"""Validate email"""
if account_models.User.objects.filter(email__icontains=value).exists():
raise serializers.ValidationError()
return value
def validate_password(self, value):
"""Custom password validation"""
try:
password_validators.validate_password(password=data)
password_validators.validate_password(password=value)
except serializers.ValidationError as e:
raise serializers.ValidationError(str(e))
else:
return data
return value
def create(self, validated_data):
"""Override create method"""
obj = account_models.User.objects.make(
username=validated_data.get('username'),
password=validated_data.get('password'),
email=validated_data.get('email'),
email=validated_data.get('email').lower(),
newsletter=validated_data.get('newsletter'))
# Send verification link on user email
if settings.USE_CELERY:
tasks.send_confirm_email.delay(obj.id)
tasks.send_confirm_email.delay(
user_id=obj.id,
country_code=self.context.get('request').country_code)
else:
tasks.send_confirm_email(obj.id)
tasks.send_confirm_email(
user_id=obj.id,
country_code=self.context.get('request').country_code)
return obj
@ -108,7 +115,7 @@ class LoginByUsernameOrEmailSerializer(SourceSerializerMixin,
authentication = authenticate(username=user.get_username(),
password=password)
if not authentication:
raise utils_exceptions.WrongAuthCredentials()
raise utils_exceptions.UserNotFoundError()
self.instance = user
return attrs
@ -129,14 +136,10 @@ class RefreshTokenSerializer(SourceSerializerMixin):
refresh_token = serializers.CharField(read_only=True)
access_token = serializers.CharField(read_only=True)
def get_request(self):
"""Return request"""
return self.context.get('request')
def validate(self, attrs):
"""Override validate method"""
cookie_refresh_token = self.get_request().COOKIES.get('refresh_token')
cookie_refresh_token = self.context.get('request').COOKIES.get('refresh_token')
# Check if refresh_token in COOKIES
if not cookie_refresh_token:
raise utils_exceptions.NotValidRefreshTokenError()

View File

@ -10,12 +10,12 @@ logger = logging.getLogger(__name__)
@shared_task
def send_confirm_email(user_id):
def send_confirm_email(user_id, country_code):
"""Send verification email to user."""
try:
obj = account_models.User.objects.get(id=user_id)
obj.send_email(subject=_('Email confirmation'),
message=obj.confirm_email_template)
message=obj.confirm_email_template(country_code))
except:
logger.error(f'METHOD_NAME: {send_confirm_email.__name__}\n'
f'DETAIL: Exception occurred for user: {user_id}')

View File

@ -29,7 +29,7 @@ urlpatterns_oauth2 = [
urlpatterns_jwt = [
path('signup/', views.SignUpView.as_view(), name='signup'),
path('signup/confirm/<uidb64>/<token>/', views.VerifyEmailConfirmView.as_view(),
path('signup/confirm/<uidb64>/<token>/', views.ConfirmationEmailView.as_view(),
name='signup-confirm'),
path('login/', views.LoginByUsernameOrEmailView.as_view(), name='login'),
path('logout/', views.LogoutView.as_view(), name="logout"),

View File

@ -24,13 +24,12 @@ from authorization.serializers import common as serializers
from utils import exceptions as utils_exceptions
from utils.models import GMTokenGenerator
from utils.permissions import IsAuthenticatedAndTokenIsValid
from utils.views import (JWTGenericViewMixin,
JWTCreateAPIView)
from utils.views import JWTGenericViewMixin
# Mixins
# JWTAuthView mixin
class JWTAuthViewMixin(JWTCreateAPIView):
class JWTAuthViewMixin(JWTGenericViewMixin):
"""Mixin for authentication views"""
def post(self, request, *args, **kwargs):
@ -151,7 +150,7 @@ class OAuth2SignUpView(OAuth2ViewMixin, JWTGenericViewMixin):
# JWT
# Sign in via username and password
class SignUpView(JWTCreateAPIView):
class SignUpView(generics.GenericAPIView):
"""View for classic signup"""
permission_classes = (permissions.AllowAny, )
serializer_class = serializers.SignupSerializer
@ -164,7 +163,7 @@ class SignUpView(JWTCreateAPIView):
return Response(status=status.HTTP_201_CREATED)
class VerifyEmailConfirmView(JWTGenericViewMixin):
class ConfirmationEmailView(JWTGenericViewMixin):
"""View for confirmation email"""
permission_classes = (permissions.AllowAny, )
@ -182,7 +181,15 @@ class VerifyEmailConfirmView(JWTGenericViewMixin):
raise utils_exceptions.NotValidTokenError()
# Approve email status
user.confirm_email()
return Response(status=status.HTTP_200_OK)
response = Response(status=status.HTTP_200_OK)
# Create tokens
tokens = user.create_jwt_tokens()
return self._put_cookies_in_response(
cookies=self._put_data_in_cookies(
access_token=tokens.get('access_token'),
refresh_token=tokens.get('refresh_token')),
response=response)
else:
raise utils_exceptions.UserNotFoundError()

View File

@ -9,7 +9,8 @@ class CommentSerializer(serializers.ModelSerializer):
nickname = serializers.CharField(read_only=True,
source='user.username')
is_mine = serializers.BooleanField(read_only=True)
profile_pic = serializers.SerializerMethodField()
profile_pic = serializers.URLField(read_only=True,
source='user.cropped_image_url')
class Meta:
"""Serializer for model Comment"""
@ -24,7 +25,3 @@ class CommentSerializer(serializers.ModelSerializer):
'nickname',
'profile_pic'
]
def get_profile_pic(self, obj):
"""Get profile picture URL"""
return obj.user.get_full_image_url(request=self.context.get('request'))

View File

@ -102,6 +102,20 @@ class EstablishmentQuerySet(models.QuerySet):
default=False,
output_field=models.BooleanField(default=False)))
def by_distance_from_point(self, center, radius, unit='m'):
"""
Returns nearest establishments
:param center: point from which to find nearby establishments
:param radius: the maximum distance within the radius of which to look for establishments
:return: all establishments within the specified radius of specified point
:param unit: length unit e.g. m, km. Default is 'm'.
"""
from django.contrib.gis.measure import Distance
kwargs = {unit: radius}
return self.filter(address__coordinates__distance_lte=(center, Distance(**kwargs)))
class Establishment(ProjectBaseMixin, ImageMixin, TranslatedFieldsMixin):
"""Establishment model."""

View File

@ -6,6 +6,7 @@ from comment.serializers import common as comment_serializers
from establishment import models
from favorites.models import Favorites
from location.serializers import AddressSerializer
from main.models import MetaDataContent
from main.serializers import MetaDataContentSerializer, AwardSerializer, CurrencySerializer
from review import models as review_models
from timetable.models import Timetable
@ -148,7 +149,7 @@ class EstablishmentBaseSerializer(serializers.ModelSerializer):
subtypes = EstablishmentSubTypeSerializer(many=True)
address = AddressSerializer()
tags = MetaDataContentSerializer(many=True)
preview_image = serializers.SerializerMethodField()
preview_image = serializers.ImageField(source='image')
class Meta:
"""Meta class."""
@ -338,3 +339,16 @@ class EstablishmentFavoritesCreateSerializer(serializers.ModelSerializer):
'content_object': validated_data.pop('establishment')
})
return super().create(validated_data)
class EstablishmentTagListSerializer(serializers.ModelSerializer):
"""List establishment tag serializer."""
label_translated = serializers.CharField(
source='metadata.label_translated', read_only=True, allow_null=True)
class Meta:
"""Meta class."""
model = MetaDataContent
fields = [
'label_translated',
]

View File

@ -5,9 +5,9 @@ from establishment import views
app_name = 'establishment'
urlpatterns = [
path('', views.EstablishmentListView.as_view(), name='list'),
path('tags/', views.EstablishmentTagListView.as_view(), name='tags'),
path('<int:pk>/', views.EstablishmentRetrieveView.as_view(), name='detail'),
path('<int:pk>/comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'),
path('<int:pk>/comments/create/', views.EstablishmentCommentCreateView.as_view(),
@ -15,5 +15,5 @@ urlpatterns = [
path('<int:pk>/comments/<int:comment_id>/', views.EstablishmentCommentRUDView.as_view(),
name='rud-comment'),
path('<int:pk>/favorites/', views.EstablishmentFavoritesCreateDestroyView.as_view(),
name='add-favorites'),
]
name='add-favorites')
]

View File

@ -0,0 +1,11 @@
"""Establishment url patterns."""
from django.urls import path
from establishment import views
from establishment.urls.common import urlpatterns as common_urlpatterns
urlpatterns = [
path('geo/', views.EstablishmentNearestRetrieveView.as_view(), name='nearest-establishments-list')
]
urlpatterns.extend(common_urlpatterns)

View File

@ -6,6 +6,7 @@ from rest_framework import generics, permissions
from comment import models as comment_models
from establishment import filters
from establishment import models, serializers
from main.models import MetaDataContent
from utils.views import JWTGenericViewMixin
from establishment.views import EstablishmentMixin
@ -108,3 +109,34 @@ class EstablishmentFavoritesCreateDestroyView(generics.CreateAPIView, generics.D
# May raise a permission denied
self.check_object_permissions(self.request, obj)
return obj
class EstablishmentNearestRetrieveView(EstablishmentMixin, JWTGenericViewMixin, generics.ListAPIView):
"""Resource for getting list of nearest establishments."""
serializer_class = serializers.EstablishmentListSerializer
filter_class = filters.EstablishmentFilter
def get_queryset(self):
"""Overrided method 'get_queryset'."""
from django.contrib.gis.geos import Point
center = Point(float(self.request.query_params["lat"]), float(self.request.query_params["lon"]))
radius = float(self.request.query_params["radius"])
unit = self.request.query_params.get("unit", None)
by_distance_from_point_kwargs = {"center": center, "radius": radius, "unit": unit}
return super(EstablishmentNearestRetrieveView, self).get_queryset() \
.by_distance_from_point(**{k: v for k, v in by_distance_from_point_kwargs.items() if v is not None}) \
.by_country_code(code=self.request.country_code) \
.annotate_in_favorites(user=self.request.user)
class EstablishmentTagListView(generics.ListAPIView):
"""List view for establishment tags."""
serializer_class = serializers.EstablishmentTagListSerializer
permission_classes = (permissions.AllowAny,)
pagination_class = None
def get_queryset(self):
"""Override get_queryset method"""
return MetaDataContent.objects.by_content_type(app_label='establishment',
model='establishment')

View File

@ -8,5 +8,5 @@ app_name = 'favorites'
urlpatterns = [
path('establishments/', views.FavoritesEstablishmentListView.as_view(),
name='establishment-list'),
path('<int:pk>/', views.FavoritesDestroyView.as_view(), name='remove-from-favorites'),
path('remove/<int:pk>/', views.FavoritesDestroyView.as_view(), name='remove-from-favorites'),
]

View File

@ -6,7 +6,7 @@ from . import models, serializers
class ImageUploadView(generics.CreateAPIView):
"""Upload image to gallery"""
permission_classes = (IsAuthenticatedAndTokenIsValid, )
model = models.Image
queryset = models.Image.objects.all()
serializer_class = serializers.ImageSerializer
permission_classes = (IsAuthenticatedAndTokenIsValid, )

View File

@ -1,8 +1,9 @@
"""News app common app."""
from rest_framework import generics, permissions
from news import filters, models
from news.serializers import common as serializers
from utils.views import JWTGenericViewMixin, JWTListAPIView
from utils.views import JWTGenericViewMixin
class NewsMixin:
@ -18,7 +19,7 @@ class NewsMixin:
.order_by('-is_highlighted', '-created')
class NewsListView(NewsMixin, JWTListAPIView):
class NewsListView(NewsMixin, generics.ListAPIView):
"""News list view."""
filter_class = filters.NewsListFilterSet

View File

@ -32,15 +32,6 @@ class UserNotFoundError(AuthErrorMixin, ProjectBaseException):
default_detail = _('User not found')
class PasswordRequestResetExists(ProjectBaseException):
"""
The exception should be thrown when request for reset password
is already exists and valid
"""
status_code = status.HTTP_400_BAD_REQUEST
default_detail = _('Password request is already exists. Please wait.')
class EmailSendingError(exceptions.APIException):
"""The exception should be thrown when unable to send an email"""
status_code = status.HTTP_400_BAD_REQUEST
@ -137,8 +128,17 @@ class WrongAuthCredentials(AuthErrorMixin):
class FavoritesError(exceptions.APIException):
"""
The exception should be thrown when you item that user
want add to favorites already exists.
The exception should be thrown when item that user
want to add to favorites is already exists.
"""
status_code = status.HTTP_400_BAD_REQUEST
default_detail = _('Item is already in favorites.')
class PasswordResetRequestExistedError(exceptions.APIException):
"""
The exception should be thrown when password reset request
already exists and valid.
"""
status_code = status.HTTP_400_BAD_REQUEST
default_detail = _('Password reset request is already exists and valid.')

View File

@ -32,3 +32,13 @@ def parse_cookies(get_response):
return response
return middleware
class CORSMiddleware:
"""Added parameter {Access-Control-Allow-Origin: *} to response"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
response["Access-Control-Allow-Origin"] = '*'
return response

View File

@ -33,12 +33,11 @@ class GMBlacklistMixin(BlacklistMixin):
"""
@classmethod
def for_user_by_source(cls, user, source: int):
def for_user(cls, user, source: int = None):
"""Create a refresh token."""
token = super().for_user(user)
token['user'] = user.get_user_info()
# Create a record in DB
JWTRefreshToken.objects.add_to_db(user=user, token=token, source=source)
JWTRefreshToken.objects.make(user=user, token=token, source=source)
return token
@ -70,7 +69,7 @@ class GMRefreshToken(GMBlacklistMixin, GMToken, RefreshToken):
# Create a record in DB
user = User.objects.get(id=self.payload.get('user_id'))
JWTAccessToken.objects.add_to_db(user=user,
access_token=access_token,
refresh_token=self)
JWTAccessToken.objects.make(user=user,
access_token=access_token,
refresh_token=self)
return access_token

View File

@ -7,7 +7,7 @@ from rest_framework.response import Response
# JWT
# Login base view mixin
# Login base view mixins
class JWTGenericViewMixin(generics.GenericAPIView):
"""JWT view mixin"""
@ -16,6 +16,13 @@ class JWTGenericViewMixin(generics.GenericAPIView):
REFRESH_TOKEN_HTTP_ONLY = False
REFRESH_TOKEN_SECURE = False
LOCALE_HTTP_ONLY = False
LOCALE_SECURE = False
COUNTRY_CODE_HTTP_ONLY = False
COUNTRY_CODE_SECURE = False
COOKIE = namedtuple('COOKIE', ['key', 'value', 'http_only', 'secure', 'max_age'])
def _put_data_in_cookies(self,
@ -26,21 +33,32 @@ class JWTGenericViewMixin(generics.GenericAPIView):
cookies it is list that contain namedtuples
cookies would contain key, value and secure parameters.
"""
COOKIES = list()
COOKIES = []
# Write to cookie access and refresh token with secure flag
if access_token and refresh_token:
_access_token = self.COOKIE(key='access_token',
value=access_token,
http_only=self.ACCESS_TOKEN_HTTP_ONLY,
secure=self.ACCESS_TOKEN_SECURE,
max_age=settings.COOKIES_MAX_AGE if permanent else None)
_refresh_token = self.COOKIE(key='refresh_token',
value=refresh_token,
http_only=self.REFRESH_TOKEN_HTTP_ONLY,
secure=self.REFRESH_TOKEN_SECURE,
max_age=settings.COOKIES_MAX_AGE if permanent else None)
COOKIES.extend((_access_token, _refresh_token))
if hasattr(self.request, 'locale'):
COOKIES.append(self.COOKIE(key='locale',
value=self.request.locale,
http_only=self.ACCESS_TOKEN_HTTP_ONLY,
secure=self.LOCALE_SECURE,
max_age=settings.COOKIES_MAX_AGE if permanent else None))
if hasattr(self.request, 'country_code'):
COOKIES.append(self.COOKIE(key='country_code',
value=self.request.country_code,
http_only=self.COUNTRY_CODE_HTTP_ONLY,
secure=self.COUNTRY_CODE_SECURE,
max_age=settings.COOKIES_MAX_AGE if permanent else None))
if access_token:
COOKIES.append(self.COOKIE(key='access_token',
value=access_token,
http_only=self.ACCESS_TOKEN_HTTP_ONLY,
secure=self.ACCESS_TOKEN_SECURE,
max_age=settings.COOKIES_MAX_AGE if permanent else None))
if refresh_token:
COOKIES.append(self.COOKIE(key='refresh_token',
value=refresh_token,
http_only=self.REFRESH_TOKEN_HTTP_ONLY,
secure=self.REFRESH_TOKEN_SECURE,
max_age=settings.COOKIES_MAX_AGE if permanent else None))
return COOKIES
def _put_cookies_in_response(self, cookies: list, response: Response):
@ -77,102 +95,3 @@ class JWTGenericViewMixin(generics.GenericAPIView):
http_only=self.REFRESH_TOKEN_HTTP_ONLY,
secure=self.REFRESH_TOKEN_SECURE,
max_age=_cookies.get('max_age'))]
class JWTListAPIView(JWTGenericViewMixin, generics.ListAPIView):
"""
Concrete view for creating a model instance.
"""
def get(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
response = self.get_paginated_response(serializer.data)
else:
serializer = self.get_serializer(queryset, many=True)
response = Response(serializer.data)
access_token, refresh_token = self._get_tokens_from_cookies(request)
return self._put_cookies_in_response(
cookies=self._put_data_in_cookies(access_token=access_token.value,
refresh_token=refresh_token.value),
response=response)
class JWTCreateAPIView(JWTGenericViewMixin, generics.CreateAPIView):
"""
Concrete view for creating a model instance.
"""
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
response = Response(serializer.data, status=status.HTTP_201_CREATED)
access_token, refresh_token = self._get_tokens_from_cookies(request)
return self._put_cookies_in_response(
cookies=self._put_data_in_cookies(access_token=access_token.value,
refresh_token=refresh_token.value),
response=response)
class JWTRetrieveAPIView(JWTGenericViewMixin, generics.RetrieveAPIView):
"""
Concrete view for retrieving a model instance.
"""
def get(self, request, *args, **kwargs):
"""Implement GET method"""
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
response = self.get_paginated_response(serializer.data)
else:
serializer = self.get_serializer(queryset, many=True)
response = Response(serializer.data, status.HTTP_200_OK)
access_token, refresh_token = self._get_tokens_from_cookies(request)
return self._put_cookies_in_response(
cookies=self._put_data_in_cookies(access_token=access_token,
refresh_token=refresh_token),
response=response)
class JWTDestroyAPIView(JWTGenericViewMixin, generics.DestroyAPIView):
"""
Concrete view for deleting a model instance.
"""
def delete(self, request, *args, **kwargs):
instance = self.get_object()
instance.delete()
response = Response(status=status.HTTP_204_NO_CONTENT)
access_token, refresh_token = self._get_tokens_from_cookies(request)
return self._put_cookies_in_response(
cookies=self._put_data_in_cookies(access_token=access_token,
refresh_token=refresh_token),
response=response)
class JWTUpdateAPIView(JWTGenericViewMixin, generics.UpdateAPIView):
"""
Concrete view for updating a model instance.
"""
def put(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
serializer.save()
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
response = Response(serializer.data)
access_token, refresh_token = self._get_tokens_from_cookies(request)
return self._put_cookies_in_response(
cookies=self._put_data_in_cookies(access_token=access_token,
refresh_token=refresh_token),
response=response)
def patch(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.put(request, *args, **kwargs)

View File

@ -79,7 +79,6 @@ EXTERNAL_APPS = [
'rest_framework',
'rest_framework.authtoken',
'easy_select2',
'corsheaders',
'oauth2_provider',
'social_django',
'rest_framework_social_oauth2',
@ -87,6 +86,7 @@ EXTERNAL_APPS = [
'rest_framework_simplejwt.token_blacklist',
'solo',
'phonenumber_field',
'corsheaders',
]
@ -333,10 +333,6 @@ THUMBNAIL_ALIASES = {
# Password reset
RESETTING_TOKEN_EXPIRATION = 24 # hours
# CORS Config
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True
GEOIP_PATH = os.path.join(PROJECT_ROOT, 'geoip_db')
@ -373,7 +369,6 @@ PASSWORD_RESET_TIMEOUT_DAYS = 1
# TEMPLATES
CONFIRMATION_PASSWORD_RESET_TEMPLATE = 'account/password_reset_confirm.html'
RESETTING_TOKEN_TEMPLATE = 'account/password_reset_email.html'
CHANGE_EMAIL_TEMPLATE = 'account/change_email.html'
CONFIRM_EMAIL_TEMPLATE = 'authorization/confirm_email.html'
@ -381,6 +376,12 @@ CONFIRM_EMAIL_TEMPLATE = 'authorization/confirm_email.html'
# COOKIES
COOKIES_MAX_AGE = 86400 # 24 hours
SESSION_COOKIE_SAMESITE = None
# CORS
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True
# UPLOAD FILES

13
project/settings/stage.py Normal file
View File

@ -0,0 +1,13 @@
"""Stage settings."""
from .base import *
ALLOWED_HOSTS = ['gm-stage.id-east.ru', '95.213.204.126']
SEND_SMS = False
SMS_CODE_SHOW = True
USE_CELERY = False
SCHEMA_URI = 'https'
DEFAULT_SUBDOMAIN = 'www'
SITE_DOMAIN_URI = 'id-east.ru'
DOMAIN_URI = 'gm-stage.id-east.ru'

View File

@ -2,9 +2,8 @@
{% blocktrans %}You're receiving this email because you want to change email address at {{ site_name }}.{% endblocktrans %}
{% trans "Please go to the following page for confirmation new email address:" %}
{% block reset_link %}
http://{{ domain_uri }}{% url 'web:account:change-email-confirm' uidb64=uidb64 token=token %}
{% endblock %}
<a href="https://{{ country_code }}.{{ domain_uri }}/registered/{{ uidb64 }}/{{ token }}/">https://{{ country_code }}.{{ domain_uri }}/registered/{{ uidb64 }}/{{ token }}/</a>
{% trans "Thanks for using our site!" %}

View File

@ -1,31 +0,0 @@
{% load i18n static %}
{% block content %}
{% if validlink %}
<p>{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}</p>
<form method="post">{% csrf_token %}
<fieldset class="module aligned">
<div class="form-row field-password1">
{{ form.new_password1.errors }}
<label for="id_new_password1">{% trans 'New password:' %}</label>
{{ form.new_password1 }}
</div>
<div class="form-row field-password2">
{{ form.new_password2.errors }}
<label for="id_new_password2">{% trans 'Confirm password:' %}</label>
{{ form.new_password2 }}
</div>
<input type="submit" value="{% trans 'Change my password' %}">
</fieldset>
</form>
{% else %}
<p>{% trans "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %}</p>
{% endif %}
{% endblock %}

View File

@ -2,9 +2,8 @@
{% blocktrans %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %}
{% trans "Please go to the following page and choose a new password:" %}
{% block reset_link %}
http://{{ domain_uri }}{% url 'web:account:form-password-reset-confirm' uidb64=uidb64 token=token %}
{% endblock %}
<a href="https://{{ country_code }}.{{ domain_uri }}/recovery/{{ uidb64 }}/{{ token }}/">https://{{ country_code }}.{{ domain_uri }}/recovery/{{ uidb64 }}/{{ token }}/</a>
{% trans "Thanks for using our site!" %}

View File

@ -2,9 +2,8 @@
{% blocktrans %}You're receiving this email because you trying to register new account at {{ site_name }}.{% endblocktrans %}
{% trans "Please confirm your email address to complete the registration:" %}
{% block signup_confirm %}
http://{{ domain_uri }}{% url 'auth:signup-confirm' uidb64=uidb64 token=token %}
{% endblock %}
<a href="https://{{ country_code }}.{{ domain_uri }}/registered/{{ uidb64 }}/{{ token }}/">https://{{ country_code }}.{{ domain_uri }}/registered/{{ uidb64 }}/{{ token }}/</a>
{% trans "Thanks for using our site!" %}

View File

@ -3,10 +3,11 @@ from django.urls import path, include
app_name = 'mobile'
urlpatterns = [
path('establishments/', include('establishment.urls.mobile')),
# path('account/', include('account.urls.web')),
# path('advertisement/', include('advertisement.urls.web')),
# path('collection/', include('collection.urls.web')),
# path('establishments/', include('establishment.urls.web')),
# path('news/', include('news.urls.web')),
# path('partner/', include('partner.urls.web')),
]
]