Merge branch 'develop' of ssh://gl.id-east.ru:222/gm/gm-backend into develop

This commit is contained in:
evgeniy-st 2019-08-30 15:42:12 +03:00
commit 7d7fa5e347
31 changed files with 341 additions and 147 deletions

View File

@ -23,13 +23,14 @@ class UserManager(BaseUserManager):
use_in_migrations = False use_in_migrations = False
def make(self, username: str, email: str, def make(self, username: str, email: str, password: str,
password: str, newsletter: bool) -> object: newsletter: bool, is_active: bool = False) -> object:
"""Register new user""" """Register new user"""
obj = self.model( obj = self.model(
username=username, username=username,
email=email, email=email,
newsletter=newsletter newsletter=newsletter,
is_active=is_active
) )
obj.set_password(password) obj.set_password(password)
obj.save() obj.save()
@ -100,6 +101,11 @@ class User(ImageMixin, AbstractUser):
self.email_confirmed = True self.email_confirmed = True
self.save() self.save()
def approve(self):
"""Set user is_active status to True"""
self.is_active = True
self.save()
def remove_access_tokens(self, source: Union[int, Union[tuple, list]]): def remove_access_tokens(self, source: Union[int, Union[tuple, list]]):
"""Method to remove user access tokens""" """Method to remove user access tokens"""
source = source if isinstance(source, list) else [source, ] source = source if isinstance(source, list) else [source, ]
@ -118,12 +124,12 @@ class User(ImageMixin, AbstractUser):
token.revoke() token.revoke()
@property @property
def get_confirm_email_token(self): def confirm_email_token(self):
"""Make a token for finish signup.""" """Make a token for finish signup."""
return gm_token_generator.make_token(self) return gm_token_generator.make_token(self)
@property @property
def get_reset_password_token(self): def reset_password_token(self):
"""Make a token for finish signup.""" """Make a token for finish signup."""
return password_token_generator.make_token(self) return password_token_generator.make_token(self)
@ -136,9 +142,10 @@ class User(ImageMixin, AbstractUser):
"""Get confirm email template""" """Get confirm email template"""
return render_to_string( return render_to_string(
template_name=settings.CONFIRM_EMAIL_TEMPLATE, template_name=settings.CONFIRM_EMAIL_TEMPLATE,
context={'token': self.get_confirm_email_token, context={'token': self.confirm_email_token,
'uid': self.get_user_uidb64, 'uid': self.get_user_uidb64,
'domain_uri': settings.DOMAIN_URI}) 'domain_uri': settings.DOMAIN_URI,
'site_name': settings.SITE_NAME})
def get_body_email_message(self, subject: str, message: str): def get_body_email_message(self, subject: str, message: str):
"""Prepare the body of the email message""" """Prepare the body of the email message"""
@ -241,5 +248,6 @@ class ResetPasswordToken(PlatformMixin, ProjectBaseMixin):
template_name=settings.RESETTING_TOKEN_TEMPLATE, template_name=settings.RESETTING_TOKEN_TEMPLATE,
context={'token': self.key, context={'token': self.key,
'uidb64': self.user.get_user_uidb64, 'uidb64': self.user.get_user_uidb64,
'domain_uri': settings.DOMAIN_URI}) 'domain_uri': settings.DOMAIN_URI,
'site_name': settings.SITE_NAME})

View File

@ -3,6 +3,9 @@ from fcm_django.models import FCMDevice
from rest_framework import serializers, exceptions from rest_framework import serializers, exceptions
from account import models from account import models
from utils import exceptions as utils_exceptions
from rest_framework_simplejwt import tokens
from django.conf import settings
# User serializers # User serializers
@ -58,3 +61,40 @@ class FCMDeviceSerializer(serializers.ModelSerializer):
instance.user = None instance.user = None
instance.save() instance.save()
return instance return instance
class RefreshTokenSerializer(serializers.Serializer):
"""Serializer for refresh token view"""
refresh_token = serializers.CharField(read_only=True)
access_token = serializers.CharField(read_only=True)
def get_request(self):
"""Return request"""
return self.context.get('request')
def validate(self, attrs):
"""Override validate method"""
refresh_token = self.get_request().COOKIES.get('refresh_token')
if not refresh_token:
raise utils_exceptions.NotValidRefreshTokenError()
token = tokens.RefreshToken(token=refresh_token)
data = {'access_token': str(token.access_token)}
if settings.SIMPLE_JWT.get('ROTATE_REFRESH_TOKENS'):
if settings.SIMPLE_JWT.get('BLACKLIST_AFTER_ROTATION'):
try:
# Attempt to blacklist the given refresh token
token.blacklist()
except AttributeError:
# If blacklist app not installed, `blacklist` method will
# not be present
pass
token.set_jti()
token.set_exp()
data['refresh_token'] = str(token)
return data

