From 7452f0bef052ed23b325f823d0502e0a3ef583cf Mon Sep 17 00:00:00 2001 From: Anatoly Date: Mon, 23 Dec 2019 12:38:13 +0300 Subject: [PATCH] intermediate commit --- apps/collection/models.py | 4 +++ apps/collection/serializers/common.py | 40 ++++++++++++++++++++++-- apps/collection/urls/back.py | 4 +-- apps/collection/views/back.py | 24 ++++++++++++-- apps/establishment/models.py | 8 +++-- apps/establishment/serializers/common.py | 30 ++++++++++++++++++ apps/establishment/views/web.py | 8 ++--- apps/product/serializers/common.py | 12 +++++++ apps/product/views/common.py | 3 +- apps/utils/serializers.py | 2 +- requirements/base.txt | 3 ++ 11 files changed, 123 insertions(+), 15 deletions(-) diff --git a/apps/collection/models.py b/apps/collection/models.py index 457d3636..c533f75d 100644 --- a/apps/collection/models.py +++ b/apps/collection/models.py @@ -568,6 +568,10 @@ class GuideElementQuerySet(models.QuerySet): """Return GuideElement with type WineNode.""" return self.filter(guide_element_type__name='WineNode') + def descendants(self): + """Return QuerySet with descendants.""" + return self.exclude(guide_element_type__name='Root') + class GuideElement(ProjectBaseMixin, MPTTModel): """Frozen state of elements of guide instance.""" diff --git a/apps/collection/serializers/common.py b/apps/collection/serializers/common.py index c3de708f..f13cbdfb 100644 --- a/apps/collection/serializers/common.py +++ b/apps/collection/serializers/common.py @@ -4,6 +4,9 @@ from collection import models from location import models as location_models from main.serializers import SiteShortSerializer from utils.serializers import TranslatedField +from rest_framework_recursive.fields import RecursiveField +from establishment.serializers import EstablishmentGuideElementSerializer +from product.serializers import ProductGuideElementSerializer class CollectionBaseSerializer(serializers.ModelSerializer): @@ -94,7 +97,8 @@ class GuideBaseSerializer(serializers.ModelSerializer): restaurant_counter = serializers.IntegerField(read_only=True) shop_counter = serializers.IntegerField(read_only=True) wine_counter = serializers.IntegerField(read_only=True) - present_objects_counter = serializers.IntegerField(read_only=True) + count_objects_during_init = serializers.IntegerField(read_only=True, + source='count_related_objects') class Meta: model = models.Guide @@ -115,7 +119,6 @@ class GuideBaseSerializer(serializers.ModelSerializer): 'restaurant_counter', 'shop_counter', 'wine_counter', - 'present_objects_counter', 'count_objects_during_init', ] extra_kwargs = { @@ -166,3 +169,36 @@ class GuideFilterBaseSerializer(serializers.ModelSerializer): """Overridden create method.""" validated_data['guide'] = self.get_guide(validated_data.pop('guide', None)) return super().create(validated_data) + + +class GuideElementBaseSerializer(serializers.ModelSerializer): + """Serializer for model GuideElement.""" + establishment_detail = EstablishmentGuideElementSerializer(read_only=True, + source='establishment') + section_name = serializers.CharField(source='section.name', + allow_null=True) + wine_color_section_name = serializers.CharField(source='wine_color_section.name', + allow_null=True) + node_name = serializers.CharField(source='guide_element_type.name') + label_photo = serializers.ImageField(source='label_photo.image', allow_null=True) + city_name = serializers.CharField(source='city.name', allow_null=True) + product_detail = ProductGuideElementSerializer(read_only=True, source='product') + + parent = RecursiveField(required=False) + + class Meta: + """Meta class.""" + model = models.GuideElement + fields = [ + 'node_name', + 'establishment_detail', + 'review', + 'wine_region', + 'product_detail', + 'priority', + 'city_name', + 'section_name', + 'wine_color_section_name', + 'parent', + 'label_photo', + ] diff --git a/apps/collection/urls/back.py b/apps/collection/urls/back.py index f2297e2e..8f355fd4 100644 --- a/apps/collection/urls/back.py +++ b/apps/collection/urls/back.py @@ -12,8 +12,8 @@ router.register(r'collections', views.CollectionBackOfficeViewSet) urlpatterns = [ path('guides/', views.GuideListCreateView.as_view(), name='guide-list-create'), - # path('guides//elements/', views.GuideElementListView.as_view(), - # name='guide-element-list'), + path('guides//', views.GuideElementListView.as_view(), + name='guide-element-list'), path('guides//filters/', views.GuideFilterCreateView.as_view(), name='guide-filter-list-create'), ] + router.urls diff --git a/apps/collection/views/back.py b/apps/collection/views/back.py index 14107f83..4410b088 100644 --- a/apps/collection/views/back.py +++ b/apps/collection/views/back.py @@ -4,6 +4,7 @@ from rest_framework import mixins, permissions, viewsets from rest_framework import status from rest_framework.filters import OrderingFilter from rest_framework.response import Response +from django.shortcuts import get_object_or_404 from collection import models, serializers from utils.views import BindObjectMixin @@ -27,7 +28,7 @@ class CollectionViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): class GuideBaseView(generics.GenericAPIView): """ViewSet for Guide model.""" serializer_class = serializers.GuideBaseSerializer - permission_classes = (permissions.IsAuthenticated, ) + permission_classes = (permissions.IsAuthenticatedOrReadOnly,) def get_queryset(self): """Overridden get_queryset method.""" @@ -43,7 +44,15 @@ class GuideFilterBaseView(generics.GenericAPIView): pagination_class = None queryset = models.GuideFilter.objects.all() serializer_class = serializers.GuideFilterBaseSerializer - permission_classes = (permissions.IsAuthenticated,) + permission_classes = (permissions.IsAuthenticatedOrReadOnly,) + + +class GuideElementBaseView(generics.GenericAPIView): + """Base view for GuideElement model.""" + pagination_class = None + queryset = models.GuideElement.objects.all() + serializer_class = serializers.GuideElementBaseSerializer + permission_classes = (permissions.IsAuthenticatedOrReadOnly,) class CollectionBackOfficeViewSet(mixins.CreateModelMixin, @@ -98,3 +107,14 @@ class GuideFilterCreateView(GuideFilterBaseView, def post(self, request, *args, **kwargs): super().create(request, *args, **kwargs) return Response(status=status.HTTP_200_OK) + + +class GuideElementListView(GuideElementBaseView, + generics.ListAPIView): + """View for model GuideElement for back office users.""" + + def get_queryset(self): + """Overridden get_queryset method.""" + guide = get_object_or_404(models.Guide.objects.all(), pk=self.kwargs.get('pk')) + return models.GuideElement.objects.get_root_node(guide) \ + .get_descendants() diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 8162aab1..800d5ee7 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -244,8 +244,9 @@ class EstablishmentQuerySet(models.QuerySet): return Subquery( self.similar_base(establishment) .filter(**filters) - .order_by('distance')[:settings.LIMITING_QUERY_OBJECTS] - .values('id') + .order_by('distance') + .distinct() + .values_list('id', flat=True)[:settings.LIMITING_QUERY_OBJECTS] ) def similar_restaurants(self, slug): @@ -263,7 +264,8 @@ class EstablishmentQuerySet(models.QuerySet): 'establishment_gallery__is_main': True, } ) - return self.filter(id__in=ids_by_subquery) \ + # todo: fix this - replace ids_by_subquery.queryset on ids_by_subquery + return self.filter(id__in=ids_by_subquery.queryset) \ .annotate_intermediate_public_mark() \ .annotate_mark_similarity(mark=restaurant.public_mark) \ .order_by('mark_similarity') \ diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 19b4b764..3628a5e0 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -621,3 +621,33 @@ class CompanyBaseSerializer(serializers.ModelSerializer): if models.Company.objects.filter(phones__overlap=phones).exists(): raise serializers.ValidationError({'detail': _('Phones is already reserved.')}) return attrs + + +class EstablishmentGuideElementSerializer(serializers.ModelSerializer): + """Serializer for Guide serializer.""" + type = EstablishmentTypeBaseSerializer(source='establishment_type', read_only=True) + subtypes = EstablishmentSubTypeBaseSerializer(many=True, source='establishment_subtypes') + address = AddressBaseSerializer() + tz = serializers.CharField(read_only=True, source='timezone_as_str') + schedule = ScheduleRUDSerializer(many=True, allow_null=True) + best_price_menu = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True) + best_price_carte = serializers.DecimalField(max_digits=14, decimal_places=2, read_only=True) + range_price_menu = RangePriceSerializer(read_only=True) + range_price_carte = RangePriceSerializer(read_only=True) + currency = CurrencySerializer() + + class Meta(EstablishmentBaseSerializer.Meta): + """Meta class.""" + fields = [ + 'id', + 'type', + 'subtypes', + 'address', + 'tz', + 'schedule', + 'best_price_menu', + 'best_price_carte', + 'range_price_menu', + 'range_price_carte', + 'currency', + ] diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index 7eba8607..a2439727 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -44,7 +44,7 @@ class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView): class EstablishmentSimilarView(EstablishmentListView): """Resource for getting a list of similar establishments.""" serializer_class = serializers.EstablishmentSimilarSerializer - pagination_class = PortionPagination + pagination_class = None class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView): @@ -90,7 +90,7 @@ class RestaurantSimilarListView(EstablishmentSimilarView): """Overridden get_queryset method""" return EstablishmentMixinView.get_queryset(self) \ .has_location() \ - .similar_restaurants(slug=self.kwargs.get('slug')) + .similar_restaurants(slug=self.kwargs.get('slug'))[:settings.QUERY_OUTPUT_OBJECTS] class WinerySimilarListView(EstablishmentSimilarView): @@ -100,7 +100,7 @@ class WinerySimilarListView(EstablishmentSimilarView): """Overridden get_queryset method""" return EstablishmentMixinView.get_queryset(self) \ .has_location() \ - .similar_wineries(slug=self.kwargs.get('slug')) + .similar_wineries(slug=self.kwargs.get('slug'))[:settings.QUERY_OUTPUT_OBJECTS] class ArtisanProducerSimilarListView(EstablishmentSimilarView): @@ -110,7 +110,7 @@ class ArtisanProducerSimilarListView(EstablishmentSimilarView): """Overridden get_queryset method""" return EstablishmentMixinView.get_queryset(self) \ .has_location() \ - .similar_artisans_producers(slug=self.kwargs.get('slug')) + .similar_artisans_producers(slug=self.kwargs.get('slug'))[:settings.QUERY_OUTPUT_OBJECTS] class EstablishmentTypeListView(generics.ListAPIView): diff --git a/apps/product/serializers/common.py b/apps/product/serializers/common.py index 86344a36..72f0abb3 100644 --- a/apps/product/serializers/common.py +++ b/apps/product/serializers/common.py @@ -219,3 +219,15 @@ class ProductCommentCreateSerializer(CommentSerializer): 'content_object': validated_data.pop('product') }) return super().create(validated_data) + + +class ProductGuideElementSerializer(ProductBaseSerializer): + """Serializer for serializing in GuideElement for model Product.""" + + class Meta(ProductBaseSerializer.Meta): + """Meta class.""" + _unused_fields = ('tags', 'wine_regions', 'wine_colors', 'preview_image_url') + fields = ProductBaseSerializer.Meta.fields + + # pop unused fields + for unused_field in _unused_fields: fields.pop(fields.index(unused_field)) diff --git a/apps/product/views/common.py b/apps/product/views/common.py index dbb24e53..f74dacd7 100644 --- a/apps/product/views/common.py +++ b/apps/product/views/common.py @@ -7,6 +7,7 @@ from product import filters, serializers from comment.serializers import CommentRUDSerializer from utils.views import FavoritesCreateDestroyMixinView from utils.pagination import PortionPagination +from django.conf import settings class ProductBaseView(generics.GenericAPIView): @@ -97,5 +98,5 @@ class SimilarListView(ProductSimilarView): """Overridden get_queryset method.""" return super().get_queryset() \ .has_location() \ - .similar(slug=self.kwargs.get('slug')) + .similar(slug=self.kwargs.get('slug'))[:settings.QUERY_OUTPUT_OBJECTS] diff --git a/apps/utils/serializers.py b/apps/utils/serializers.py index 15a58f7b..503c970b 100644 --- a/apps/utils/serializers.py +++ b/apps/utils/serializers.py @@ -72,7 +72,7 @@ class TimeZoneChoiceField(serializers.ChoiceField): class ProjectModelSerializer(serializers.ModelSerializer): - """Overrided ModelSerializer.""" + """Overridden ModelSerializer.""" serializers.ModelSerializer.serializer_field_mapping[models.TJSONField] = TJSONField diff --git a/requirements/base.txt b/requirements/base.txt index 90e5b2d5..9f647275 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -63,3 +63,6 @@ pycountry==19.8.18 # sql-tree django-mptt==0.9.1 + +# For recursive fields +djangorestframework-recursive==0.1.2