version 0.0.11: added new model and CRUD endpoints to them; refactored auth app

This commit is contained in:
Anatoly 2019-08-15 11:59:10 +03:00
parent 033674c5b2
commit d5a14ef8c2
21 changed files with 438 additions and 157 deletions

View File

@ -40,12 +40,12 @@ class UserQuerySet(models.QuerySet):
"""Filter only active users.""" """Filter only active users."""
return self.filter(is_active=switcher) return self.filter(is_active=switcher)
def by_access_token(self, token): def by_oauth2_access_token(self, token):
"""Find user by access token""" """Find user by access token"""
return self.filter(oauth2_provider_accesstoken__token=token, return self.filter(oauth2_provider_accesstoken__token=token,
oauth2_provider_accesstoken__expires__gt=timezone.now()) oauth2_provider_accesstoken__expires__gt=timezone.now())
def by_refresh_token(self, token): def by_oauth2_refresh_token(self, token):
"""Find user by access token""" """Find user by access token"""
return self.filter(oauth2_provider_refreshtoken__token=token, return self.filter(oauth2_provider_refreshtoken__token=token,
oauth2_provider_refreshtoken__expires__gt=timezone.now()) oauth2_provider_refreshtoken__expires__gt=timezone.now())

View File

@ -1,3 +1,7 @@
from django.contrib import admin from django.contrib import admin
from authorization import models
# Register your models here.
@admin.register(models.BlacklistedAccessToken)
class BlacklistedAccessTokenAdmin(admin.ModelAdmin):
"""Admin for BlackListedAccessToken"""

View File

