version 0.0.11: added new model and CRUD endpoints to them; refactored auth app
This commit is contained in:
parent
033674c5b2
commit
d5a14ef8c2
|
|
@ -40,12 +40,12 @@ class UserQuerySet(models.QuerySet):
|
|||
"""Filter only active users."""
|
||||
return self.filter(is_active=switcher)
|
||||
|
||||
def by_access_token(self, token):
|
||||
def by_oauth2_access_token(self, token):
|
||||
"""Find user by access token"""
|
||||
return self.filter(oauth2_provider_accesstoken__token=token,
|
||||
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"""
|
||||
return self.filter(oauth2_provider_refreshtoken__token=token,
|
||||
oauth2_provider_refreshtoken__expires__gt=timezone.now())
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
from django.contrib import admin
|
||||
from authorization import models
|
||||
|
||||
# Register your models here.
|
||||
|
||||
@admin.register(models.BlacklistedAccessToken)
|
||||
class BlacklistedAccessTokenAdmin(admin.ModelAdmin):
|
||||
"""Admin for BlackListedAccessToken"""
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class BaseAuthSerializerMixin(serializers.Serializer):
|
|||
source = serializers.ChoiceField(choices=Application.SOURCES)
|
||||
|
||||
|
||||
class JWTBaseMixin(serializers.Serializer):
|
||||
class JWTBaseSerializerMixin(serializers.Serializer):
|
||||
"""
|
||||
Mixin for JWT authentication.
|
||||
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):
|
||||
"""Override to_representation method"""
|
||||
token = self.get_token()
|
||||
setattr(instance, 'refresh_token', str(token))
|
||||
setattr(instance, 'access_token', str(token.access_token))
|
||||
setattr(instance, 'refresh_token', str(token))
|
||||
return super().to_representation(instance)
|
||||
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ class ClassicAuthSerializerMixin(BaseAuthSerializerMixin):
|
|||
|
||||
|
||||
# Serializers
|
||||
class SignupSerializer(JWTBaseMixin, serializers.ModelSerializer):
|
||||
class SignupSerializer(JWTBaseSerializerMixin, serializers.ModelSerializer):
|
||||
"""Signup serializer serializer mixin"""
|
||||
# REQUEST
|
||||
username = serializers.CharField(
|
||||
|
|
@ -100,7 +100,7 @@ class SignupSerializer(JWTBaseMixin, serializers.ModelSerializer):
|
|||
return obj
|
||||
|
||||
|
||||
class LoginByUsernameOrEmailSerializer(JWTBaseMixin, serializers.ModelSerializer):
|
||||
class LoginByUsernameOrEmailSerializer(JWTBaseSerializerMixin, serializers.ModelSerializer):
|
||||
"""Serializer for login user"""
|
||||
username_or_email = serializers.CharField(write_only=True)
|
||||
password = serializers.CharField(write_only=True)
|
||||
|
|
@ -164,22 +164,28 @@ class LogoutSerializer(serializers.ModelSerializer):
|
|||
|
||||
class Meta:
|
||||
model = BlacklistedAccessToken
|
||||
fields = '__all__'
|
||||
read_only_fields = [
|
||||
'jti', 'token', 'user'
|
||||
]
|
||||
fields = (
|
||||
'user',
|
||||
'token',
|
||||
'jti'
|
||||
)
|
||||
read_only_fields = (
|
||||
'user',
|
||||
'token',
|
||||
'jti'
|
||||
)
|
||||
|
||||
def create(self, validated_data, *args, **kwargs):
|
||||
"""Override create method"""
|
||||
def validate(self, attrs):
|
||||
"""Override validated data"""
|
||||
request = self.context.get('request')
|
||||
token = request._request.headers.get('Authorization')\
|
||||
token = request._request.headers.get('Authorization') \
|
||||
.split(' ')[::-1][0]
|
||||
access_token = tokens.AccessToken(token)
|
||||
# Prepare validated data
|
||||
validated_data['user'] = request.user
|
||||
validated_data['token'] = access_token.token
|
||||
validated_data['jti'] = access_token.payload.get('jti')
|
||||
return super().create(validated_data)
|
||||
attrs['user'] = request.user
|
||||
attrs['token'] = access_token.token
|
||||
attrs['jti'] = access_token.payload.get('jti')
|
||||
return attrs
|
||||
|
||||
|
||||
# OAuth
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
"""Common views for application Account"""
|
||||
import json
|
||||
from collections import namedtuple
|
||||
|
||||
from braces.views import CsrfExemptMixin
|
||||
from django.conf import settings
|
||||
|
|
@ -20,12 +19,36 @@ from account.models import User
|
|||
from authorization.models import Application
|
||||
from authorization.serializers import common as serializers
|
||||
from utils import exceptions as utils_exceptions
|
||||
|
||||
|
||||
# JWT
|
||||
from utils.views import JWTViewMixin
|
||||
|
||||
|
||||
# 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
|
||||
class BaseOAuth2ViewMixin(generics.GenericAPIView):
|
||||
"""BaseMixin for classic auth views"""
|
||||
|
|
@ -74,53 +97,8 @@ class OAuth2ViewMixin(CsrfExemptMixin, OAuthLibMixin, BaseOAuth2ViewMixin):
|
|||
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
|
||||
class OAuth2SignUpView(OAuth2ViewMixin, JWTViewMixin):
|
||||
class OAuth2SignUpView(OAuth2ViewMixin, JWTAuthViewMixin):
|
||||
"""
|
||||
Implements an endpoint to convert a provider token to an access token
|
||||
|
||||
|
|
@ -135,96 +113,112 @@ class OAuth2SignUpView(OAuth2ViewMixin, JWTViewMixin):
|
|||
serializer_class = serializers.OAuth2Serialzier
|
||||
|
||||
def get_jwt_token(self, user: User,
|
||||
access_token: str,
|
||||
refresh_token: str):
|
||||
oauth2_access_token: str,
|
||||
oauth2_refresh_token: str):
|
||||
"""Get JWT token"""
|
||||
token = jwt_tokens.RefreshToken.for_user(user)
|
||||
# Adding additional information about user to payload
|
||||
token['user'] = user.get_user_info()
|
||||
# Adding OAuth2 tokens to payloads
|
||||
token['oauth2_fb'] = {'access_token': access_token,
|
||||
'refresh_token': refresh_token}
|
||||
token['oauth2_fb'] = {'access_token': oauth2_access_token,
|
||||
'refresh_token': oauth2_refresh_token}
|
||||
return token
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
||||
# Preparing request data
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
request_data = self.prepare_request_data(serializer.validated_data)
|
||||
request_data.update({
|
||||
'grant_type': settings.OAUTH2_SOCIAL_AUTH_GRANT_TYPE,
|
||||
'backend': settings.OAUTH2_SOCIAL_AUTH_BACKEND_NAME
|
||||
})
|
||||
# Use the rest framework `.data` to fake the post body of the django request.
|
||||
request._request.POST = request._request.POST.copy()
|
||||
for key, value in request_data.items():
|
||||
request._request.POST[key] = value
|
||||
_locale = request.COOKIES.get('locale')
|
||||
try:
|
||||
locale = self._check_locale(locale=_locale)
|
||||
# Preparing request data
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
request_data = self.prepare_request_data(serializer.validated_data)
|
||||
request_data.update({
|
||||
'grant_type': settings.OAUTH2_SOCIAL_AUTH_GRANT_TYPE,
|
||||
'backend': settings.OAUTH2_SOCIAL_AUTH_BACKEND_NAME
|
||||
})
|
||||
# Use the rest framework `.data` to fake the post body of the django request.
|
||||
request._request.POST = request._request.POST.copy()
|
||||
for key, value in request_data.items():
|
||||
request._request.POST[key] = value
|
||||
|
||||
url, headers, body, oauth2_status = self.create_token_response(request._request)
|
||||
body = json.loads(body)
|
||||
# Get JWT token
|
||||
if oauth2_status != status.HTTP_200_OK:
|
||||
raise ValueError('status isn\'t 200')
|
||||
user = User.objects.by_access_token(token=body.get('access_token'))\
|
||||
.first()
|
||||
token = self.get_jwt_token(user=user,
|
||||
access_token=body.get('access_token'),
|
||||
refresh_token=body.get('refresh_token'))
|
||||
refresh_token = str(token)
|
||||
access_token = str(token.access_token)
|
||||
response = Response(data={'refresh_token': refresh_token,
|
||||
'access_token': access_token},
|
||||
status=status.HTTP_200_OK)
|
||||
return self._put_cookies_in_response(
|
||||
cookies=self._handle_cookies(request, access_token, refresh_token),
|
||||
response=response)
|
||||
# OAuth2 authentication process
|
||||
url, headers, body, oauth2_status = self.create_token_response(request._request)
|
||||
body = json.loads(body)
|
||||
|
||||
# Get JWT token
|
||||
if oauth2_status != status.HTTP_200_OK:
|
||||
raise ValueError('status isn\'t 200')
|
||||
|
||||
# Get authenticated user
|
||||
user = User.objects.by_oauth2_access_token(token=body.get('access_token'))\
|
||||
.first()
|
||||
|
||||
# Create JWT token and put oauth2 token (access, refresh tokens) in payload
|
||||
token = self.get_jwt_token(user=user,
|
||||
oauth2_access_token=body.get('access_token'),
|
||||
oauth2_refresh_token=body.get('refresh_token'))
|
||||
|
||||
access_token = str(token.access_token)
|
||||
refresh_token = str(token)
|
||||
response = Response(data={'access_token': access_token,
|
||||
'refresh_token': refresh_token},
|
||||
status=status.HTTP_200_OK)
|
||||
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)
|
||||
|
||||
|
||||
# JWT
|
||||
# Sign in via username and password
|
||||
class SignUpView(JWTViewMixin):
|
||||
class SignUpView(JWTAuthViewMixin):
|
||||
"""View for classic signup"""
|
||||
permission_classes = (permissions.AllowAny, )
|
||||
serializer_class = serializers.SignupSerializer
|
||||
|
||||
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)
|
||||
serializer.save()
|
||||
response = Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
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)
|
||||
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 = 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)
|
||||
|
||||
|
||||
# Login by username|email + password
|
||||
class LoginByUsernameOrEmailView(JWTViewMixin):
|
||||
class LoginByUsernameOrEmailView(JWTAuthViewMixin):
|
||||
"""Login by email and password"""
|
||||
permission_classes = (permissions.AllowAny,)
|
||||
serializer_class = serializers.LoginByUsernameOrEmailSerializer
|
||||
|
||||
|
||||
# Refresh access_token
|
||||
class RefreshTokenView(JWTViewMixin):
|
||||
class RefreshTokenView(JWTAuthViewMixin):
|
||||
"""Refresh access_token"""
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
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
|
||||
class LogoutView(generics.CreateAPIView):
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from rest_framework import serializers
|
||||
from location import models
|
||||
from django.contrib.gis.geos import Point
|
||||
|
||||
|
||||
class CountrySerializer(serializers.ModelSerializer):
|
||||
|
|
@ -48,8 +49,8 @@ class CitySerializer(serializers.ModelSerializer):
|
|||
class AddressSerializer(serializers.ModelSerializer):
|
||||
"""Address serializer."""
|
||||
city = CitySerializer()
|
||||
longitude = serializers.DecimalField(max_digits=10, decimal_places=6)
|
||||
latitude = serializers.DecimalField(max_digits=10, decimal_places=6)
|
||||
geo_lon = serializers.FloatField(allow_null=True)
|
||||
geo_lat = serializers.FloatField(allow_null=True)
|
||||
|
||||
class Meta:
|
||||
model = models.Address
|
||||
|
|
@ -59,14 +60,26 @@ class AddressSerializer(serializers.ModelSerializer):
|
|||
'street_name_2',
|
||||
'number',
|
||||
'postal_code',
|
||||
'longitude',
|
||||
'latitude'
|
||||
'geo_lon',
|
||||
'geo_lat'
|
||||
]
|
||||
|
||||
# def validate(self, attrs):
|
||||
# geo_lat = attrs.pop('geo_lat') if 'geo_lat' in attrs else None
|
||||
# geo_lon = attrs.pop('geo_lon') if 'geo_lon' in attrs else None
|
||||
# if geo_lat and geo_lon:
|
||||
# # Point(longitude, latitude)
|
||||
# attrs['coordinates'] = Point(geo_lon, geo_lat)
|
||||
# return attrs
|
||||
def validate(self, attrs):
|
||||
# if geo_lat and geo_lon was sent
|
||||
geo_lat = attrs.pop('geo_lat') if 'geo_lat' in attrs else None
|
||||
geo_lon = attrs.pop('geo_lon') if 'geo_lon' in attrs else None
|
||||
if geo_lat and geo_lon:
|
||||
# Point(longitude, latitude)
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -6,27 +6,27 @@ from location import views
|
|||
app_name = 'location'
|
||||
|
||||
urlpatterns = [
|
||||
path('country/list/', views.CountryListView.as_view(), name='country-list'),
|
||||
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>/delete/', views.CountryDestroyView.as_view(), name='country-destroy'),
|
||||
path('country/<int:pk>/update/', views.CountryUpdateView.as_view(), name='country-update'),
|
||||
path('country/', views.CountryListView.as_view(), name='country_list'),
|
||||
path('country/create/', views.CountryCreateView.as_view(), name='country_create'),
|
||||
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>/update/', views.CountryUpdateView.as_view(), name='country_update'),
|
||||
|
||||
path('region/list/', views.RegionListView.as_view(), name='region-list'),
|
||||
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>/delete/', views.RegionDestroyView.as_view(), name='region-destroy'),
|
||||
path('region/<int:pk>/update/', views.RegionUpdateView.as_view(), name='region-update'),
|
||||
path('region/', views.RegionListView.as_view(), name='region_list'),
|
||||
path('region/create/', views.RegionCreateView.as_view(), name='region_create'),
|
||||
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>/update/', views.RegionUpdateView.as_view(), name='region_update'),
|
||||
|
||||
path('city/list/', views.CityListView.as_view(), name='city-list'),
|
||||
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>/delete/', views.CityDestroyView.as_view(), name='city-destroy'),
|
||||
path('city/<int:pk>/update/', views.CityUpdateView.as_view(), name='city-update'),
|
||||
path('city/', views.CityListView.as_view(), name='city_list'),
|
||||
path('city/create/', views.CityCreateView.as_view(), name='city_create'),
|
||||
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>/update/', views.CityUpdateView.as_view(), name='city_update'),
|
||||
|
||||
path('address/list/', views.AddressListView.as_view(), name='address-list'),
|
||||
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>/delete/', views.AddressDestroyView.as_view(), name='address-destroy'),
|
||||
path('address/<int:pk>/update/', views.AddressUpdateView.as_view(), name='address-update'),
|
||||
path('address/', views.AddressListView.as_view(), name='address_list'),
|
||||
path('address/create/', views.AddressCreateView.as_view(), name='address_create'),
|
||||
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>/update/', views.AddressUpdateView.as_view(), name='address_update'),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
from rest_framework import generics, permissions
|
||||
from news.models import News
|
||||
from news.serializers import common as serializers
|
||||
from utils.views import JWTViewMixin
|
||||
|
||||
|
||||
class NewsList(generics.ListAPIView):
|
||||
class NewsList(JWTViewMixin, generics.ListAPIView):
|
||||
"""News list view."""
|
||||
queryset = News.objects.all()
|
||||
permission_classes = (permissions.AllowAny, )
|
||||
|
|
|
|||
0
apps/translation/__init__.py
Normal file
0
apps/translation/__init__.py
Normal file
7
apps/translation/admin.py
Normal file
7
apps/translation/admin.py
Normal 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
7
apps/translation/apps.py
Normal 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')
|
||||
26
apps/translation/migrations/0001_initial.py
Normal file
26
apps/translation/migrations/0001_initial.py
Normal 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',
|
||||
},
|
||||
),
|
||||
]
|
||||
0
apps/translation/migrations/__init__.py
Normal file
0
apps/translation/migrations/__init__.py
Normal file
33
apps/translation/models.py
Normal file
33
apps/translation/models.py
Normal 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})'
|
||||
14
apps/translation/serializers.py
Normal file
14
apps/translation/serializers.py
Normal 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'
|
||||
]
|
||||
3
apps/translation/tests.py
Normal file
3
apps/translation/tests.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
18
apps/translation/urls.py
Normal file
18
apps/translation/urls.py
Normal 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
40
apps/translation/views.py
Normal 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
|
||||
|
|
@ -47,3 +47,17 @@ class EmailSendingError(exceptions.APIException):
|
|||
'detail': self.default_detail % recipient,
|
||||
}
|
||||
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__()
|
||||
|
|
|
|||
|
|
@ -1,3 +1,101 @@
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ PROJECT_APPS = [
|
|||
'authorization.apps.AuthorizationConfig',
|
||||
'location.apps.LocationConfig',
|
||||
'news.apps.NewsConfig',
|
||||
'translation.apps.TranslationConfig',
|
||||
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ from rest_framework import permissions
|
|||
# URL platform patterns
|
||||
from project.urls import web as web_urlpatterns
|
||||
from location import urls as location_urls
|
||||
from translation import urls as translation_urls
|
||||
|
||||
|
||||
schema_view = get_schema_view(
|
||||
|
|
@ -60,6 +61,7 @@ urlpatterns = [
|
|||
path('admin/', admin.site.urls),
|
||||
path('api/web/', include(web_urlpatterns)),
|
||||
path('api/location/', include(location_urls.urlpatterns)),
|
||||
path('api/translation/', include(translation_urls.urlpatterns)),
|
||||
]
|
||||
|
||||
urlpatterns = urlpatterns + \
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user