added filter - establishment_id, refactored guide counters

This commit is contained in:
Anatoly 2020-01-28 17:52:38 +03:00
parent 4604c81ede
commit 61d2f6ec46
6 changed files with 122 additions and 37 deletions

View File

@ -0,0 +1,56 @@
"""Collection app filters."""
from django_filters import rest_framework as filters
from django.core.validators import EMPTY_VALUES
from collection import models
class CollectionFilterSet(filters.FilterSet):
"""Collection filter set."""
establishment_id = filters.NumberFilter(
field_name='establishments__id',
help_text='Establishment id. Allows to filter list of collections by choosen estblishment. '
'Use for Establishment detail\'s sheet to content display within '
'"Collections & Guides" tab.'
)
# "ordering" instead of "o" is for backward compatibility
ordering = filters.OrderingFilter(
# tuple-mapping retains order
fields=(
('rank', 'rank'),
('start', 'start'),
),
help_text='Ordering by fields - rank, start',
)
class Meta:
"""Meta class."""
model = models.Collection
fields = (
'ordering',
'establishment_id',
)
class GuideFilterSet(filters.FilterSet):
"""Guide filter set."""
establishment_id = filters.NumberFilter(
method='by_establishment_id',
help_text='Establishment id. Allows to filter list of guides by choosen establishment. '
'Use for Establishment detail\'s sheet to content display within '
'"Collections & Guides" tab.'
)
class Meta:
"""Meta class."""
model = models.Guide
fields = (
'establishment_id',
)
def by_establishment_id(self, queryset, name, value):
"""Filter by establishment id."""
if value not in EMPTY_VALUES:
return queryset.by_establishment_id(value)
return queryset

View File

@ -171,17 +171,26 @@ class GuideQuerySet(models.QuerySet):
"""Return QuerySet with related."""
return self.select_related('site', )
def with_extended_related(self):
"""Return QuerySet with extended related."""
return self.with_base_related().prefetch_related('guideelement_set')
def by_country_id(self, country_id):
"""Return QuerySet filtered by country code."""
return self.filter(country_json__id__contains=country_id)
def annotate_in_restaurant_section(self):
"""Annotate flag if GuideElement in RestaurantSectionNode."""
restaurant_guides = models.Subquery(
self.filter(
guideelement__guide_element_type__name='EstablishmentNode',
guideelement__parent__guide_element_type__name='RestaurantSectionNode',
).values_list('id', flat=True).distinct()
)
return self.annotate(
in_restaurant_section=models.Case(
models.When(
guideelement__guide_element_type__name='EstablishmentNode',
guideelement__parent__guide_element_type__name='RestaurantSectionNode',
id__in=restaurant_guides,
then=True),
default=False,
output_field=models.BooleanField(default=False)
@ -190,11 +199,16 @@ class GuideQuerySet(models.QuerySet):
def annotate_in_shop_section(self):
"""Annotate flag if GuideElement in ShopSectionNode."""
shop_guides = models.Subquery(
self.filter(
guideelement__guide_element_type__name='EstablishmentNode',
guideelement__parent__guide_element_type__name='ShopSectionNode',
).values_list('guideelement__id', flat=True).distinct()
)
return self.annotate(
in_shop_section=models.Case(
models.When(
guideelement__guide_element_type__name='EstablishmentNode',
guideelement__parent__guide_element_type__name='ShopSectionNode',
id__in=shop_guides,
then=True),
default=False,
output_field=models.BooleanField(default=False)
@ -205,37 +219,60 @@ class GuideQuerySet(models.QuerySet):
"""Return QuerySet with annotated field - restaurant_counter."""
return self.annotate_in_restaurant_section().annotate(
restaurant_counter=models.Count(
'guideelement',
'guideelement__establishment',
filter=models.Q(in_restaurant_section=True) &
models.Q(guideelement__parent_id__isnull=False),
distinct=True))
models.Q(guideelement__parent_id__isnull=True),
distinct=True
)
)
def annotate_shop_counter(self):
"""Return QuerySet with annotated field - shop_counter."""
return self.annotate_in_shop_section().annotate(
shop_counter=models.Count(
'guideelement',
'guideelement__establishment',
filter=models.Q(in_shop_section=True) &
models.Q(guideelement__parent_id__isnull=False),
distinct=True))
models.Q(guideelement__parent_id__isnull=True),
distinct=True
)
)
def annotate_wine_counter(self):
"""Return QuerySet with annotated field - shop_counter."""
return self.annotate_in_restaurant_section().annotate(
wine_counter=models.Count(
'guideelement',
'guideelement__product',
filter=models.Q(guideelement__guide_element_type__name='WineNode') &
models.Q(guideelement__parent_id__isnull=False),
distinct=True))
distinct=True
)
)
def annotate_present_objects_counter(self):
"""Return QuerySet with annotated field - present_objects_counter."""
return self.annotate_in_restaurant_section().annotate(
present_objects_counter=models.Count(
'guideelement',
filter=models.Q(guideelement__guide_element_type__name__in=['EstablishmentNode', 'WineNode']) &
models.Q(guideelement__parent_id__isnull=False),
distinct=True))
return (
self.annotate_restaurant_counter()
.annotate_shop_counter()
.annotate_wine_counter()
.annotate(
present_objects_counter=(
models.F('restaurant_counter') +
models.F('shop_counter') +
models.F('wine_counter')
)
)
)
def annotate_counters(self):
return (
self.annotate_restaurant_counter()
.annotate_shop_counter()
.annotate_wine_counter()
.annotate_present_objects_counter()
)
def by_establishment_id(self, establishment_id: int):
return self.filter(guideelement__establishment=establishment_id).distinct()
class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin):