View File

@ -22,15 +22,3 @@ def send_reset_password_email(request_id):
logger.error(f'METHOD_NAME: {send_reset_password_email.__name__}\n' logger.error(f'METHOD_NAME: {send_reset_password_email.__name__}\n'
f'DETAIL: Exception occurred for ResetPasswordToken instance: ' f'DETAIL: Exception occurred for ResetPasswordToken instance: '
f'{request_id}') f'{request_id}')
@shared_task
def send_confirm_email(user_id):
"""Send verification email to user."""
try:
obj = models.User.objects.get(id=user_id)
obj.send_email(subject=_('Email confirmation'),
message=obj.get_confirm_email_template())
except:
logger.error(f'METHOD_NAME: {send_confirm_email.__name__}\n'
f'DETAIL: Exception occurred for user: {user_id}')

View File

@ -7,5 +7,5 @@ app_name = 'account'
urlpatterns = [ urlpatterns = [
path('user/', views.UserView.as_view(), name='user-get-update'), path('user/', views.UserView.as_view(), name='user-get-update'),
path('refresh-token/', views.RefreshTokenView.as_view(), name="refresh-token"),
] ]

View File

@ -7,10 +7,6 @@ from account.views import web as views
app_name = 'account' app_name = 'account'
urlpatterns_api = [ urlpatterns_api = [
path('verify/email/', views.VerifyEmailView.as_view(),
name='verify-email'),
path('verify/email/confirm/<uidb64>/<token>/', views.VerifyEmailConfirmView.as_view(),
name='verify-email-confirm'),
path('reset-password/', views.PasswordResetView.as_view(), path('reset-password/', views.PasswordResetView.as_view(),
name='password-reset'), name='password-reset'),
path('form/reset-password/<uidb64>/<token>/', views.FormPasswordResetConfirmView.as_view(), path('form/reset-password/<uidb64>/<token>/', views.FormPasswordResetConfirmView.as_view(),

View File

@ -6,6 +6,7 @@ from rest_framework.response import Response
from account import models from account import models
from account.serializers import common as serializers from account.serializers import common as serializers
from utils.views import JWTGenericViewMixin
# User views # User views
@ -53,3 +54,21 @@ class FCMDeviceViewSet(generics.GenericAPIView):
obj = queryset.filter(**filter).first() obj = queryset.filter(**filter).first()
obj and self.check_object_permissions(self.request, obj) obj and self.check_object_permissions(self.request, obj)
return obj return obj
# Refresh access_token
class RefreshTokenView(JWTGenericViewMixin):
"""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.data, status=status.HTTP_201_CREATED)
access_token = serializer.data.get('access_token')
refresh_token = serializer.data.get('refresh_token')
return self._put_cookies_in_response(
cookies=self._put_data_in_cookies(access_token=access_token,
refresh_token=refresh_token),
response=response)

View File

@ -18,7 +18,6 @@ from rest_framework import views
from rest_framework.response import Response from rest_framework.response import Response
from account import models from account import models
from account import tasks
from account.forms import SetPasswordForm from account.forms import SetPasswordForm
from account.serializers import web as serializers from account.serializers import web as serializers
from utils import exceptions as utils_exceptions from utils import exceptions as utils_exceptions
@ -27,45 +26,6 @@ from utils.views import (JWTCreateAPIView,
JWTGenericViewMixin) JWTGenericViewMixin)
# Email confirmation
class VerifyEmailView(JWTGenericViewMixin):
"""View for confirmation email"""
def post(self, request, *args, **kwargs):
"""Implement POST method"""
user = request.user
if user.email_confirmed:
raise utils_exceptions.EmailConfirmedError()
# Send verification link on user email
if settings.USE_CELERY:
tasks.send_confirm_email.delay(user.id)
else:
tasks.send_confirm_email(user.id)
return Response(status=status.HTTP_200_OK)
class VerifyEmailConfirmView(JWTGenericViewMixin):
"""View for confirmation email"""
permission_classes = (permissions.AllowAny, )
def get(self, request, *args, **kwargs):
"""Implement GET-method"""
uidb64 = kwargs.get('uidb64')
token = kwargs.get('token')
uid = force_text(urlsafe_base64_decode(uidb64))
user_qs = models.User.objects.filter(pk=uid)
if user_qs.exists():
user = user_qs.first()
if not gm_token_generator.check_token(user, token):
raise utils_exceptions.NotValidAccessTokenError()
# Change email status
user.confirm_email()
return Response(status=status.HTTP_200_OK)
else:
raise utils_exceptions.UserNotFoundError()
# Password reset # Password reset
class PasswordResetView(JWTCreateAPIView): class PasswordResetView(JWTCreateAPIView):
"""View for resetting user password""" """View for resetting user password"""

View File

@ -5,6 +5,7 @@ from django.contrib.auth import password_validation as password_validators
from django.db.models import Q from django.db.models import Q
from rest_framework import serializers from rest_framework import serializers
from rest_framework import validators as rest_validators from rest_framework import validators as rest_validators
from authorization import tasks
# JWT # JWT
from rest_framework_simplejwt import tokens from rest_framework_simplejwt import tokens
@ -13,8 +14,6 @@ from authorization.models import Application, BlacklistedAccessToken
from utils import exceptions as utils_exceptions from utils import exceptions as utils_exceptions
from utils import methods as utils_methods from utils import methods as utils_methods
JWT_SETTINGS = settings.SIMPLE_JWT
# Mixins # Mixins
class BaseAuthSerializerMixin(serializers.Serializer): class BaseAuthSerializerMixin(serializers.Serializer):
@ -46,17 +45,6 @@ class JWTBaseSerializerMixin(serializers.Serializer):
return super().to_representation(instance) return super().to_representation(instance)
class LoginSerializerMixin(BaseAuthSerializerMixin):
"""Mixin for login serializers"""
password = serializers.CharField(write_only=True)
class ClassicAuthSerializerMixin(BaseAuthSerializerMixin):
"""Classic authorization serializer mixin"""
password = serializers.CharField(write_only=True)
newsletter = serializers.BooleanField()
# Serializers # Serializers
class SignupSerializer(serializers.ModelSerializer): class SignupSerializer(serializers.ModelSerializer):
"""Signup serializer serializer mixin""" """Signup serializer serializer mixin"""
@ -102,8 +90,12 @@ class SignupSerializer(serializers.ModelSerializer):
username=validated_data.get('username'), username=validated_data.get('username'),
password=validated_data.get('password'), password=validated_data.get('password'),
email=validated_data.get('email'), email=validated_data.get('email'),
newsletter=validated_data.get('newsletter') newsletter=validated_data.get('newsletter'))
) # Send verification link on user email
if settings.USE_CELERY:
tasks.send_confirm_email.delay(obj.id)
else:
tasks.send_confirm_email(obj.id)
return obj return obj
@ -152,43 +144,6 @@ class LoginByUsernameOrEmailSerializer(JWTBaseSerializerMixin, serializers.Model
return super().to_representation(instance) return super().to_representation(instance)
class RefreshTokenSerializer(serializers.Serializer):
"""Serializer for refresh token view"""
refresh_token = serializers.CharField(read_only=True)
access_token = serializers.CharField(read_only=True)
def get_request(self):
"""Return request"""
return self.context.get('request')
def validate(self, attrs):
"""Override validate method"""
refresh_token = self.get_request().COOKIES.get('refresh_token')
if not refresh_token:
raise utils_exceptions.NotValidRefreshTokenError()
token = tokens.RefreshToken(token=refresh_token)
data = {'access_token': str(token.access_token)}
if JWT_SETTINGS.get('ROTATE_REFRESH_TOKENS'):
if JWT_SETTINGS.get('BLACKLIST_AFTER_ROTATION'):
try:
# Attempt to blacklist the given refresh token
token.blacklist()
except AttributeError:
# If blacklist app not installed, `blacklist` method will
# not be present
pass
token.set_jti()
token.set_exp()
data['refresh_token'] = str(token)
return data
class LogoutSerializer(serializers.ModelSerializer): class LogoutSerializer(serializers.ModelSerializer):
"""Serializer class for model Logout""" """Serializer class for model Logout"""

View File

@ -7,3 +7,15 @@ from account import models as account_models
logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.INFO) logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.INFO)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@shared_task
def send_confirm_email(user_id):
"""Send verification email to user."""
try:
obj = account_models.User.objects.get(id=user_id)
obj.send_email(subject=_('Email confirmation'),
message=obj.get_confirm_email_template())
except:
logger.error(f'METHOD_NAME: {send_confirm_email.__name__}\n'
f'DETAIL: Exception occurred for user: {user_id}')

View File

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

View File

@ -3,6 +3,8 @@ import json
from braces.views import CsrfExemptMixin from braces.views import CsrfExemptMixin
from django.conf import settings from django.conf import settings
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.utils.translation import gettext_lazy as _
from oauth2_provider.oauth2_backends import OAuthLibCore from oauth2_provider.oauth2_backends import OAuthLibCore
from oauth2_provider.settings import oauth2_settings from oauth2_provider.settings import oauth2_settings
@ -14,6 +16,7 @@ from rest_framework.response import Response
from rest_framework_simplejwt import tokens as jwt_tokens from rest_framework_simplejwt import tokens as jwt_tokens
from rest_framework_social_oauth2.oauth2_backends import KeepRequestCore from rest_framework_social_oauth2.oauth2_backends import KeepRequestCore
from rest_framework_social_oauth2.oauth2_endpoints import SocialTokenServer from rest_framework_social_oauth2.oauth2_endpoints import SocialTokenServer
from utils.models import gm_token_generator
from account.models import User from account.models import User
from authorization.models import Application from authorization.models import Application
@ -166,6 +169,30 @@ class SignUpView(JWTCreateAPIView):
return Response(status=status.HTTP_201_CREATED) return Response(status=status.HTTP_201_CREATED)
class VerifyEmailConfirmView(JWTGenericViewMixin):
"""View for confirmation email"""
permission_classes = (permissions.AllowAny, )
def get(self, request, *args, **kwargs):
"""Implement GET-method"""
uidb64 = kwargs.get('uidb64')
token = kwargs.get('token')
uid = force_text(urlsafe_base64_decode(uidb64))
user_qs = User.objects.filter(pk=uid)
if user_qs.exists():
user = user_qs.first()
if not gm_token_generator.check_token(user, token):
raise utils_exceptions.NotValidTokenError()
# Approve email status
user.confirm_email()
# Set user status as active
user.approve()
return Response(status=status.HTTP_200_OK)
else:
raise utils_exceptions.UserNotFoundError()
# Login by username|email + password # Login by username|email + password
class LoginByUsernameOrEmailView(JWTAuthViewMixin): class LoginByUsernameOrEmailView(JWTAuthViewMixin):
"""Login by email and password""" """Login by email and password"""
@ -187,24 +214,6 @@ class LoginByUsernameOrEmailView(JWTAuthViewMixin):
response=response) response=response)
# Refresh access_token
class RefreshTokenView(JWTGenericViewMixin):
"""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.data, status=status.HTTP_201_CREATED)
access_token = serializer.data.get('access_token')
refresh_token = serializer.data.get('refresh_token')
return self._put_cookies_in_response(
cookies=self._put_data_in_cookies(access_token=access_token,
refresh_token=refresh_token),
response=response)
# Logout # Logout
class LogoutView(JWTGenericViewMixin): class LogoutView(JWTGenericViewMixin):
"""Logout user""" """Logout user"""

View File

@ -29,3 +29,8 @@ class MetaDataContentInline(GenericTabularInline):
class EstablishmentAdmin(admin.ModelAdmin): class EstablishmentAdmin(admin.ModelAdmin):
"""Establishment admin.""" """Establishment admin."""
inlines = [AwardInline, MetaDataContentInline] inlines = [AwardInline, MetaDataContentInline]
@admin.register(models.EstablishmentSchedule)
class EstablishmentSchedule(admin.ModelAdmin):
"""Establishment schedule"""

View File

@ -1,4 +1,3 @@
from django.utils.translation import ugettext_lazy as _
from django_filters import FilterSet from django_filters import FilterSet
from django_filters import rest_framework as filters from django_filters import rest_framework as filters

View File

@ -0,0 +1,34 @@
# Generated by Django 2.2.4 on 2019-08-29 14:46
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('timetable', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('establishment', '0004_auto_20190828_1156'),
]
operations = [
migrations.CreateModel(
name='EstablishmentSchedule',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')),
('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='establishmentschedule_records_created', to=settings.AUTH_USER_MODEL, verbose_name='created by')),
('establishment', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='schedule', to='establishment.Establishment', verbose_name='Establishment')),
('modified_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='establishmentschedule_records_modified', to=settings.AUTH_USER_MODEL, verbose_name='modified by')),
('schedule', models.ManyToManyField(to='timetable.Timetable', verbose_name='Establishment schedule')),
],
options={
'verbose_name': 'Establishment schedule',
'verbose_name_plural': 'Establishment schedules',
},
),
]

View File

@ -3,7 +3,8 @@ from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from location.models import Address from location.models import Address
from utils.models import ProjectBaseMixin, ImageMixin, TJSONField, TraslatedFieldsMixin from utils.models import (ProjectBaseMixin, ImageMixin, TJSONField,
TraslatedFieldsMixin, BaseAttributes)
from django.contrib.contenttypes import fields as generic from django.contrib.contenttypes import fields as generic
@ -124,3 +125,24 @@ class Establishment(ProjectBaseMixin, ImageMixin, TraslatedFieldsMixin):
if establishment_subtype.establishment_type != self.establishment_type: if establishment_subtype.establishment_type != self.establishment_type:
raise ValidationError('Establishment type of subtype does not match') raise ValidationError('Establishment type of subtype does not match')
self.establishment_subtypes.add(establishment_subtype) self.establishment_subtypes.add(establishment_subtype)
class EstablishmentScheduleQuerySet(models.QuerySet):
"""QuerySet for model EstablishmentSchedule"""
class EstablishmentSchedule(BaseAttributes):
"""Establishment schedule model."""
establishment = models.OneToOneField(Establishment,
related_name='schedule',
on_delete=models.CASCADE,
verbose_name=_('Establishment'))
schedule = models.ManyToManyField(to='timetable.Timetable',
verbose_name=_('Establishment schedule'))
objects = EstablishmentScheduleQuerySet.as_manager()
class Meta:
"""Meta class"""
verbose_name = _('Establishment schedule')
verbose_name_plural = _('Establishment schedules')

View File

@ -3,6 +3,7 @@ from rest_framework import serializers
from establishment import models from establishment import models
from location.serializers import AddressSerializer from location.serializers import AddressSerializer
from main.serializers import MetaDataContentSerializer, AwardSerializer from main.serializers import MetaDataContentSerializer, AwardSerializer
from timetable.models import Timetable
class EstablishmentTypeSerializer(serializers.ModelSerializer): class EstablishmentTypeSerializer(serializers.ModelSerializer):
@ -29,6 +30,22 @@ class EstablishmentSubTypeSerializer(serializers.ModelSerializer):
fields = ('id', 'name_translated') fields = ('id', 'name_translated')
class EstablishmentScheduleSerializer(serializers.ModelSerializer):
"""Serializer for Establishment model."""
weekday = serializers.CharField(source='get_weekday_display')
class Meta:
"""Meta class."""
model = Timetable
fields = (
'weekday',
'lunch_start',
'lunch_end',
'dinner_start',
'dinner_end',
)
class EstablishmentSerializer(serializers.ModelSerializer): class EstablishmentSerializer(serializers.ModelSerializer):
"""Serializer for Establishment model.""" """Serializer for Establishment model."""
@ -39,6 +56,9 @@ class EstablishmentSerializer(serializers.ModelSerializer):
address = AddressSerializer() address = AddressSerializer()
tags = MetaDataContentSerializer(many=True) tags = MetaDataContentSerializer(many=True)
awards = AwardSerializer(many=True) awards = AwardSerializer(many=True)
schedule = EstablishmentScheduleSerializer(source='schedule.schedule',
many=True,
allow_null=True)
class Meta: class Meta:
"""Meta class.""" """Meta class."""
@ -58,4 +78,5 @@ class EstablishmentSerializer(serializers.ModelSerializer):
'address', 'address',
'tags', 'tags',
'awards', 'awards',
'schedule',
) )

View File

7
apps/timetable/admin.py Normal file
View File

@ -0,0 +1,7 @@
from django.contrib import admin
from timetable import models
@admin.register(models.Timetable)
class TimetableModelAdmin(admin.ModelAdmin):
"""Timetable model admin"""

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

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

View File

@ -0,0 +1,32 @@
# Generated by Django 2.2.4 on 2019-08-29 14:46
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Timetable',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')),
('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('weekday', models.PositiveSmallIntegerField(choices=[(0, 'Monday'), (1, 'Tuesday'), (2, 'Wednesday'), (3, 'Thursday'), (4, 'Friday'), (5, 'Saturday'), (6, 'Sunday')], verbose_name='Week day')),
('lunch_start', models.TimeField(verbose_name='Lunch start time')),
('lunch_end', models.TimeField(verbose_name='Lunch end time')),
('dinner_start', models.TimeField(verbose_name='Dinner start time')),
('dinner_end', models.TimeField(verbose_name='Dinner end time')),
],
options={
'verbose_name': 'Timetable',
'verbose_name_plural': 'Timetables',
},
),
]