@ -24,7 +24,7 @@ class BaseAuthSerializerMixin(serializers.Serializer):
source = serializers.ChoiceField(choices=Application.SOURCES) source = serializers.ChoiceField(choices=Application.SOURCES)
class JWTBaseMixin(serializers.Serializer): class JWTBaseSerializerMixin(serializers.Serializer):
""" """
Mixin for JWT authentication. Mixin for JWT authentication.
Uses in serializers when need give in response access and refresh token Uses in serializers when need give in response access and refresh token
@ -43,8 +43,8 @@ class JWTBaseMixin(serializers.Serializer):
def to_representation(self, instance): def to_representation(self, instance):
"""Override to_representation method""" """Override to_representation method"""
token = self.get_token() token = self.get_token()
setattr(instance, 'refresh_token', str(token))
setattr(instance, 'access_token', str(token.access_token)) setattr(instance, 'access_token', str(token.access_token))
setattr(instance, 'refresh_token', str(token))
return super().to_representation(instance) return super().to_representation(instance)
@ -60,7 +60,7 @@ class ClassicAuthSerializerMixin(BaseAuthSerializerMixin):
# Serializers # Serializers
class SignupSerializer(JWTBaseMixin, serializers.ModelSerializer): class SignupSerializer(JWTBaseSerializerMixin, serializers.ModelSerializer):
"""Signup serializer serializer mixin""" """Signup serializer serializer mixin"""
# REQUEST # REQUEST
username = serializers.CharField( username = serializers.CharField(
@ -100,7 +100,7 @@ class SignupSerializer(JWTBaseMixin, serializers.ModelSerializer):
return obj return obj
class LoginByUsernameOrEmailSerializer(JWTBaseMixin, serializers.ModelSerializer): class LoginByUsernameOrEmailSerializer(JWTBaseSerializerMixin, serializers.ModelSerializer):
"""Serializer for login user""" """Serializer for login user"""
username_or_email = serializers.CharField(write_only=True) username_or_email = serializers.CharField(write_only=True)
password = serializers.CharField(write_only=True) password = serializers.CharField(write_only=True)
@ -164,22 +164,28 @@ class LogoutSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = BlacklistedAccessToken model = BlacklistedAccessToken
fields = '__all__' fields = (
read_only_fields = [ 'user',
'jti', 'token', 'user' 'token',
] 'jti'
)
read_only_fields = (
'user',
'token',
'jti'
)
def create(self, validated_data, *args, **kwargs): def validate(self, attrs):
"""Override create method""" """Override validated data"""
request = self.context.get('request') request = self.context.get('request')
token = request._request.headers.get('Authorization') \ token = request._request.headers.get('Authorization') \
.split(' ')[::-1][0] .split(' ')[::-1][0]
access_token = tokens.AccessToken(token) access_token = tokens.AccessToken(token)
# Prepare validated data # Prepare validated data
validated_data['user'] = request.user attrs['user'] = request.user
validated_data['token'] = access_token.token attrs['token'] = access_token.token
validated_data['jti'] = access_token.payload.get('jti') attrs['jti'] = access_token.payload.get('jti')
return super().create(validated_data) return attrs
# OAuth # OAuth

View File

@ -1,6 +1,5 @@
"""Common views for application Account""" """Common views for application Account"""
import json import json
from collections import namedtuple
from braces.views import CsrfExemptMixin from braces.views import CsrfExemptMixin
from django.conf import settings from django.conf import settings
@ -20,12 +19,36 @@ from account.models import User
from authorization.models import Application from authorization.models import Application
from authorization.serializers import common as serializers from authorization.serializers import common as serializers
from utils import exceptions as utils_exceptions from utils import exceptions as utils_exceptions
from utils.views import JWTViewMixin
# JWT
# Mixins # Mixins
# JWTAuthView mixin
class JWTAuthViewMixin(JWTViewMixin):
"""Mixin for authentication views"""
def post(self, request, *args, **kwargs):
"""Implement POST method"""
_locale = request.COOKIES.get('locale')
try:
locale = self._check_locale(locale=_locale)
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
response = Response(serializer.data, status=status.HTTP_200_OK)
access_token = serializer.data.get('access_token')
refresh_token = serializer.data.get('refresh_token')
except utils_exceptions.LocaleNotExisted:
raise utils_exceptions.LocaleNotExisted(locale=_locale)
else:
return self._put_cookies_in_response(
cookies=self._put_data_in_cookies(locale=locale,
access_token=access_token,
refresh_token=refresh_token),
response=response)
# OAuth2 # OAuth2
class BaseOAuth2ViewMixin(generics.GenericAPIView): class BaseOAuth2ViewMixin(generics.GenericAPIView):
"""BaseMixin for classic auth views""" """BaseMixin for classic auth views"""
@ -74,53 +97,8 @@ class OAuth2ViewMixin(CsrfExemptMixin, OAuthLibMixin, BaseOAuth2ViewMixin):
raise utils_exceptions.ServiceError() raise utils_exceptions.ServiceError()
# JWT
# Login base view mixin
class JWTViewMixin(generics.GenericAPIView):
"""JWT view mixin"""
def _handle_cookies(self, request, access_token, refresh_token):
"""
CHECK locale in cookies and PUT access and refresh tokens there.
cookies it is list that contain namedtuples
cookies would contain key, value and secure parameters.
"""
cookies = list()
COOKIE = namedtuple('COOKIE', ['key', 'value', 'secure'])
if 'locale' in request.COOKIES:
# Write locale in cookie
_locale = COOKIE(key='locale', value=request.COOKIES.get('locale'), secure=False)
cookies.append(_locale)
# Write to cookie access and refresh token with secure flag
_access_token = COOKIE(key='access_token', value=access_token, secure=True)
_refresh_token = COOKIE(key='refresh_token', value=refresh_token, secure=True)
cookies.extend([_access_token, _refresh_token])
return cookies
def _put_cookies_in_response(self, cookies: list, response: Response):
"""Update COOKIES in response obj"""
for cookie in cookies:
response.set_cookie(key=cookie.key,
value=cookie.value,
secure=cookie.secure)
return response
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
response = Response(serializer.data, status=status.HTTP_200_OK)
access_token = serializer.data.get('access_token')
refresh_token = serializer.data.get('access_token')
return self._put_cookies_in_response(
cookies=self._handle_cookies(request, access_token, refresh_token),
response=response)
# Serializers
# Sign in via Facebook # Sign in via Facebook
class OAuth2SignUpView(OAuth2ViewMixin, JWTViewMixin): class OAuth2SignUpView(OAuth2ViewMixin, JWTAuthViewMixin):
""" """
Implements an endpoint to convert a provider token to an access token Implements an endpoint to convert a provider token to an access token
@ -135,19 +113,22 @@ class OAuth2SignUpView(OAuth2ViewMixin, JWTViewMixin):
serializer_class = serializers.OAuth2Serialzier serializer_class = serializers.OAuth2Serialzier
def get_jwt_token(self, user: User, def get_jwt_token(self, user: User,
access_token: str, oauth2_access_token: str,
refresh_token: str): oauth2_refresh_token: str):
"""Get JWT token""" """Get JWT token"""
token = jwt_tokens.RefreshToken.for_user(user) token = jwt_tokens.RefreshToken.for_user(user)
# Adding additional information about user to payload # Adding additional information about user to payload
token['user'] = user.get_user_info() token['user'] = user.get_user_info()
# Adding OAuth2 tokens to payloads # Adding OAuth2 tokens to payloads
token['oauth2_fb'] = {'access_token': access_token, token['oauth2_fb'] = {'access_token': oauth2_access_token,
'refresh_token': refresh_token} 'refresh_token': oauth2_refresh_token}
return token return token
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
_locale = request.COOKIES.get('locale')
try:
locale = self._check_locale(locale=_locale)
# Preparing request data # Preparing request data
serializer = self.get_serializer(data=request.data) serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
@ -161,70 +142,83 @@ class OAuth2SignUpView(OAuth2ViewMixin, JWTViewMixin):
for key, value in request_data.items(): for key, value in request_data.items():
request._request.POST[key] = value request._request.POST[key] = value
# OAuth2 authentication process
url, headers, body, oauth2_status = self.create_token_response(request._request) url, headers, body, oauth2_status = self.create_token_response(request._request)
body = json.loads(body) body = json.loads(body)
# Get JWT token # Get JWT token
if oauth2_status != status.HTTP_200_OK: if oauth2_status != status.HTTP_200_OK:
raise ValueError('status isn\'t 200') raise ValueError('status isn\'t 200')
user = User.objects.by_access_token(token=body.get('access_token'))\
# Get authenticated user
user = User.objects.by_oauth2_access_token(token=body.get('access_token'))\
.first() .first()
# Create JWT token and put oauth2 token (access, refresh tokens) in payload
token = self.get_jwt_token(user=user, token = self.get_jwt_token(user=user,
access_token=body.get('access_token'), oauth2_access_token=body.get('access_token'),
refresh_token=body.get('refresh_token')) oauth2_refresh_token=body.get('refresh_token'))
refresh_token = str(token)
access_token = str(token.access_token) access_token = str(token.access_token)
response = Response(data={'refresh_token': refresh_token, refresh_token = str(token)
'access_token': access_token}, response = Response(data={'access_token': access_token,
'refresh_token': refresh_token},
status=status.HTTP_200_OK) status=status.HTTP_200_OK)
except utils_exceptions.LocaleNotExisted:
raise utils_exceptions.LocaleNotExisted(locale=_locale)
else:
return self._put_cookies_in_response( return self._put_cookies_in_response(
cookies=self._handle_cookies(request, access_token, refresh_token), cookies=self._put_data_in_cookies(locale=locale,
access_token=access_token,
refresh_token=refresh_token),
response=response) response=response)
# JWT # JWT
# Sign in via username and password # Sign in via username and password
class SignUpView(JWTViewMixin): class SignUpView(JWTAuthViewMixin):
"""View for classic signup""" """View for classic signup"""
permission_classes = (permissions.AllowAny, ) permission_classes = (permissions.AllowAny, )
serializer_class = serializers.SignupSerializer serializer_class = serializers.SignupSerializer
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
"""Implement POST-method"""
_locale = request.COOKIES.get('locale')
try:
locale = self._check_locale(locale=_locale)
serializer = self.get_serializer(data=request.data) serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
serializer.save() serializer.save()
response = Response(serializer.data, status=status.HTTP_201_CREATED) response = Response(serializer.data, status=status.HTTP_201_CREATED)
access_token = serializer.data.get('access_token') access_token = serializer.data.get('access_token')
refresh_token = serializer.data.get('access_token') refresh_token = serializer.data.get('refresh_token')
except utils_exceptions.LocaleNotExisted:
raise utils_exceptions.LocaleNotExisted(locale=_locale)
else:
return self._put_cookies_in_response( return self._put_cookies_in_response(
cookies=self._handle_cookies(request, access_token, refresh_token), cookies=self._put_data_in_cookies(
locale=locale,
access_token=access_token,
refresh_token=refresh_token),
response=response) response=response)
# Login by username|email + password # Login by username|email + password
class LoginByUsernameOrEmailView(JWTViewMixin): class LoginByUsernameOrEmailView(JWTAuthViewMixin):
"""Login by email and password""" """Login by email and password"""
permission_classes = (permissions.AllowAny,) permission_classes = (permissions.AllowAny,)
serializer_class = serializers.LoginByUsernameOrEmailSerializer serializer_class = serializers.LoginByUsernameOrEmailSerializer
# Refresh access_token # Refresh access_token
class RefreshTokenView(JWTViewMixin): class RefreshTokenView(JWTAuthViewMixin):
"""Refresh access_token""" """Refresh access_token"""
permission_classes = (permissions.IsAuthenticated,) permission_classes = (permissions.IsAuthenticated,)
serializer_class = serializers.RefreshTokenSerializer serializer_class = serializers.RefreshTokenSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
response = Response(serializer.validated_data, status=status.HTTP_200_OK)
access_token = serializer.data.get('access_token')
refresh_token = serializer.data.get('access_token')
return self._put_cookies_in_response(
cookies=self._handle_cookies(request, access_token, refresh_token),
response=response)
# Logout # Logout
class LogoutView(generics.CreateAPIView): class LogoutView(generics.CreateAPIView):

View File

@ -1,5 +1,6 @@
from rest_framework import serializers from rest_framework import serializers
from location import models from location import models
from django.contrib.gis.geos import Point
class CountrySerializer(serializers.ModelSerializer): class CountrySerializer(serializers.ModelSerializer):
@ -48,8 +49,8 @@ class CitySerializer(serializers.ModelSerializer):
class AddressSerializer(serializers.ModelSerializer): class AddressSerializer(serializers.ModelSerializer):
"""Address serializer.""" """Address serializer."""
city = CitySerializer() city = CitySerializer()
longitude = serializers.DecimalField(max_digits=10, decimal_places=6) geo_lon = serializers.FloatField(allow_null=True)
latitude = serializers.DecimalField(max_digits=10, decimal_places=6) geo_lat = serializers.FloatField(allow_null=True)
class Meta: class Meta:
model = models.Address model = models.Address
@ -59,14 +60,26 @@ class AddressSerializer(serializers.ModelSerializer):
'street_name_2', 'street_name_2',
'number', 'number',
'postal_code', 'postal_code',
'longitude', 'geo_lon',
'latitude' 'geo_lat'
] ]
# def validate(self, attrs): def validate(self, attrs):
# geo_lat = attrs.pop('geo_lat') if 'geo_lat' in attrs else None # if geo_lat and geo_lon was sent
# geo_lon = attrs.pop('geo_lon') if 'geo_lon' in attrs else None geo_lat = attrs.pop('geo_lat') if 'geo_lat' in attrs else None
# if geo_lat and geo_lon: geo_lon = attrs.pop('geo_lon') if 'geo_lon' in attrs else None
# # Point(longitude, latitude) if geo_lat and geo_lon:
# attrs['coordinates'] = Point(geo_lon, geo_lat) # Point(longitude, latitude)
# return attrs attrs['location'] = Point(geo_lat, geo_lon)
return attrs
def to_representation(self, instance):
"""Override to_representation method"""
if instance.coordinates and isinstance(instance.coordinates, Point):
# Point(longitude, latitude)
setattr(instance, 'geo_lat', instance.coordinates.x)
setattr(instance, 'geo_lon', instance.coordinates.y)
else:
setattr(instance, 'geo_lat', float(0))
setattr(instance, 'geo_lon', float(0))
return super().to_representation(instance)