View File

@ -109,6 +109,7 @@ 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')
@ -131,6 +132,7 @@ class GuideBaseSerializer(serializers.ModelSerializer):
'restaurant_counter',
'shop_counter',
'wine_counter',
'present_objects_counter',
'count_objects_during_init',
]
extra_kwargs = {

View File

@ -1,13 +1,11 @@
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import generics
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 collection import models, serializers
from collection import models, serializers, filters
from collection import tasks
from utils.views import BindObjectMixin
@ -34,12 +32,7 @@ class GuideBaseView(generics.GenericAPIView):
def get_queryset(self):
"""Overridden get_queryset method."""
return models.Guide.objects.with_base_related() \
.annotate_restaurant_counter() \
.annotate_shop_counter() \
.annotate_wine_counter() \
.annotate_present_objects_counter() \
.distinct()
return models.Guide.objects.with_extended_related().annotate_counters()
class GuideFilterBaseView(generics.GenericAPIView):
@ -72,17 +65,14 @@ class CollectionBackOfficeViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
BindObjectMixin,
CollectionViewSet):
"""ViewSet for Collection model for BackOffice users."""
"""ViewSet for Collections list for BackOffice users and Collection create."""
permission_classes = (permissions.IsAuthenticated,)
queryset = models.Collection.objects.with_base_related()
filter_backends = [DjangoFilterBackend, OrderingFilter]
queryset = models.Collection.objects.with_base_related().order_by('-start')
filter_class = filters.CollectionFilterSet
serializer_class = serializers.CollectionBackOfficeSerializer
bind_object_serializer_class = serializers.CollectionBindObjectSerializer
ordering_fields = ('rank', 'start')
ordering = ('-start', )
def perform_binding(self, serializer):
data = serializer.validated_data
collection = data.pop('collection')
@ -106,7 +96,8 @@ class CollectionBackOfficeViewSet(mixins.CreateModelMixin,
class GuideListCreateView(GuideBaseView,
generics.ListCreateAPIView):
"""View for Guide model for BackOffice users."""
"""View for Guides list for BackOffice users and Guide create."""
filter_class = filters.GuideFilterSet
class GuideFilterCreateView(GuideFilterBaseView,

View File

@ -1,8 +1,7 @@
"""Establishment app filters."""
from django.core.validators import EMPTY_VALUES
from django.utils.translation import ugettext_lazy as _
from django_filters import rest_framework as filters, Filter
from django_filters.fields import Lookup
from django_filters import rest_framework as filters
from rest_framework.serializers import ValidationError
from establishment import models

View File

@ -15,7 +15,7 @@ urlpatterns = [
name='create-comment'),
path('slug/<slug:slug>/comments/<int:comment_id>/', views.EstablishmentCommentRUDView.as_view(),
name='rud-comment'),
path('slug/<slug:slug>/favorites/', views.EstablishmentFavoritesCreateDestroyView.as_view(),
path('slug/<slug:slug>/collections/', views.EstablishmentFavoritesCreateDestroyView.as_view(),
name='create-destroy-favorites'),
# similar establishments by type/subtype