View File

34
apps/timetable/models.py Normal file
View File

@ -0,0 +1,34 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from utils.models import ProjectBaseMixin
class Timetable(ProjectBaseMixin):
"""Timetable model."""
MONDAY = 0
TUESDAY = 1
WEDNESDAY = 2
THURSDAY = 3
FRIDAY = 4
SATURDAY = 5
SUNDAY = 6
WEEKDAYS_CHOICES = (
(MONDAY, _('Monday')),
(TUESDAY, _('Tuesday')),
(WEDNESDAY, _('Wednesday')),
(THURSDAY, _('Thursday')),
(FRIDAY, _('Friday')),
(SATURDAY, _('Saturday')),
(SUNDAY, _('Sunday')))
weekday = models.PositiveSmallIntegerField(choices=WEEKDAYS_CHOICES, verbose_name=_('Week day'))
lunch_start = models.TimeField(verbose_name=_('Lunch start time'))
lunch_end = models.TimeField(verbose_name=_('Lunch end time'))
dinner_start = models.TimeField(verbose_name=_('Dinner start time'))
dinner_end = models.TimeField(verbose_name=_('Dinner end time'))
class Meta:
"""Meta class."""
verbose_name = _('Timetable')
verbose_name_plural = _('Timetables')

View File

@ -0,0 +1,16 @@
"""Serializer for app timetable"""
from rest_framework import serializers
from timetable import models
class TimetableSerializer(serializers.ModelSerializer):
"""Serializer for model Timetable"""
class Meta:
"""Meta class."""
model = models.Timetable
fields = (
'weekday',
'start',
'end',
)