View File

@ -6,27 +6,27 @@ from location import views
app_name = 'location' app_name = 'location'
urlpatterns = [ urlpatterns = [
path('country/list/', views.CountryListView.as_view(), name='country-list'), path('country/', views.CountryListView.as_view(), name='country_list'),
path('country/create/', views.CountryCreateView.as_view(), name='country-create'), path('country/create/', views.CountryCreateView.as_view(), name='country_create'),
path('country/<int:pk>/detail/', views.CountryRetrieveView.as_view(), name='country-retrieve'), path('country/<int:pk>/', views.CountryRetrieveView.as_view(), name='country_retrieve'),
path('country/<int:pk>/delete/', views.CountryDestroyView.as_view(), name='country-destroy'), path('country/<int:pk>/delete/', views.CountryDestroyView.as_view(), name='country_destroy'),
path('country/<int:pk>/update/', views.CountryUpdateView.as_view(), name='country-update'), path('country/<int:pk>/update/', views.CountryUpdateView.as_view(), name='country_update'),
path('region/list/', views.RegionListView.as_view(), name='region-list'), path('region/', views.RegionListView.as_view(), name='region_list'),
path('region/create/', views.RegionCreateView.as_view(), name='region-create'), path('region/create/', views.RegionCreateView.as_view(), name='region_create'),
path('region/<int:pk>/detail/', views.RegionRetrieveView.as_view(), name='region-retrieve'), path('region/<int:pk>/', views.RegionRetrieveView.as_view(), name='region_retrieve'),
path('region/<int:pk>/delete/', views.RegionDestroyView.as_view(), name='region-destroy'), path('region/<int:pk>/delete/', views.RegionDestroyView.as_view(), name='region_destroy'),
path('region/<int:pk>/update/', views.RegionUpdateView.as_view(), name='region-update'), path('region/<int:pk>/update/', views.RegionUpdateView.as_view(), name='region_update'),
path('city/list/', views.CityListView.as_view(), name='city-list'), path('city/', views.CityListView.as_view(), name='city_list'),
path('city/create/', views.CityCreateView.as_view(), name='city-create'), path('city/create/', views.CityCreateView.as_view(), name='city_create'),
path('city/<int:pk>/detail/', views.CityRetrieveView.as_view(), name='city-retrieve'), path('city/<int:pk>/', views.CityRetrieveView.as_view(), name='city_retrieve'),
path('city/<int:pk>/delete/', views.CityDestroyView.as_view(), name='city-destroy'), path('city/<int:pk>/delete/', views.CityDestroyView.as_view(), name='city_destroy'),
path('city/<int:pk>/update/', views.CityUpdateView.as_view(), name='city-update'), path('city/<int:pk>/update/', views.CityUpdateView.as_view(), name='city_update'),
path('address/list/', views.AddressListView.as_view(), name='address-list'), path('address/', views.AddressListView.as_view(), name='address_list'),
path('address/create/', views.AddressCreateView.as_view(), name='address-create'), path('address/create/', views.AddressCreateView.as_view(), name='address_create'),
path('address/<int:pk>/detail/', views.AddressRetrieveView.as_view(), name='address-retrieve'), path('address/<int:pk>/', views.AddressRetrieveView.as_view(), name='address_retrieve'),
path('address/<int:pk>/delete/', views.AddressDestroyView.as_view(), name='address-destroy'), path('address/<int:pk>/delete/', views.AddressDestroyView.as_view(), name='address_destroy'),
path('address/<int:pk>/update/', views.AddressUpdateView.as_view(), name='address-update'), path('address/<int:pk>/update/', views.AddressUpdateView.as_view(), name='address_update'),
] ]

