/', views.FavoritesDestroyView.as_view(), name='remove-from-favorites'),
]
diff --git a/apps/gallery/views.py b/apps/gallery/views.py
index a3b61727..109a01ef 100644
--- a/apps/gallery/views.py
+++ b/apps/gallery/views.py
@@ -6,7 +6,7 @@ from . import models, serializers
class ImageUploadView(generics.CreateAPIView):
"""Upload image to gallery"""
- permission_classes = (IsAuthenticatedAndTokenIsValid, )
model = models.Image
queryset = models.Image.objects.all()
serializer_class = serializers.ImageSerializer
+ permission_classes = (IsAuthenticatedAndTokenIsValid, )
diff --git a/apps/news/views/common.py b/apps/news/views/common.py
index 2678289b..cf3c7e29 100644
--- a/apps/news/views/common.py
+++ b/apps/news/views/common.py
@@ -1,8 +1,9 @@
"""News app common app."""
from rest_framework import generics, permissions
+
from news import filters, models
from news.serializers import common as serializers
-from utils.views import JWTGenericViewMixin, JWTListAPIView
+from utils.views import JWTGenericViewMixin
class NewsMixin:
@@ -18,7 +19,7 @@ class NewsMixin:
.order_by('-is_highlighted', '-created')
-class NewsListView(NewsMixin, JWTListAPIView):
+class NewsListView(NewsMixin, generics.ListAPIView):
"""News list view."""
filter_class = filters.NewsListFilterSet
diff --git a/apps/utils/exceptions.py b/apps/utils/exceptions.py
index b2730b97..df9c2c27 100644
--- a/apps/utils/exceptions.py
+++ b/apps/utils/exceptions.py
@@ -32,15 +32,6 @@ class UserNotFoundError(AuthErrorMixin, ProjectBaseException):
default_detail = _('User not found')
-class PasswordRequestResetExists(ProjectBaseException):
- """
- The exception should be thrown when request for reset password
- is already exists and valid
- """
- status_code = status.HTTP_400_BAD_REQUEST
- default_detail = _('Password request is already exists. Please wait.')
-
-
class EmailSendingError(exceptions.APIException):
"""The exception should be thrown when unable to send an email"""
status_code = status.HTTP_400_BAD_REQUEST
@@ -137,8 +128,17 @@ class WrongAuthCredentials(AuthErrorMixin):
class FavoritesError(exceptions.APIException):
"""
- The exception should be thrown when you item that user
- want add to favorites already exists.
+ The exception should be thrown when item that user
+ want to add to favorites is already exists.
"""
status_code = status.HTTP_400_BAD_REQUEST
default_detail = _('Item is already in favorites.')
+
+
+class PasswordResetRequestExistedError(exceptions.APIException):
+ """
+ The exception should be thrown when password reset request
+ already exists and valid.
+ """
+ status_code = status.HTTP_400_BAD_REQUEST
+ default_detail = _('Password reset request is already exists and valid.')
diff --git a/apps/utils/middleware.py b/apps/utils/middleware.py
index 3cc57319..7203127c 100644
--- a/apps/utils/middleware.py
+++ b/apps/utils/middleware.py
@@ -32,3 +32,13 @@ def parse_cookies(get_response):
return response
return middleware
+
+class CORSMiddleware:
+ """Added parameter {Access-Control-Allow-Origin: *} to response"""
+ def __init__(self, get_response):
+ self.get_response = get_response
+
+ def __call__(self, request):
+ response = self.get_response(request)
+ response["Access-Control-Allow-Origin"] = '*'
+ return response
diff --git a/apps/utils/tokens.py b/apps/utils/tokens.py
index e686b42b..a236bfa3 100644
--- a/apps/utils/tokens.py
+++ b/apps/utils/tokens.py
@@ -33,12 +33,11 @@ class GMBlacklistMixin(BlacklistMixin):
"""
@classmethod
- def for_user_by_source(cls, user, source: int):
+ def for_user(cls, user, source: int = None):
"""Create a refresh token."""
token = super().for_user(user)
token['user'] = user.get_user_info()
- # Create a record in DB
- JWTRefreshToken.objects.add_to_db(user=user, token=token, source=source)
+ JWTRefreshToken.objects.make(user=user, token=token, source=source)
return token
@@ -70,7 +69,7 @@ class GMRefreshToken(GMBlacklistMixin, GMToken, RefreshToken):
# Create a record in DB
user = User.objects.get(id=self.payload.get('user_id'))
- JWTAccessToken.objects.add_to_db(user=user,
- access_token=access_token,
- refresh_token=self)
+ JWTAccessToken.objects.make(user=user,
+ access_token=access_token,
+ refresh_token=self)
return access_token
diff --git a/apps/utils/views.py b/apps/utils/views.py
index d01d30cd..9af89360 100644
--- a/apps/utils/views.py
+++ b/apps/utils/views.py
@@ -7,7 +7,7 @@ from rest_framework.response import Response
# JWT
-# Login base view mixin
+# Login base view mixins
class JWTGenericViewMixin(generics.GenericAPIView):
"""JWT view mixin"""
@@ -16,6 +16,13 @@ class JWTGenericViewMixin(generics.GenericAPIView):
REFRESH_TOKEN_HTTP_ONLY = False
REFRESH_TOKEN_SECURE = False
+
+ LOCALE_HTTP_ONLY = False
+ LOCALE_SECURE = False
+
+ COUNTRY_CODE_HTTP_ONLY = False
+ COUNTRY_CODE_SECURE = False
+
COOKIE = namedtuple('COOKIE', ['key', 'value', 'http_only', 'secure', 'max_age'])
def _put_data_in_cookies(self,
@@ -26,21 +33,32 @@ class JWTGenericViewMixin(generics.GenericAPIView):
cookies it is list that contain namedtuples
cookies would contain key, value and secure parameters.
"""
- COOKIES = list()
+ COOKIES = []
- # Write to cookie access and refresh token with secure flag
- if access_token and refresh_token:
- _access_token = self.COOKIE(key='access_token',
- value=access_token,
- http_only=self.ACCESS_TOKEN_HTTP_ONLY,
- secure=self.ACCESS_TOKEN_SECURE,
- max_age=settings.COOKIES_MAX_AGE if permanent else None)
- _refresh_token = self.COOKIE(key='refresh_token',
- value=refresh_token,
- http_only=self.REFRESH_TOKEN_HTTP_ONLY,
- secure=self.REFRESH_TOKEN_SECURE,
- max_age=settings.COOKIES_MAX_AGE if permanent else None)
- COOKIES.extend((_access_token, _refresh_token))
+ if hasattr(self.request, 'locale'):
+ COOKIES.append(self.COOKIE(key='locale',
+ value=self.request.locale,
+ http_only=self.ACCESS_TOKEN_HTTP_ONLY,
+ secure=self.LOCALE_SECURE,
+ max_age=settings.COOKIES_MAX_AGE if permanent else None))
+ if hasattr(self.request, 'country_code'):
+ COOKIES.append(self.COOKIE(key='country_code',
+ value=self.request.country_code,
+ http_only=self.COUNTRY_CODE_HTTP_ONLY,
+ secure=self.COUNTRY_CODE_SECURE,
+ max_age=settings.COOKIES_MAX_AGE if permanent else None))
+ if access_token:
+ COOKIES.append(self.COOKIE(key='access_token',
+ value=access_token,
+ http_only=self.ACCESS_TOKEN_HTTP_ONLY,
+ secure=self.ACCESS_TOKEN_SECURE,
+ max_age=settings.COOKIES_MAX_AGE if permanent else None))
+ if refresh_token:
+ COOKIES.append(self.COOKIE(key='refresh_token',
+ value=refresh_token,
+ http_only=self.REFRESH_TOKEN_HTTP_ONLY,
+ secure=self.REFRESH_TOKEN_SECURE,
+ max_age=settings.COOKIES_MAX_AGE if permanent else None))
return COOKIES
def _put_cookies_in_response(self, cookies: list, response: Response):
@@ -77,102 +95,3 @@ class JWTGenericViewMixin(generics.GenericAPIView):
http_only=self.REFRESH_TOKEN_HTTP_ONLY,
secure=self.REFRESH_TOKEN_SECURE,
max_age=_cookies.get('max_age'))]
-
-
-class JWTListAPIView(JWTGenericViewMixin, generics.ListAPIView):
- """
- Concrete view for creating a model instance.
- """
- def get(self, request, *args, **kwargs):
- queryset = self.filter_queryset(self.get_queryset())
- page = self.paginate_queryset(queryset)
- if page is not None:
- serializer = self.get_serializer(page, many=True)
- response = self.get_paginated_response(serializer.data)
- else:
- serializer = self.get_serializer(queryset, many=True)
- response = Response(serializer.data)
- access_token, refresh_token = self._get_tokens_from_cookies(request)
- return self._put_cookies_in_response(
- cookies=self._put_data_in_cookies(access_token=access_token.value,
- refresh_token=refresh_token.value),
- response=response)
-
-
-class JWTCreateAPIView(JWTGenericViewMixin, generics.CreateAPIView):
- """
- Concrete view for creating a model instance.
- """
- def post(self, request, *args, **kwargs):
- serializer = self.get_serializer(data=request.data)
- serializer.is_valid(raise_exception=True)
- serializer.save()
- response = Response(serializer.data, status=status.HTTP_201_CREATED)
- access_token, refresh_token = self._get_tokens_from_cookies(request)
- return self._put_cookies_in_response(
- cookies=self._put_data_in_cookies(access_token=access_token.value,
- refresh_token=refresh_token.value),
- response=response)
-
-
-class JWTRetrieveAPIView(JWTGenericViewMixin, generics.RetrieveAPIView):
- """
- Concrete view for retrieving a model instance.
- """
- def get(self, request, *args, **kwargs):
- """Implement GET method"""
- queryset = self.filter_queryset(self.get_queryset())
- page = self.paginate_queryset(queryset)
- if page is not None:
- serializer = self.get_serializer(page, many=True)
- response = self.get_paginated_response(serializer.data)
- else:
- serializer = self.get_serializer(queryset, many=True)
- response = Response(serializer.data, status.HTTP_200_OK)
- access_token, refresh_token = self._get_tokens_from_cookies(request)
- return self._put_cookies_in_response(
- cookies=self._put_data_in_cookies(access_token=access_token,
- refresh_token=refresh_token),
- response=response)
-
-
-class JWTDestroyAPIView(JWTGenericViewMixin, generics.DestroyAPIView):
- """
- Concrete view for deleting a model instance.
- """
- def delete(self, request, *args, **kwargs):
- instance = self.get_object()
- instance.delete()
- response = Response(status=status.HTTP_204_NO_CONTENT)
- access_token, refresh_token = self._get_tokens_from_cookies(request)
- return self._put_cookies_in_response(
- cookies=self._put_data_in_cookies(access_token=access_token,
- refresh_token=refresh_token),
- response=response)
-
-
-class JWTUpdateAPIView(JWTGenericViewMixin, generics.UpdateAPIView):
- """
- Concrete view for updating a model instance.
- """
- def put(self, request, *args, **kwargs):
- partial = kwargs.pop('partial', False)
- instance = self.get_object()
- serializer = self.get_serializer(instance, data=request.data, partial=partial)
- serializer.is_valid(raise_exception=True)
- serializer.save()
- if getattr(instance, '_prefetched_objects_cache', None):
- # If 'prefetch_related' has been applied to a queryset, we need to
- # forcibly invalidate the prefetch cache on the instance.
- instance._prefetched_objects_cache = {}
- response = Response(serializer.data)
- access_token, refresh_token = self._get_tokens_from_cookies(request)
- return self._put_cookies_in_response(
- cookies=self._put_data_in_cookies(access_token=access_token,
- refresh_token=refresh_token),
- response=response)
-
- def patch(self, request, *args, **kwargs):
- kwargs['partial'] = True
- return self.put(request, *args, **kwargs)
-
diff --git a/project/settings/base.py b/project/settings/base.py
index fceaa632..16ac6002 100644
--- a/project/settings/base.py
+++ b/project/settings/base.py
@@ -79,7 +79,6 @@ EXTERNAL_APPS = [
'rest_framework',
'rest_framework.authtoken',
'easy_select2',
- 'corsheaders',
'oauth2_provider',
'social_django',
'rest_framework_social_oauth2',
@@ -87,6 +86,7 @@ EXTERNAL_APPS = [
'rest_framework_simplejwt.token_blacklist',
'solo',
'phonenumber_field',
+ 'corsheaders',
]
@@ -333,10 +333,6 @@ THUMBNAIL_ALIASES = {
# Password reset
RESETTING_TOKEN_EXPIRATION = 24 # hours
-# CORS Config
-CORS_ORIGIN_ALLOW_ALL = True
-CORS_ALLOW_CREDENTIALS = True
-
GEOIP_PATH = os.path.join(PROJECT_ROOT, 'geoip_db')
@@ -373,7 +369,6 @@ PASSWORD_RESET_TIMEOUT_DAYS = 1
# TEMPLATES
-CONFIRMATION_PASSWORD_RESET_TEMPLATE = 'account/password_reset_confirm.html'
RESETTING_TOKEN_TEMPLATE = 'account/password_reset_email.html'
CHANGE_EMAIL_TEMPLATE = 'account/change_email.html'
CONFIRM_EMAIL_TEMPLATE = 'authorization/confirm_email.html'
@@ -381,6 +376,12 @@ CONFIRM_EMAIL_TEMPLATE = 'authorization/confirm_email.html'
# COOKIES
COOKIES_MAX_AGE = 86400 # 24 hours
+SESSION_COOKIE_SAMESITE = None
+
+
+# CORS
+CORS_ORIGIN_ALLOW_ALL = True
+CORS_ALLOW_CREDENTIALS = True
# UPLOAD FILES
diff --git a/project/settings/stage.py b/project/settings/stage.py
new file mode 100644
index 00000000..bbb2f245
--- /dev/null
+++ b/project/settings/stage.py
@@ -0,0 +1,13 @@
+"""Stage settings."""
+from .base import *
+
+ALLOWED_HOSTS = ['gm-stage.id-east.ru', '95.213.204.126']
+
+SEND_SMS = False
+SMS_CODE_SHOW = True
+USE_CELERY = False
+
+SCHEMA_URI = 'https'
+DEFAULT_SUBDOMAIN = 'www'
+SITE_DOMAIN_URI = 'id-east.ru'
+DOMAIN_URI = 'gm-stage.id-east.ru'
diff --git a/project/templates/account/change_email.html b/project/templates/account/change_email.html
index ceec753f..40c1b227 100644
--- a/project/templates/account/change_email.html
+++ b/project/templates/account/change_email.html
@@ -2,9 +2,8 @@
{% blocktrans %}You're receiving this email because you want to change email address at {{ site_name }}.{% endblocktrans %}
{% trans "Please go to the following page for confirmation new email address:" %}
-{% block reset_link %}
-http://{{ domain_uri }}{% url 'web:account:change-email-confirm' uidb64=uidb64 token=token %}
-{% endblock %}
+
+https://{{ country_code }}.{{ domain_uri }}/registered/{{ uidb64 }}/{{ token }}/
{% trans "Thanks for using our site!" %}
diff --git a/project/templates/account/password_reset_confirm.html b/project/templates/account/password_reset_confirm.html
deleted file mode 100644
index 62cdb8eb..00000000
--- a/project/templates/account/password_reset_confirm.html
+++ /dev/null
@@ -1,31 +0,0 @@
-{% load i18n static %}
-
-{% block content %}
-
-{% if validlink %}
-
-{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}
-
-
-
-{% else %}
-
-{% trans "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %}
-
-{% endif %}
-
-{% endblock %}
\ No newline at end of file
diff --git a/project/templates/account/password_reset_email.html b/project/templates/account/password_reset_email.html
index 1a395cee..ee84fd0b 100644
--- a/project/templates/account/password_reset_email.html
+++ b/project/templates/account/password_reset_email.html
@@ -2,9 +2,8 @@
{% blocktrans %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %}
{% trans "Please go to the following page and choose a new password:" %}
-{% block reset_link %}
-http://{{ domain_uri }}{% url 'web:account:form-password-reset-confirm' uidb64=uidb64 token=token %}
-{% endblock %}
+
+https://{{ country_code }}.{{ domain_uri }}/recovery/{{ uidb64 }}/{{ token }}/
{% trans "Thanks for using our site!" %}
diff --git a/project/templates/authorization/confirm_email.html b/project/templates/authorization/confirm_email.html
index 7fa06aa5..f3bbd50e 100644
--- a/project/templates/authorization/confirm_email.html
+++ b/project/templates/authorization/confirm_email.html
@@ -2,9 +2,8 @@
{% blocktrans %}You're receiving this email because you trying to register new account at {{ site_name }}.{% endblocktrans %}
{% trans "Please confirm your email address to complete the registration:" %}
-{% block signup_confirm %}
-http://{{ domain_uri }}{% url 'auth:signup-confirm' uidb64=uidb64 token=token %}
-{% endblock %}
+
+https://{{ country_code }}.{{ domain_uri }}/registered/{{ uidb64 }}/{{ token }}/
{% trans "Thanks for using our site!" %}
diff --git a/project/urls/mobile.py b/project/urls/mobile.py
index c007e260..0bcbd31c 100644
--- a/project/urls/mobile.py
+++ b/project/urls/mobile.py
@@ -3,10 +3,11 @@ from django.urls import path, include
app_name = 'mobile'
urlpatterns = [
+ path('establishments/', include('establishment.urls.mobile')),
# path('account/', include('account.urls.web')),
# path('advertisement/', include('advertisement.urls.web')),
# path('collection/', include('collection.urls.web')),
# path('establishments/', include('establishment.urls.web')),
# path('news/', include('news.urls.web')),
# path('partner/', include('partner.urls.web')),
-]
\ No newline at end of file
+]