3
apps/timetable/tests.py Normal file
View File

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

10
apps/timetable/urls.py Normal file
View File

@ -0,0 +1,10 @@
"""Timetable app common urlconf."""
from django.urls import path
from timetable import views
app_name = 'timetable'
urlpatterns = [
path('', views.TimetableListView.as_view(), name='list')
]

8
apps/timetable/views.py Normal file
View File

@ -0,0 +1,8 @@
from rest_framework import generics
from timetable import serialziers, models
class TimetableListView(generics.ListAPIView):
"""Method to get timetables"""
serializer_class = serialziers.TimetableSerializer
queryset = models.Timetable.objects.all()

View File

@ -70,6 +70,13 @@ class NotValidUsernameError(exceptions.APIException):
default_detail = _('Wrong username') default_detail = _('Wrong username')
class NotValidTokenError(exceptions.APIException):
"""The exception should be thrown when token in url is not valid
"""
status_code = status.HTTP_400_BAD_REQUEST
default_detail = _('Not valid token')
class NotValidAccessTokenError(exceptions.APIException): class NotValidAccessTokenError(exceptions.APIException):
"""The exception should be thrown when access token in url is not valid """The exception should be thrown when access token in url is not valid
""" """

View File

@ -12,8 +12,6 @@ from rest_framework_simplejwt import tokens
class JWTGenericViewMixin(generics.GenericAPIView): class JWTGenericViewMixin(generics.GenericAPIView):
"""JWT view mixin""" """JWT view mixin"""
JWT_SETTINGS = settings.SIMPLE_JWT
ACCESS_TOKEN_HTTP_ONLY = False ACCESS_TOKEN_HTTP_ONLY = False
ACCESS_TOKEN_SECURE = False ACCESS_TOKEN_SECURE = False
@ -42,8 +40,8 @@ class JWTGenericViewMixin(generics.GenericAPIView):
# Set max_age for tokens # Set max_age for tokens
if permanent: if permanent:
access_token_max_age = self.JWT_SETTINGS.get('ACCESS_TOKEN_LIFETIME_SECONDS') access_token_max_age = settings.SIMPLE_JWT.get('ACCESS_TOKEN_LIFETIME_SECONDS')
refresh_token_max_age = self.JWT_SETTINGS.get('REFRESH_TOKEN_LIFETIME_SECONDS') refresh_token_max_age = settings.SIMPLE_JWT.get('REFRESH_TOKEN_LIFETIME_SECONDS')
else: else:
access_token_max_age = settings.COOKIES_MAX_AGE access_token_max_age = settings.COOKIES_MAX_AGE
refresh_token_max_age = settings.COOKIES_MAX_AGE refresh_token_max_age = settings.COOKIES_MAX_AGE