View File

@ -1,9 +1,10 @@
from rest_framework import generics, permissions from rest_framework import generics, permissions
from news.models import News from news.models import News
from news.serializers import common as serializers from news.serializers import common as serializers
from utils.views import JWTViewMixin
class NewsList(generics.ListAPIView): class NewsList(JWTViewMixin, generics.ListAPIView):
"""News list view.""" """News list view."""
queryset = News.objects.all() queryset = News.objects.all()
permission_classes = (permissions.AllowAny, ) permission_classes = (permissions.AllowAny, )

View File

View File

@ -0,0 +1,7 @@
from django.contrib import admin
from translation.models import Language
@admin.register(Language)
class LanguageAdmin(admin.ModelAdmin):
"""Language admin."""

7
apps/translation/apps.py Normal file
View File

@ -0,0 +1,7 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class TranslationConfig(AppConfig):
name = 'translation'
verbose_name = _('Translation')

View File

@ -0,0 +1,26 @@
# Generated by Django 2.2.4 on 2019-08-14 13:35
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Language',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255, verbose_name='Language title')),
('locale', models.CharField(max_length=10, verbose_name='Locale identifier')),
],
options={
'verbose_name': 'Language',
'verbose_name_plural': 'Languages',
},
),
]

View File

View File

@ -0,0 +1,33 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
class LanguageQuerySet(models.QuerySet):
"""QuerySet for model Language"""
def by_locale(self, locale: str) -> models.QuerySet:
"""Filter by locale"""
return self.filter(locale=locale)
def by_title(self, title: str) -> models.QuerySet:
"""Filter by title"""
return self.filter(title=title)
class Language(models.Model):
"""Language model."""
title = models.CharField(max_length=255,
verbose_name=_('Language title'))
locale = models.CharField(max_length=10,
verbose_name=_('Locale identifier'))
objects = LanguageQuerySet.as_manager()
class Meta:
verbose_name = _('Language')
verbose_name_plural = _('Languages')
def __str__(self):
"""String method"""
return f'{self.title} ({self.locale})'

