From 755f007d25324e1cfb3c0de5f1010c8a9327fcb3 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 12 Dec 2019 16:09:43 +0300 Subject: [PATCH 01/14] clone news API method --- apps/news/serializers.py | 28 ++++++++++++++++++++++++++++ apps/news/urls/back.py | 1 + apps/news/views.py | 9 ++++++++- 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 41b6ebc4..f2d6f27e 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -13,6 +13,8 @@ from tag.serializers import TagBaseSerializer from utils import exceptions as utils_exceptions from utils.serializers import (TranslatedField, ProjectModelSerializer, FavoritesCreateSerializer, ImageBaseSerializer, CarouselCreateSerializer) +from rating import models as rating_models +from django.shortcuts import get_object_or_404 class AgendaSerializer(ProjectModelSerializer): @@ -327,3 +329,29 @@ class NewsCarouselCreateSerializer(CarouselCreateSerializer): 'content_object': validated_data.pop('news') }) return super().create(validated_data) + + +class NewsCloneCreateSerializer(NewsBackOfficeBaseSerializer, + NewsDetailSerializer): + """Serializer for creating news clone.""" + template_display = serializers.CharField(source='get_template_display', + read_only=True) + class Meta(NewsBackOfficeBaseSerializer.Meta, NewsDetailSerializer.Meta): + fields = NewsBackOfficeBaseSerializer.Meta.fields + NewsDetailSerializer.Meta.fields + ( + 'template_display', + ) + read_only_fields = fields + + def create(self, validated_data): + kwargs = self.context.get('request').parser_context.get('kwargs') + instance = get_object_or_404(models.News, pk=kwargs['pk']) + new_country = get_object_or_404(location_models.Country, code=kwargs['country_code']) + view_count_model = rating_models.ViewCount.objects.create(count=0) + instance.pk = None + instance.state = models.News.WAITING + instance.slugs = {locale: f'{slug}-{kwargs["country_code"]}'for locale, slug in instance.slugs.items()} + instance.country = new_country + instance.views_count = view_count_model + instance.save() + return instance + diff --git a/apps/news/urls/back.py b/apps/news/urls/back.py index 982e7810..e45a7337 100644 --- a/apps/news/urls/back.py +++ b/apps/news/urls/back.py @@ -14,4 +14,5 @@ urlpatterns = [ path('/gallery//', views.NewsBackOfficeGalleryCreateDestroyView.as_view(), name='gallery-create-destroy'), path('/carousels/', views.NewsCarouselCreateDestroyView.as_view(), name='create-destroy-carousels'), + path('/clone/', views.NewsCloneView.as_view(), name='create-destroy-carousels'), ] diff --git a/apps/news/views.py b/apps/news/views.py index 54868e52..c8acb3ac 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -7,7 +7,7 @@ from news import filters, models, serializers from rating.tasks import add_rating from utils.permissions import IsCountryAdmin, IsContentPageManager from utils.views import CreateDestroyGalleryViewMixin, FavoritesCreateDestroyMixinView, CarouselCreateDestroyMixinView -from utils.serializers import ImageBaseSerializer +from utils.serializers import ImageBaseSerializer, EmptySerializer class NewsMixinView: @@ -167,3 +167,10 @@ class NewsCarouselCreateDestroyView(CarouselCreateDestroyMixinView): _model = models.News serializer_class = serializers.NewsCarouselCreateSerializer + + +class NewsCloneView(generics.CreateAPIView): + """View for creating clone News""" + permission_classes = (permissions.AllowAny, ) + serializer_class = serializers.NewsCloneCreateSerializer + queryset = models.News.objects.all() From bd3dd47742e2ae1685a34949377589ab891791f8 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 12 Dec 2019 16:27:40 +0300 Subject: [PATCH 02/14] slugs news --- apps/news/serializers.py | 11 ++++++++++- apps/search_indexes/serializers.py | 7 ++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index f2d6f27e..896ff420 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -15,6 +15,7 @@ from utils.serializers import (TranslatedField, ProjectModelSerializer, FavoritesCreateSerializer, ImageBaseSerializer, CarouselCreateSerializer) from rating import models as rating_models from django.shortcuts import get_object_or_404 +from utils.models import get_current_locale, get_default_locale class AgendaSerializer(ProjectModelSerializer): @@ -70,6 +71,13 @@ class NewsBaseSerializer(ProjectModelSerializer): tags = TagBaseSerializer(read_only=True, many=True, source='visible_tags') in_favorites = serializers.BooleanField(allow_null=True, read_only=True) view_counter = serializers.IntegerField(read_only=True) + slug = serializers.SerializerMethodField(read_only=True, allow_null=True) + + def get_slug(self, obj): + if obj.slugs: + return obj.slugs.get(get_current_locale()) \ + or obj.slugs.get(get_default_locale()) \ + or next(iter(obj.slugs.values())) class Meta: """Meta class.""" @@ -77,12 +85,12 @@ class NewsBaseSerializer(ProjectModelSerializer): model = models.News fields = ( 'id', + 'slug', 'title_translated', 'subtitle_translated', 'is_highlighted', 'news_type', 'tags', - 'slugs', 'view_counter', ) @@ -173,6 +181,7 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer): 'title', 'backoffice_title', 'subtitle', + 'slugs', 'is_published', ) extra_kwargs = { diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 4fc5a027..4cad05fc 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -222,6 +222,7 @@ class NewsDocumentSerializer(InFavoritesMixin, DocumentSerializer): subtitle_translated = serializers.SerializerMethodField(allow_null=True) news_type = NewsTypeSerializer() tags = TagsDocumentSerializer(many=True, source='visible_tags') + slug = serializers.SerializerMethodField(allow_null=True) class Meta: """Meta class.""" @@ -237,9 +238,13 @@ class NewsDocumentSerializer(InFavoritesMixin, DocumentSerializer): 'news_type', 'tags', 'start', - 'slugs', + 'slug', ) + @staticmethod + def get_slug(obj): + return get_translated_value(obj.slugs) + @staticmethod def get_title_translated(obj): return get_translated_value(obj.title) From fcf3999d3d009feefefc2ddb89a56332039829c8 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 12 Dec 2019 16:51:55 +0300 Subject: [PATCH 03/14] news duplication date --- .../migrations/0042_news_duplication_date.py | 18 ++++++++++++++++++ apps/news/models.py | 13 ++++++++++++- apps/news/serializers.py | 9 +++------ 3 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 apps/news/migrations/0042_news_duplication_date.py diff --git a/apps/news/migrations/0042_news_duplication_date.py b/apps/news/migrations/0042_news_duplication_date.py new file mode 100644 index 00000000..61a258d3 --- /dev/null +++ b/apps/news/migrations/0042_news_duplication_date.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.7 on 2019-12-12 13:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0041_auto_20191211_1528'), + ] + + operations = [ + migrations.AddField( + model_name='news', + name='duplication_date', + field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='Duplication datetime'), + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index b4e1b5d5..6ebdca76 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -211,6 +211,8 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi verbose_name=_('banner')) site = models.ForeignKey('main.SiteSettings', blank=True, null=True, on_delete=models.SET_NULL, verbose_name=_('site settings')) + duplication_date = models.DateTimeField(blank=True, null=True, default=None, + verbose_name=_('Duplication datetime')) objects = NewsQuerySet.as_manager() class Meta: @@ -220,7 +222,16 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi verbose_name_plural = _('news') def __str__(self): - return f'news: {self.slug}' + return f'news: {next(iter(self.slugs.values()))}' + + def create_duplicate(self, new_country, view_count_model): + self.pk = None + self.state = self.WAITING + self.slugs = {locale: f'{slug}-{new_country.code}' for locale, slug in self.slugs.items()} + self.country = new_country + self.views_count = view_count_model + self.duplication_date = timezone.now() + self.save() @property def is_publish(self): diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 896ff420..5b9a8162 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -183,9 +183,11 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer): 'subtitle', 'slugs', 'is_published', + 'duplication_date', ) extra_kwargs = { 'backoffice_title': {'allow_null': False}, + 'duplication_date': {'read_only': True}, } def create(self, validated_data): @@ -356,11 +358,6 @@ class NewsCloneCreateSerializer(NewsBackOfficeBaseSerializer, instance = get_object_or_404(models.News, pk=kwargs['pk']) new_country = get_object_or_404(location_models.Country, code=kwargs['country_code']) view_count_model = rating_models.ViewCount.objects.create(count=0) - instance.pk = None - instance.state = models.News.WAITING - instance.slugs = {locale: f'{slug}-{kwargs["country_code"]}'for locale, slug in instance.slugs.items()} - instance.country = new_country - instance.views_count = view_count_model - instance.save() + instance.create_duplicate(new_country, view_count_model) return instance From f565285abdb74454fe143ccaf5cd7e614fc30709 Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 12 Dec 2019 16:58:13 +0300 Subject: [PATCH 04/14] back menu filter by establishment --- apps/establishment/views/back.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index 6e2c953a..ad5eafdf 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -1,6 +1,7 @@ """Establishment app views.""" from django.http import Http404, HttpResponse from django.shortcuts import get_object_or_404 +from django_filters.rest_framework import DjangoFilterBackend from rest_framework import generics, permissions, status from establishment import filters, models, serializers @@ -41,7 +42,7 @@ class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView): """Establishment schedule RUD view""" lookup_field = 'slug' serializer_class = ScheduleRUDSerializer - permission_classes = [IsWineryReviewer |IsEstablishmentManager] + permission_classes = [IsWineryReviewer | IsEstablishmentManager] def get_object(self): """ @@ -75,6 +76,11 @@ class MenuListCreateView(generics.ListCreateAPIView): serializer_class = serializers.MenuSerializers queryset = models.Menu.objects.all() permission_classes = [IsWineryReviewer | IsEstablishmentManager] + filter_backends = (DjangoFilterBackend,) + filterset_fields = ( + 'establishment', + 'establishment__slug', + ) class MenuRUDView(generics.RetrieveUpdateDestroyAPIView): @@ -161,7 +167,7 @@ class EmailRUDView(generics.RetrieveUpdateDestroyAPIView): class EmployeeListCreateView(generics.ListCreateAPIView): """Emplyoee list create view.""" - permission_classes = (permissions.AllowAny, ) + permission_classes = (permissions.AllowAny,) filter_class = filters.EmployeeBackFilter serializer_class = serializers.EmployeeBackSerializers queryset = models.Employee.objects.all() @@ -170,7 +176,7 @@ class EmployeeListCreateView(generics.ListCreateAPIView): class EstablishmentEmployeeListView(generics.ListCreateAPIView): """Establishment emplyoees list view.""" - permission_classes = (permissions.AllowAny, ) + permission_classes = (permissions.AllowAny,) serializer_class = serializers.EstablishmentEmployeeBackSerializer def get_queryset(self): @@ -352,8 +358,8 @@ class EstablishmentEmployeeCreateView(generics.CreateAPIView): class EstablishmentEmployeeDeleteView(generics.DestroyAPIView): def _get_object_to_delete(self, establishment_id, employee_id): - result_qs = models.EstablishmentEmployee\ - .objects\ + result_qs = models.EstablishmentEmployee \ + .objects \ .filter(establishment_id=establishment_id, employee_id=employee_id) if not result_qs.exists(): raise Http404 @@ -371,6 +377,6 @@ class EstablishmentPositionListView(generics.ListAPIView): """Establishment positions list view.""" pagination_class = None - permission_classes = (permissions.AllowAny, ) + permission_classes = (permissions.AllowAny,) queryset = models.Position.objects.all() serializer_class = serializers.PositionBackSerializer From b58d54c07effffb64ddd30c6bf38ad82f0f49862 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 12 Dec 2019 17:06:30 +0300 Subject: [PATCH 05/14] smallfix --- apps/tag/serializers.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py index 2fea5ad0..f6266060 100644 --- a/apps/tag/serializers.py +++ b/apps/tag/serializers.py @@ -123,19 +123,7 @@ class FiltersTagCategoryBaseSerializer(serializers.ModelSerializer): return obj in ['open_now', ] def get_param_name(self, obj): - if obj == 'service': - return 'tags_id__in' - - elif obj == 'pop': - return 'tags_id__in' - - elif obj == 'open_now': - return 'open_now' - - elif obj == 'wine_region': - return 'wine_region_id__in' - - return '%s__in' % obj.index_name + return 'tags_id__in' def get_fields(self, *args, **kwargs): fields = super(FiltersTagCategoryBaseSerializer, self).get_fields() From e2ec179f69f097cc41f4b961ff65c38265c6a9eb Mon Sep 17 00:00:00 2001 From: Anatoly Date: Thu, 12 Dec 2019 18:10:20 +0300 Subject: [PATCH 06/14] refactored the mechanism for finding similar establishments --- apps/establishment/models.py | 69 +++++++++++++++++++++++++------ apps/establishment/urls/common.py | 4 +- apps/establishment/views/web.py | 13 +++++- 3 files changed, 70 insertions(+), 16 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 9ecf38c2..ab7f14fa 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -213,7 +213,13 @@ class EstablishmentQuerySet(models.QuerySet): )) def similar_base(self, establishment): - + """ + Return filtered QuerySet by base filters. + Filters including: + 1 Filter by type (and subtype) establishment. + 2 Filter by published Review. + 3 With annotated distance. + """ filters = { 'reviews__status': Review.READY, 'establishment_type': establishment.establishment_type, @@ -224,27 +230,64 @@ class EstablishmentQuerySet(models.QuerySet): .filter(**filters) \ .annotate_distance(point=establishment.location) + def similar_base_subquery(self, establishment, filters: dict) -> Subquery: + """ + Return filtered Subquery object by filters. + Filters including: + 1 Filter by transmitted filters. + 2 With ordering by distance. + """ + return Subquery( + self.similar_base(establishment) + .filter(**filters) + .order_by('distance')[:settings.LIMITING_QUERY_OBJECTS] + .values('id') + ) + def similar_restaurants(self, slug): """ Return QuerySet with objects that similar to Restaurant. - :param restaurant_slug: str Establishment slug + :param slug: str restaurant slug """ - restaurant_qs = self.filter(slug=slug, - public_mark__isnull=False) + restaurant_qs = self.filter(slug=slug) if restaurant_qs.exists(): - establishment = restaurant_qs.first() - subquery_filter_by_distance = Subquery( - self.similar_base(establishment) - .filter(public_mark__gte=10, - establishment_gallery__is_main=True) - .order_by('distance')[:settings.LIMITING_QUERY_OBJECTS] - .values('id') + restaurant = restaurant_qs.first() + ids_by_subquery = self.similar_base_subquery( + establishment=restaurant, + filters={ + 'public_mark__gte': 10, + 'establishment_gallery__is_main': True, + } ) - return self.filter(id__in=subquery_filter_by_distance) \ + return self.filter(id__in=ids_by_subquery) \ .annotate_intermediate_public_mark() \ - .annotate_mark_similarity(mark=establishment.public_mark) \ + .annotate_mark_similarity(mark=restaurant.public_mark) \ .order_by('mark_similarity') \ .distinct('mark_similarity', 'id') + else: + return self.none() + + def similar_artisans(self, slug): + """ + Return QuerySet with objects that similar to Artisan. + :param slug: str artisan slug + """ + artisan_qs = self.filter(slug=slug) + if artisan_qs.exists(): + artisan = artisan_qs.first() + ids_by_subquery = self.similar_base_subquery( + establishment=artisan, + filters={ + 'public_mark__gte': 10, + } + ) + return self.filter(id__in=ids_by_subquery) \ + .annotate_intermediate_public_mark() \ + .annotate_mark_similarity(mark=artisan.public_mark) \ + .order_by('mark_similarity') \ + .distinct('mark_similarity', 'id') + else: + return self.none() def by_wine_region(self, wine_region): """ diff --git a/apps/establishment/urls/common.py b/apps/establishment/urls/common.py index 68ba2b16..046667df 100644 --- a/apps/establishment/urls/common.py +++ b/apps/establishment/urls/common.py @@ -21,6 +21,8 @@ urlpatterns = [ path('slug//similar/', views.RestaurantSimilarListView.as_view(), name='similar-restaurants'), path('slug//similar/wineries/', views.WinerySimilarListView.as_view(), - name='similar-restaurants'), + name='similar-wineries'), + path('slug//similar/artisans/', views.ArtisanSimilarListView.as_view(), + name='similar-artisans'), ] diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index 9e6dc026..4253b6a6 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -87,7 +87,7 @@ class RestaurantSimilarListView(EstablishmentSimilarList): """Resource for getting a list of similar restaurants.""" def get_queryset(self): - """Override get_queryset method""" + """Overridden get_queryset method""" return EstablishmentMixinView.get_queryset(self) \ .similar_restaurants(slug=self.kwargs.get('slug')) @@ -96,11 +96,20 @@ class WinerySimilarListView(EstablishmentSimilarList): """Resource for getting a list of similar wineries.""" def get_queryset(self): - """Override get_queryset method""" + """Overridden get_queryset method""" return EstablishmentMixinView.get_queryset(self) \ .similar_wineries(slug=self.kwargs.get('slug')) +class ArtisanSimilarListView(EstablishmentSimilarList): + """Resource for getting a list of similar artisans.""" + + def get_queryset(self): + """Overridden get_queryset method""" + return EstablishmentMixinView.get_queryset(self) \ + .similar_artisans(slug=self.kwargs.get('slug')) + + class EstablishmentTypeListView(generics.ListAPIView): """Resource for getting a list of establishment types.""" From 16def7df5d40ea315e663735ec06ac16748d931c Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 12 Dec 2019 18:20:57 +0300 Subject: [PATCH 07/14] search crunches for mobiles --- apps/search_indexes/views.py | 22 ++++++++++++++++++---- apps/tag/serializers.py | 2 +- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index 387a6eae..e567f212 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -3,7 +3,6 @@ from rest_framework import permissions from django_elasticsearch_dsl_drf import constants from django_elasticsearch_dsl_drf.filter_backends import ( FilteringFilterBackend, - GeoSpatialFilteringFilterBackend, GeoSpatialOrderingFilterBackend, OrderingFilterBackend, ) @@ -13,9 +12,24 @@ from search_indexes import serializers, filters, utils from search_indexes.documents import EstablishmentDocument, NewsDocument from search_indexes.documents.product import ProductDocument from utils.pagination import ESDocumentPagination +from tag.models import TagCategory -class NewsDocumentViewSet(BaseDocumentViewSet): +class CustomBaseDocumentViewSet(BaseDocumentViewSet): + def __init__(self, *args, **kwargs): + if self.filter_fields: + for name in TagCategory.objects.all().values('index_name'): + self.filter_fields.update({ + f'{name["index_name"]}_id': { + 'field': 'tags.id', + 'lookups': [constants.LOOKUP_QUERY_IN] + } + }) + + super().__init__(*args, **kwargs) + + +class NewsDocumentViewSet(CustomBaseDocumentViewSet): """News document ViewSet.""" document = NewsDocument @@ -94,7 +108,7 @@ class MobileNewsDocumentViewSet(NewsDocumentViewSet): ] -class EstablishmentDocumentViewSet(BaseDocumentViewSet): +class EstablishmentDocumentViewSet(CustomBaseDocumentViewSet): """Establishment document ViewSet.""" document = EstablishmentDocument @@ -319,7 +333,7 @@ class MobileEstablishmentDocumentViewSet(EstablishmentDocumentViewSet): ] -class ProductDocumentViewSet(BaseDocumentViewSet): +class ProductDocumentViewSet(CustomBaseDocumentViewSet): """Product document ViewSet.""" document = ProductDocument diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py index f6266060..c842774b 100644 --- a/apps/tag/serializers.py +++ b/apps/tag/serializers.py @@ -123,7 +123,7 @@ class FiltersTagCategoryBaseSerializer(serializers.ModelSerializer): return obj in ['open_now', ] def get_param_name(self, obj): - return 'tags_id__in' + return f'{obj.index_name}_id__in' def get_fields(self, *args, **kwargs): fields = super(FiltersTagCategoryBaseSerializer, self).get_fields() From 38f221198276fc0454ca46c5920bb9f20fad5ffa Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 12 Dec 2019 19:05:47 +0300 Subject: [PATCH 08/14] sum fixes for mobile filters --- apps/tag/views.py | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/apps/tag/views.py b/apps/tag/views.py index e56447bc..6ad7f20a 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -1,17 +1,12 @@ """Tag views.""" from django.conf import settings -from rest_framework import generics -from rest_framework import mixins -from rest_framework import permissions -from rest_framework import status -from rest_framework import viewsets +from rest_framework import generics, mixins, permissions, status, viewsets from rest_framework.decorators import action from rest_framework.response import Response from location.models import WineRegion -from tag import filters -from tag import models -from tag import serializers +from product.models import ProductType +from tag import filters, models, serializers class ChosenTagsView(generics.ListAPIView, viewsets.GenericViewSet): @@ -61,14 +56,9 @@ class TagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): # User`s views & viewsets -class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): +class FiltersTagCategoryViewSet(TagCategoryViewSet): """ViewSet for TagCategory model.""" - filterset_class = filters.TagCategoryFilterSet - pagination_class = None - permission_classes = (permissions.AllowAny,) - queryset = models.TagCategory.objects.with_tags().with_base_related(). \ - distinct() serializer_class = serializers.FiltersTagCategoryBaseSerializer def list(self, request, *args, **kwargs): @@ -114,7 +104,7 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): } result_list.append(toques) - if filter_flags['wine_region']: + if request.query_params.get('product_type') == ProductType.WINE: wine_region_id = query_params.get('wine_region_id__in') if str(wine_region_id).isdigit(): From 0193ca90e3e7641892d49eab7561a3fa331af140 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 12 Dec 2019 22:07:07 +0300 Subject: [PATCH 09/14] dynamic filters --- apps/tag/views.py | 45 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/apps/tag/views.py b/apps/tag/views.py index 6ad7f20a..6a594220 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -3,6 +3,9 @@ from django.conf import settings from rest_framework import generics, mixins, permissions, status, viewsets from rest_framework.decorators import action from rest_framework.response import Response +from rest_framework.serializers import ValidationError +from django.utils.translation import gettext_lazy as _ +from search_indexes import views as search_views from location.models import WineRegion from product.models import ProductType @@ -82,7 +85,7 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet): if params_type == 'restaurant': additional_flags += ['toque_number', 'works_noon', 'works_evening', 'works_now'] - elif params_type == 'winery': + elif params_type in ['winery', 'wine']: additional_flags += ['wine_region'] elif params_type == 'artisan': @@ -175,12 +178,42 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet): } result_list.append(works_at_weekday) - if 'tags_id__in' in query_params: - # filtering by params_type and tags id - # todo: result_list.append( filtering_data ) - pass + search_view_class = self.define_search_view_by_request(request) + facets = search_view_class.as_view({'get': 'list'})(self.mutate_request(self.request)).data['facets'] + return Response(self.remove_empty_filters(result_list, facets)) - return Response(result_list) + @staticmethod + def mutate_request(request): + """Remove all filtering get params and remove s_ from the rest of them""" + request.GET._mutable = True + for name in request.query_params.copy().keys(): + value = request.query_params.pop(name) + if name.startswith('s_'): + request.query_params[name[2:]] = value[0] + request.GET._mutable = False + return request._request + + @staticmethod + def define_search_view_by_request(request): + request.GET._mutable = True + if request.query_params.get('items'): + items = request.query_params.pop('items')[0] + else: + raise ValidationError({'detail': _('Missing required "items" parameter')}) + item_to_class = { + 'news': search_views.NewsDocumentViewSet, + 'establishments': search_views.EstablishmentDocumentViewSet, + 'products': search_views.ProductDocumentViewSet, + } + klass = item_to_class.get(items) + if klass is None: + raise ValidationError({'detail': _('news/establishments/products')}) + request.GET._mutable = False + return klass + + @staticmethod + def remove_empty_filters(filters, facets): + return filters # BackOffice user`s views & viewsets From 8aa2cb71f36d366529b8bdc5c9be52613f6a9a89 Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Fri, 13 Dec 2019 12:08:24 +0300 Subject: [PATCH 10/14] fix migrations --- apps/account/migrations/0023_auto_20191204_0916.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/account/migrations/0023_auto_20191204_0916.py b/apps/account/migrations/0023_auto_20191204_0916.py index 68d313a0..3b5fa7ea 100644 --- a/apps/account/migrations/0023_auto_20191204_0916.py +++ b/apps/account/migrations/0023_auto_20191204_0916.py @@ -17,6 +17,6 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='userrole', - unique_together={('user', 'role')}, + unique_together={('user', 'role', 'establishment', 'state')}, ), ] From 54db050f03e210b2db0901197d58e60ab6001ce3 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 13 Dec 2019 13:23:49 +0300 Subject: [PATCH 11/14] return tags_id__in --- apps/search_indexes/views.py | 21 +++------------------ apps/tag/serializers.py | 2 +- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index e567f212..cb5b448c 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -12,24 +12,9 @@ from search_indexes import serializers, filters, utils from search_indexes.documents import EstablishmentDocument, NewsDocument from search_indexes.documents.product import ProductDocument from utils.pagination import ESDocumentPagination -from tag.models import TagCategory -class CustomBaseDocumentViewSet(BaseDocumentViewSet): - def __init__(self, *args, **kwargs): - if self.filter_fields: - for name in TagCategory.objects.all().values('index_name'): - self.filter_fields.update({ - f'{name["index_name"]}_id': { - 'field': 'tags.id', - 'lookups': [constants.LOOKUP_QUERY_IN] - } - }) - - super().__init__(*args, **kwargs) - - -class NewsDocumentViewSet(CustomBaseDocumentViewSet): +class NewsDocumentViewSet(BaseDocumentViewSet): """News document ViewSet.""" document = NewsDocument @@ -108,7 +93,7 @@ class MobileNewsDocumentViewSet(NewsDocumentViewSet): ] -class EstablishmentDocumentViewSet(CustomBaseDocumentViewSet): +class EstablishmentDocumentViewSet(BaseDocumentViewSet): """Establishment document ViewSet.""" document = EstablishmentDocument @@ -333,7 +318,7 @@ class MobileEstablishmentDocumentViewSet(EstablishmentDocumentViewSet): ] -class ProductDocumentViewSet(CustomBaseDocumentViewSet): +class ProductDocumentViewSet(BaseDocumentViewSet): """Product document ViewSet.""" document = ProductDocument diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py index c842774b..f6266060 100644 --- a/apps/tag/serializers.py +++ b/apps/tag/serializers.py @@ -123,7 +123,7 @@ class FiltersTagCategoryBaseSerializer(serializers.ModelSerializer): return obj in ['open_now', ] def get_param_name(self, obj): - return f'{obj.index_name}_id__in' + return 'tags_id__in' def get_fields(self, *args, **kwargs): fields = super(FiltersTagCategoryBaseSerializer, self).get_fields() From ae15a5e66bc05d3c6b2b66b8f5b62f95623ac580 Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Fri, 13 Dec 2019 13:24:04 +0300 Subject: [PATCH 12/14] fix migration --- apps/account/migrations/0023_auto_20191204_0916.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/account/migrations/0023_auto_20191204_0916.py b/apps/account/migrations/0023_auto_20191204_0916.py index 3b5fa7ea..68d313a0 100644 --- a/apps/account/migrations/0023_auto_20191204_0916.py +++ b/apps/account/migrations/0023_auto_20191204_0916.py @@ -17,6 +17,6 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='userrole', - unique_together={('user', 'role', 'establishment', 'state')}, + unique_together={('user', 'role')}, ), ] From 9fbb8f01def12a622323a7892f93ea239980d4b8 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 13 Dec 2019 14:57:15 +0300 Subject: [PATCH 13/14] dynamic filters --- apps/tag/serializers.py | 2 ++ apps/tag/views.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py index f6266060..b5e5a267 100644 --- a/apps/tag/serializers.py +++ b/apps/tag/serializers.py @@ -123,6 +123,8 @@ class FiltersTagCategoryBaseSerializer(serializers.ModelSerializer): return obj in ['open_now', ] def get_param_name(self, obj): + if obj.index_name == 'wine-color': + return 'wine_colors_id__in' return 'tags_id__in' def get_fields(self, *args, **kwargs): diff --git a/apps/tag/views.py b/apps/tag/views.py index 6a594220..3bb33975 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -213,6 +213,41 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet): @staticmethod def remove_empty_filters(filters, facets): + # parse facets + if facets.get('_filter_tag'): + tags_to_preserve = list(map(lambda el: el['key'], facets['_filter_tag']['tag']['buckets'])) + if facets.get('_filter_wine_colors'): + wine_colors_to_preserve = list(map(lambda el: el['key'], facets['_filter_wine_colors']['wine_colors']['buckets'])) + if facets.get('_filter_wine_region_id'): + wine_regions_to_preserve = list(map(lambda el: el['key'], facets['_filter_wine_region_id']['wine_region_id']['buckets'])) + if facets.get('_filter_toque_number'): + toque_numbers = list(map(lambda el: el['key'], facets['_filter_toque_number']['toque_number']['buckets'])) + if facets.get('_filter_works_noon'): + works_noon = list(map(lambda el: el['key'], facets['_filter_works_noon']['works_noon']['buckets'])) + if facets.get('_filter_works_evening'): + works_evening = list(map(lambda el: el['key'], facets['_filter_works_evening']['works_evening']['buckets'])) + if facets.get('_filter_works_at_weekday'): + works_at_weekday = list(map(lambda el: el['key'], facets['_filter_works_at_weekday']['works_at_weekday']['buckets'])) + if facets.get('_filter_works_now'): + works_now = list(map(lambda el: el['key'], facets['_filter_works_now']['works_now']['buckets'])) + + # remove empty filters + for category in filters: + param_name = category.get('param_name') + if param_name == 'tags_id__in': + category['filters'] = list(filter(lambda tag: tag['id'] in tags_to_preserve, category['filters'])) + elif param_name == 'wine_colors_id__in': + category['filters'] = list(filter(lambda tag: tag['id'] in wine_colors_to_preserve, category['filters'])) + elif param_name == 'wine_region_id__in': + category['filters'] = list(filter(lambda tag: tag['id'] in wine_regions_to_preserve, category['filters'])) + elif param_name == 'toque_number__in': + category['filters'] = list(filter(lambda tag: tag['id'] in toque_numbers, category['filters'])) + elif param_name == 'works_noon__in': + category['filters'] = list(filter(lambda tag: tag['id'] in works_noon, category['filters'])) + elif param_name == 'works_evening__in': + category['filters'] = list(filter(lambda tag: tag['id'] in works_evening, category['filters'])) + elif param_name == 'works_at_weekday__in': + category['filters'] = list(filter(lambda tag: tag['id'] in works_at_weekday, category['filters'])) return filters From 833fada6d11910112502ee34b1edd12236b3a195 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 13 Dec 2019 15:11:22 +0300 Subject: [PATCH 14/14] add fix migration issue --- db_migration_resolve.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 db_migration_resolve.txt diff --git a/db_migration_resolve.txt b/db_migration_resolve.txt new file mode 100644 index 00000000..770dac81 --- /dev/null +++ b/db_migration_resolve.txt @@ -0,0 +1,12 @@ +В случае возникновения проблемы с применением миграции account 0027: + +1 Удаляем unique together constrain для app - account + ALTER TABLE account_userrole + DROP CONSTRAINT account_userrole_user_id_role_id_26fa14c4_uniq; +2 Правим миграцию 0023 + migrations.AlterUniqueTogether( + name='userrole', + unique_together=set(), + ), +3 Применяем account 0027 +4 Возвращаем миграцию account 0023, в исходное состояние \ No newline at end of file