refactored apps account, authorization, news, utils

This commit is contained in:
Anatoly 2019-09-12 14:44:49 +03:00
parent ec2e87fe1a
commit 075298f6ec
19 changed files with 165 additions and 521 deletions

View File

@ -46,12 +46,3 @@ class UserAdmin(BaseUserAdmin):
return obj.get_short_name()
short_name.short_description = _('Name')
@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', )

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

@ -147,120 +147,43 @@ 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):
"""Get base64 value for user by primary key identifier"""
return urlsafe_base64_encode(force_bytes(self.pk))
def confirm_email_template(self, country_code):
"""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,
'country_code': country_code})
@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)
@property
def get_resetting_token_expiration(self):
"""Get resetting token expiration"""
return settings.RESETTING_TOKEN_EXPIRATION
@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)
def base_template(self):
"""Base email template"""
return {'domain_uri': settings.DOMAIN_URI,
'uidb64': self.get_user_uidb64,
'site_name': settings.SITE_NAME}
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,
'country_code': country_code})
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

@ -48,7 +48,7 @@ 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):
@ -58,24 +58,21 @@ class UserSerializer(serializers.ModelSerializer):
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
@ -163,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,27 +1,18 @@
"""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"""
@ -30,41 +21,29 @@ class PasswordResetSerializer(serializers.ModelSerializer):
def validate(self, attrs):
"""Override validate method"""
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 in request body.'))
# Check user in DB
raise serializers.ValidationError(_('username or email not in request body.'))
filters = {}
if username_validator(username_or_email):
filters.update({'username': username_or_email})
filters.update({'username__icontains': username_or_email})
else:
filters.update({'email': username_or_email.lower()})
user_qs = models.User.objects.filter(**filters)
if user_qs.exists() and filters:
attrs['user'] = user_qs.first()
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.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(request_id=obj.id,
country_code=self.request.country_code)
else:
tasks.send_reset_password_email(request_id=obj.id,
country_code=self.request.country_code)
return obj
class PasswordResetConfirmSerializer(serializers.ModelSerializer):
"""Serializer for model User"""
@ -73,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, country_code):
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(country_code))
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

@ -6,14 +6,12 @@ from rest_framework import generics
from rest_framework import permissions
from rest_framework import status
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
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
@ -26,53 +24,27 @@ 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()
permission_classes = (permissions.AllowAny, )
def get_object(self):
"""Overridden get_object method."""
if not self.request.user.is_authenticated:
uidb64 = self.kwargs.get('uidb64')
user_id = force_text(urlsafe_base64_decode(uidb64))
token = self.kwargs.get('token')
filter_kwargs = {'key': token, 'user_id': user_id}
password_reset_obj = get_object_or_404(models.ResetPasswordToken.objects.valid(),
**filter_kwargs)
if not GMTokenGenerator(GMTokenGenerator.RESET_PASSWORD).check_token(
user=password_reset_obj.user, token=token):
raise utils_exceptions.NotValidAccessTokenError()
if not password_reset_obj.user.is_active:
raise utils_exceptions.UserNotFoundError()
obj = password_reset_obj.user
else:
obj = self.request.user
# May raise a permission denied
self.check_object_permissions(self.request, obj)
return obj
def patch(self, request, *args, **kwargs):
"""Implement PUT method"""
instance = self.get_object()
serializer = self.get_serializer(instance=instance,
serializer = self.get_serializer(instance=self.request.user,
data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
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
@ -82,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,)
@ -95,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,42 +1,35 @@
"""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 JWTGenericViewMixin
class PasswordResetView(JWTGenericViewMixin):
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'):
serializer.save()
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)
@ -44,10 +37,7 @@ 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
@ -58,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

@ -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, )

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
@ -142,3 +133,12 @@ class FavoritesError(exceptions.APIException):
"""
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

@ -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"""
@ -95,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

@ -369,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'

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,7 +2,9 @@
{% 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:" %}
<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!" %}
{% blocktrans %}The {{ site_name }} team{% endblocktrans %}