View File

@ -0,0 +1,14 @@
from rest_framework import serializers
from translation import models
class LanguageSerializer(serializers.ModelSerializer):
"""Serializer for model Language"""
class Meta:
model = models.Language
fields = [
'id',
'title',
'locale'
]

View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

18
apps/translation/urls.py Normal file
View File

@ -0,0 +1,18 @@
from django.urls import path
from translation import views
app_name = 'translation'
urlpatterns = [
path('language/', views.LanguageListView.as_view(),
name='language_list'),
path('language/create/', views.LanguageCreateView.as_view(),
name='language_create'),
path('language/<int:pk>/update/', views.LanguageUpdateView.as_view(),
name='language_update'),
path('language/<int:pk>/destroy/', views.LanguageDestroyView.as_view(),
name='language_destroy'),
path('language/<int:pk>/', views.LanguageRetrieveView.as_view(),
name='language_retrieve'),
]

40
apps/translation/views.py Normal file
View File

@ -0,0 +1,40 @@
from rest_framework import generics
from translation import models
from translation import serializers
from rest_framework import permissions
from utils.views import JWTViewMixin
# Mixins
class LanguageViewMixin(generics.GenericAPIView):
"""Mixin for Language views"""
model = models.Language
queryset = models.Language.objects.all()
# Views
class LanguageListView(LanguageViewMixin, JWTViewMixin, generics.ListAPIView):
"""List view for model Language"""
permission_classes = (permissions.AllowAny, )
serializer_class = serializers.LanguageSerializer
class LanguageCreateView(LanguageViewMixin, generics.CreateAPIView):
"""Create view for model Language"""
permission_classes = (permissions.AllowAny, )
serializer_class = serializers.LanguageSerializer
class LanguageRetrieveView(LanguageViewMixin, generics.RetrieveAPIView):
"""Retrieve view for model Language"""
serializer_class = serializers.LanguageSerializer
class LanguageUpdateView(LanguageViewMixin, generics.UpdateAPIView):
"""Update view for model Language"""
serializer_class = serializers.LanguageSerializer
class LanguageDestroyView(LanguageViewMixin, generics.DestroyAPIView):
"""Destroy view for model Language"""
serializer_class = serializers.LanguageSerializer

