From 8cf50ff2cd0e5a8f288f40bd8bb8ff600e9cc7b1 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 13 Nov 2019 12:07:02 +0300 Subject: [PATCH] added endpoints for back office and refactored a little --- apps/news/serializers.py | 3 ++ apps/news/views.py | 22 +-------- apps/product/serializers/back.py | 66 ++++++++++++++++++++++++- apps/product/serializers/common.py | 3 +- apps/product/urls/back.py | 19 +++++-- apps/product/views/back.py | 79 ++++++++++++++++++++++-------- apps/utils/views.py | 26 ++++++++++ 7 files changed, 171 insertions(+), 47 deletions(-) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 95ec21b8..81cbf627 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -290,6 +290,9 @@ class NewsBackOfficeGallerySerializer(serializers.ModelSerializer): news = news_qs.first() image = image_qs.first() + if image in news.gallery.all(): + raise serializers.ValidationError({'detail': _('Image is already added.')}) + attrs['news'] = news attrs['image'] = image diff --git a/apps/news/views.py b/apps/news/views.py index 6c2374bc..883bc343 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -9,6 +9,7 @@ from gallery.tasks import delete_image from news import filters, models, serializers from rating.tasks import add_rating from utils.permissions import IsCountryAdmin, IsContentPageManager +from utils.views import CreateDestroyGalleryViewMixin class NewsMixinView: @@ -84,8 +85,7 @@ class NewsBackOfficeLCView(NewsBackOfficeMixinView, class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView, - generics.CreateAPIView, - generics.DestroyAPIView): + CreateDestroyGalleryViewMixin): """Resource for a create gallery for news for back-office users.""" serializer_class = serializers.NewsBackOfficeGallerySerializer @@ -103,24 +103,6 @@ class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView, return 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 NewsGallery model - gallery_obj.delete() - return Response(status=status.HTTP_204_NO_CONTENT) - class NewsBackOfficeGalleryListView(NewsBackOfficeMixinView, generics.ListAPIView): """Resource for returning gallery for news for back-office users.""" diff --git a/apps/product/serializers/back.py b/apps/product/serializers/back.py index b217db47..9c98dff1 100644 --- a/apps/product/serializers/back.py +++ b/apps/product/serializers/back.py @@ -2,9 +2,10 @@ from django.utils.translation import gettext_lazy as _ from rest_framework import serializers -from product import models -from product.serializers import ProductDetailSerializer from gallery.models import Image +from product import models +from product.serializers import ProductDetailSerializer, ProductTypeBaseSerializer +from tag.models import TagCategory class ProductBackOfficeGallerySerializer(serializers.ModelSerializer): @@ -33,12 +34,16 @@ class ProductBackOfficeGallerySerializer(serializers.ModelSerializer): if not product_qs.exists(): raise serializers.ValidationError({'detail': _('Product not found')}) + if not image_qs.exists(): raise serializers.ValidationError({'detail': _('Image not found')}) product = product_qs.first() image = image_qs.first() + if image in product.gallery.all(): + raise serializers.ValidationError({'detail': _('Image is already added.')}) + attrs['product'] = product attrs['image'] = image @@ -46,8 +51,10 @@ class ProductBackOfficeGallerySerializer(serializers.ModelSerializer): class ProductBackOfficeDetailSerializer(ProductDetailSerializer): + """Product back-office detail serializer.""" class Meta(ProductDetailSerializer.Meta): + """Meta class.""" fields = ProductDetailSerializer.Meta.fields + [ 'description', 'available', @@ -68,3 +75,58 @@ class ProductBackOfficeDetailSerializer(ProductDetailSerializer): 'wine_village': {'write_only': True}, 'state': {'write_only': True}, } + + +class ProductTypeBackOfficeDetailSerializer(ProductTypeBaseSerializer): + """Product type back-office detail serializer.""" + + class Meta(ProductTypeBaseSerializer.Meta): + """Meta class.""" + fields = ProductTypeBaseSerializer.Meta.fields + [ + 'name', + 'index_name', + 'use_subtypes', + ] + extra_kwargs = { + 'name': {'write_only': True}, + 'index_name': {'write_only': True}, + 'use_subtypes': {'write_only': True}, + } + + +class ProductTypeTagCategorySerializer(serializers.ModelSerializer): + """Serializer for attaching tag category to product type.""" + product_type_id = serializers.PrimaryKeyRelatedField( + queryset=models.ProductType.objects.all(), + write_only=True) + tag_category_id = serializers.PrimaryKeyRelatedField( + queryset=TagCategory.objects.all(), + write_only=True) + + class Meta(ProductTypeBaseSerializer.Meta): + """Meta class.""" + fields = ProductTypeBaseSerializer.Meta.fields + [ + 'product_type_id', + 'tag_category_id', + ] + + def validate(self, attrs): + """Validation method.""" + product_type = attrs.pop('product_type_id') + tag_category = attrs.get('tag_category_id') + + if tag_category in product_type.tag_categories.all(): + raise serializers.ValidationError({ + 'detail': _('Tag category is already attached.')}) + + attrs['product_type'] = product_type + attrs['tag_category'] = tag_category + return attrs + + def create(self, validated_data): + """Overridden create method.""" + product_type = validated_data.get('product_type') + tag_category = validated_data.get('tag_category') + + product_type.tag_categories.add(tag_category) + return product_type diff --git a/apps/product/serializers/common.py b/apps/product/serializers/common.py index af1203ff..1562016f 100644 --- a/apps/product/serializers/common.py +++ b/apps/product/serializers/common.py @@ -32,7 +32,8 @@ class ProductSubTypeBaseSerializer(serializers.ModelSerializer): class ProductTypeBaseSerializer(serializers.ModelSerializer): """ProductType base serializer""" name_translated = TranslatedField() - index_name_display = serializers.CharField(source='get_index_name_display') + index_name_display = serializers.CharField(source='get_index_name_display', + read_only=True) class Meta: model = models.ProductType diff --git a/apps/product/urls/back.py b/apps/product/urls/back.py index 5ddc932c..41ed3766 100644 --- a/apps/product/urls/back.py +++ b/apps/product/urls/back.py @@ -1,14 +1,27 @@ """Product backoffice url patterns.""" from django.urls import path -from product.urls.common import urlpatterns as common_urlpatterns + from product import views urlpatterns = [ + path('', views.ProductListCreateBackOfficeView.as_view(), name='list-create'), path('/', views.ProductDetailBackOfficeView.as_view(), name='rud'), path('/gallery/', views.ProductBackOfficeGalleryListView.as_view(), name='gallery-list'), path('/gallery//', views.ProductBackOfficeGalleryCreateDestroyView.as_view(), name='gallery-create-destroy'), -] + # product types + path('types/', views.ProductTypeListCreateBackOfficeView.as_view(), name='type-list-create'), + path('types//', views.ProductTypeRUDBackOfficeView.as_view(), + name='type-retrieve-update-destroy'), + path('types/attach-tag-category/', views.ProductTypeTagCategoryCreateBackOfficeView.as_view(), + name='type-tag-category-create'), + # product sub types + # path('subtypes/', views.ProductSubTypeListCreateBackOfficeView.as_view(), + # name='subtype-list-create'), + # path('subtypes//', views.ProductSubTypeRUDBackOfficeView.as_view(), + # name='subtype-retrieve-update-destroy'), + # path('subtypes/attach-tag-category/', views.ProductSubTypeTagCategoryCreateBackOfficeView.as_view(), + # name='subtype-tag-category-create'), -urlpatterns.extend(common_urlpatterns) +] diff --git a/apps/product/views/back.py b/apps/product/views/back.py index 0298ba18..473daa0c 100644 --- a/apps/product/views/back.py +++ b/apps/product/views/back.py @@ -2,11 +2,12 @@ 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, permissions +from rest_framework import generics, status, permissions, views from rest_framework.response import Response from gallery.tasks import delete_image from product import serializers, models +from utils.views import CreateDestroyGalleryViewMixin class ProductBackOfficeMixinView: @@ -17,9 +18,34 @@ class ProductBackOfficeMixinView: .order_by('-created', ) +class ProductTypeBackOfficeMixinView: + """Product type back-office mixin view.""" + + permission_classes = (permissions.IsAuthenticated,) + queryset = models.ProductType.objects.all() + + +class ProductSubTypeBackOfficeMixinView: + """Product sub type back-office mixin view.""" + + permission_classes = (permissions.IsAuthenticated,) + queryset = models.ProductSubType.objects.all() + + +class BackOfficeListCreateMixin(views.APIView): + """Back-office list-create mixin view.""" + + def check_permissions(self, request): + """ + Check if the request should be permitted. + Raises an appropriate exception if the request is not permitted. + """ + if self.request.method != 'GET': + super().check_permissions(request) + + class ProductBackOfficeGalleryCreateDestroyView(ProductBackOfficeMixinView, - generics.CreateAPIView, - generics.DestroyAPIView): + CreateDestroyGalleryViewMixin): """Resource for a create gallery for product for back-office users.""" serializer_class = serializers.ProductBackOfficeGallerySerializer @@ -37,24 +63,6 @@ class ProductBackOfficeGalleryCreateDestroyView(ProductBackOfficeMixinView, return 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 ProductGallery model - gallery_obj.delete() - return Response(status=status.HTTP_204_NO_CONTENT) - class ProductBackOfficeGalleryListView(ProductBackOfficeMixinView, generics.ListAPIView): """Resource for returning gallery for product for back-office users.""" @@ -78,3 +86,32 @@ class ProductBackOfficeGalleryListView(ProductBackOfficeMixinView, generics.List class ProductDetailBackOfficeView(ProductBackOfficeMixinView, generics.RetrieveUpdateDestroyAPIView): """Product back-office R/U/D view.""" serializer_class = serializers.ProductBackOfficeDetailSerializer + + +class ProductListCreateBackOfficeView(BackOfficeListCreateMixin, ProductBackOfficeMixinView, + generics.ListCreateAPIView): + """Product back-office list-create view.""" + serializer_class = serializers.ProductBackOfficeDetailSerializer + + +class ProductTypeListCreateBackOfficeView(BackOfficeListCreateMixin, ProductTypeBackOfficeMixinView, + generics.ListCreateAPIView): + """Product type back-office list-create view.""" + serializer_class = serializers.ProductTypeBackOfficeDetailSerializer + + +class ProductTypeRUDBackOfficeView(BackOfficeListCreateMixin, ProductTypeBackOfficeMixinView, + generics.RetrieveUpdateDestroyAPIView): + """Product type back-office retrieve-update-destroy view.""" + serializer_class = serializers.ProductTypeBackOfficeDetailSerializer + + +class ProductTypeTagCategoryCreateBackOfficeView(ProductTypeBackOfficeMixinView, + generics.CreateAPIView): + """View for attaching tag category to product type.""" + serializer_class = serializers.ProductTypeTagCategorySerializer + + def create(self, request, *args, **kwargs): + super().create(request, *args, **kwargs) + return Response(status=status.HTTP_201_CREATED) + diff --git a/apps/utils/views.py b/apps/utils/views.py index 8333b34a..d3d09079 100644 --- a/apps/utils/views.py +++ b/apps/utils/views.py @@ -1,10 +1,13 @@ from collections import namedtuple from django.conf import settings +from django.db.transaction import on_commit from rest_framework import generics from rest_framework import status from rest_framework.response import Response +from gallery.tasks import delete_image + # JWT # Login base view mixins @@ -95,3 +98,26 @@ class JWTGenericViewMixin(generics.GenericAPIView): 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)