View File

@ -65,6 +65,7 @@ PROJECT_APPS = [
'partner.apps.PartnerConfig', 'partner.apps.PartnerConfig',
'translation.apps.TranslationConfig', 'translation.apps.TranslationConfig',
'configuration.apps.ConfigurationConfig', 'configuration.apps.ConfigurationConfig',
'timetable.apps.TimetableConfig',
] ]
EXTERNAL_APPS = [ EXTERNAL_APPS = [
@ -368,7 +369,7 @@ PASSWORD_RESET_TIMEOUT_DAYS = 1
# TEMPLATES # TEMPLATES
CONFIRMATION_PASSWORD_RESET_TEMPLATE = 'account/password_reset_confirm.html' CONFIRMATION_PASSWORD_RESET_TEMPLATE = 'account/password_reset_confirm.html'
RESETTING_TOKEN_TEMPLATE = 'account/password_reset_email.html' RESETTING_TOKEN_TEMPLATE = 'account/password_reset_email.html'
CONFIRM_EMAIL_TEMPLATE = 'account/confirm_email.html' CONFIRM_EMAIL_TEMPLATE = 'authorization/confirm_email.html'
# COOKIES # COOKIES
@ -388,3 +389,5 @@ SOLO_CACHE_TIMEOUT = 300
# REDIRECT URL # REDIRECT URL
SITE_REDIRECT_URL_UNSUBSCRIBE = '/unsubscribe/' SITE_REDIRECT_URL_UNSUBSCRIBE = '/unsubscribe/'
SITE_NAME = 'Gault & Millau'

View File

@ -3,7 +3,7 @@
{% trans "Please confirm your email address to complete the registration:" %} {% trans "Please confirm your email address to complete the registration:" %}
{% block signup_confirm %} {% block signup_confirm %}
http://{{ domain_uri }}{% url 'web:account:verify-email-confirm' uidb64=uid token=token %} http://{{ domain_uri }}{% url 'auth:signup-confirm' uidb64=uid token=token %}
{% endblock %} {% endblock %}
{% trans "Thanks for using our site!" %} {% trans "Thanks for using our site!" %}

View File

@ -19,7 +19,7 @@ app_name = 'web'
urlpatterns = [ urlpatterns = [
path('account/', include('account.urls.web')), path('account/', include('account.urls.web')),
path('advertisement/', include('advertisement.urls.web')), path('re_blocks/', include('advertisement.urls.web')),
path('collection/', include('collection.urls.web')), path('collection/', include('collection.urls.web')),
path('establishments/', include('establishment.urls.web')), path('establishments/', include('establishment.urls.web')),
path('news/', include('news.urls.web')), path('news/', include('news.urls.web')),