View File

@ -47,3 +47,17 @@ class EmailSendingError(exceptions.APIException):
'detail': self.default_detail % recipient, 'detail': self.default_detail % recipient,
} }
super().__init__() super().__init__()
class LocaleNotExisted(exceptions.APIException):
"""The exception should be thrown when passed locale isn't in model Language
"""
status_code = status.HTTP_400_BAD_REQUEST
default_detail = _('Locale not found in database (%s)')
def __init__(self, locale: str = None):
if locale:
self.default_detail = {
'detail': self.default_detail % locale
}
super().__init__()

View File

@ -1,3 +1,101 @@
from rest_framework import generics from rest_framework import generics
from collections import namedtuple
from translation import models as translation_models
from utils import exceptions
from rest_framework.response import Response
# JWT
# Login base view mixin
class JWTViewMixin(generics.GenericAPIView):
"""JWT view mixin"""
ACCESS_TOKEN_HTTP = True
ACCESS_TOKEN_SECURE = False
REFRESH_TOKEN_HTTP = True
REFRESH_TOKEN_SECURE = False
COOKIE = namedtuple('COOKIE', ['key', 'value', 'http', 'secure'])
def _check_locale(self, locale: str):
locale_qs = translation_models.Language.objects.by_locale(locale=locale)
if not locale_qs.exists():
raise exceptions.LocaleNotExisted()
return locale
def _put_data_in_cookies(self,
locale: str,
access_token: str,
refresh_token: str):
"""
CHECK locale in cookies and PUT access and refresh tokens there.
cookies it is list that contain namedtuples
cookies would contain key, value and secure parameters.
"""
COOKIES = list()
# Create locale namedtuple
locale = self.COOKIE(key='locale',
value=locale,
http=True,
secure=False)
COOKIES.append(locale)
# Write to cookie access and refresh token with secure flag
_access_token = self.COOKIE(key='access_token',
value=access_token,
http=self.ACCESS_TOKEN_HTTP,
secure=self.ACCESS_TOKEN_SECURE)
_refresh_token = self.COOKIE(key='refresh_token',
value=refresh_token,
http=self.REFRESH_TOKEN_HTTP,
secure=self.REFRESH_TOKEN_SECURE)
COOKIES.extend((_access_token, _refresh_token))
return COOKIES
def _put_cookies_in_response(self, cookies: list, response: Response):
"""Update COOKIES in response from namedtuple"""
for cookie in cookies:
response.set_cookie(key=cookie.key,
value=cookie.value,
secure=cookie.secure)
return response
def _get_tokens_from_cookies(self, request, cookies: dict = None):
"""Get user tokens from cookies and put in namedtuple"""
_cookies = request.COOKIES or cookies
return [self.COOKIE(key='access_token',
value=_cookies.get('access_token'),
http=self.ACCESS_TOKEN_HTTP,
secure=self.ACCESS_TOKEN_SECURE),
self.COOKIE(key='refresh_token',
value=_cookies.get('refresh_token'),
http=self.REFRESH_TOKEN_HTTP,
secure=self.REFRESH_TOKEN_SECURE)]
def get(self, request, *args, **kwargs):
"""Implement GET method"""
_locale = request.COOKIES.get('locale')
try:
locale = self._check_locale(locale=_locale)
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)
except exceptions.LocaleNotExisted:
raise exceptions.LocaleNotExisted(locale=_locale)
else:
return self._put_cookies_in_response(
cookies=self._put_data_in_cookies(locale=locale,
access_token=access_token,
refresh_token=refresh_token),
response=response)

View File

@ -56,6 +56,7 @@ PROJECT_APPS = [
'authorization.apps.AuthorizationConfig', 'authorization.apps.AuthorizationConfig',
'location.apps.LocationConfig', 'location.apps.LocationConfig',
'news.apps.NewsConfig', 'news.apps.NewsConfig',
'translation.apps.TranslationConfig',
] ]

View File

@ -24,6 +24,7 @@ from rest_framework import permissions
# URL platform patterns # URL platform patterns
from project.urls import web as web_urlpatterns from project.urls import web as web_urlpatterns
from location import urls as location_urls from location import urls as location_urls
from translation import urls as translation_urls
schema_view = get_schema_view( schema_view = get_schema_view(
@ -60,6 +61,7 @@ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('api/web/', include(web_urlpatterns)), path('api/web/', include(web_urlpatterns)),
path('api/location/', include(location_urls.urlpatterns)), path('api/location/', include(location_urls.urlpatterns)),
path('api/translation/', include(translation_urls.urlpatterns)),
] ]
urlpatterns = urlpatterns + \ urlpatterns = urlpatterns + \