from collections import namedtuple from django.conf import settings from django.db.transaction import on_commit from django.shortcuts import get_object_or_404 from rest_framework import generics, status from rest_framework.decorators import action from rest_framework.response import Response from gallery.tasks import delete_image from search_indexes.documents import es_update from news.models import News # JWT # Login base view mixins class JWTGenericViewMixin: """JWT view mixin""" ACCESS_TOKEN_HTTP_ONLY = False ACCESS_TOKEN_SECURE = False 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, access_token: str = None, refresh_token: str = None, permanent: bool = None): """ cookies it is list that contain namedtuples cookies would contain key, value and secure parameters. """ COOKIES = [] if hasattr(self, 'request'): 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): """Update COOKIES in response from namedtuple""" for cookie in cookies: response.set_cookie(key=cookie.key, value=cookie.value, secure=cookie.secure, httponly=cookie.http_only, max_age=cookie.max_age, domain=settings.COOKIE_DOMAIN) 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_only=self.ACCESS_TOKEN_HTTP_ONLY, secure=self.ACCESS_TOKEN_SECURE, max_age=_cookies.get('max_age')), self.COOKIE(key='refresh_token', value=_cookies.get('refresh_token'), http_only=self.REFRESH_TOKEN_HTTP_ONLY, secure=self.REFRESH_TOKEN_SECURE, max_age=_cookies.get('max_age'))] class CreateDestroyGalleryViewMixin(generics.CreateAPIView, generics.DestroyAPIView): """Mixin for creating and destroying entity linked with gallery.""" def create(self, request, *args, **kwargs): """Overridden create method""" super().create(request, *args, **kwargs) return Response(status=status.HTTP_201_CREATED) def destroy(self, request, *args, **kwargs): """Override destroy method.""" gallery_obj = self.get_object() if settings.USE_CELERY: on_commit(lambda: delete_image.delay(image_id=gallery_obj.image.id, completely=False)) else: on_commit(lambda: delete_image(image_id=gallery_obj.image.id, completely=False)) # Delete an instances of Gallery model gallery_obj.delete() return Response(status=status.HTTP_204_NO_CONTENT) class BaseCreateDestroyMixinView(generics.CreateAPIView, generics.DestroyAPIView): """Base Create Destroy mixin.""" _model = None serializer_class = None lookup_field = 'slug' def get_base_object(self): if isinstance(self._model, News): get_object_or_404(self._model, slugs__values__contains=[self.kwargs['slug']]) return get_object_or_404(self._model, slug=self.kwargs['slug']) def es_update_base_object(self): es_update(self.get_base_object()) def perform_create(self, serializer): serializer.save() self.es_update_base_object() def perform_destroy(self, instance): instance.delete() self.es_update_base_object() class FavoritesCreateDestroyMixinView(BaseCreateDestroyMixinView): """Favorites Create Destroy mixin.""" def get_object(self): """ Returns the object the view is displaying. """ obj = self.get_base_object() favorites = get_object_or_404(obj.favorites.filter(user=self.request.user)) # May raise a permission denied self.check_object_permissions(self.request, favorites) return favorites class CarouselCreateDestroyMixinView(BaseCreateDestroyMixinView): """Carousel Create Destroy mixin.""" lookup_field = 'id' def get_base_object(self): establishment_pk = self.kwargs.get('pk') establishment_slug = self.kwargs.get('slug') search_kwargs = {'id': establishment_pk} if establishment_pk else {'slug': establishment_slug} return get_object_or_404(self._model, **search_kwargs) def get_object(self): """ Returns the object the view is displaying. """ obj = self.get_base_object() carousels = get_object_or_404(obj.carousels.all()) # May raise a permission denied # TODO: возможно нужны пермишены # self.check_object_permissions(self.request, carousels) return carousels # BackOffice user`s views & viewsets class BindObjectMixin: """Bind object mixin.""" def get_serializer_class(self): if self.action == 'bind_object': return self.bind_object_serializer_class return self.serializer_class def perform_binding(self, serializer): raise NotImplemented def perform_unbinding(self, serializer): raise NotImplemented @action(methods=['post', 'delete'], detail=True, url_path='bind-object') def bind_object(self, request, pk=None): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) if request.method == 'POST': self.perform_binding(serializer) return Response(serializer.data, status=status.HTTP_201_CREATED) elif request.method == 'DELETE': self.perform_unbinding(serializer) return Response(status=status.HTTP_204_NO_CONTENT)