From 96f317582747a5f79c9265adc6487062c0868afa Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 10 Dec 2019 09:45:35 +0300 Subject: [PATCH 01/81] fix get_name_translated --- apps/search_indexes/serializers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 3b5561fa..9c08b849 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -102,7 +102,7 @@ class ProductTypeDocumentSerializer(serializers.Serializer): @staticmethod def get_name_translated(obj): - return get_translated_value(obj.name) + return get_translated_value(obj.get['name']) class CityDocumentShortSerializer(serializers.Serializer): @@ -122,7 +122,7 @@ class CountryDocumentSerializer(serializers.Serializer): @staticmethod def get_name_translated(obj): - return get_translated_value(obj.name) + return get_translated_value(obj.get('name')) class AnotherCityDocumentShortSerializer(CityDocumentShortSerializer): From 32e6e20c6b40d536a2bd6cdfa81e57d6d43e303e Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 10 Dec 2019 10:15:04 +0300 Subject: [PATCH 02/81] fix get method --- apps/search_indexes/documents/product.py | 4 ++-- apps/search_indexes/serializers.py | 2 +- docker-compose.mysql.yml | 2 -- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/search_indexes/documents/product.py b/apps/search_indexes/documents/product.py index c9459dd8..6ce36bb7 100644 --- a/apps/search_indexes/documents/product.py +++ b/apps/search_indexes/documents/product.py @@ -5,7 +5,7 @@ from search_indexes.utils import OBJECT_FIELD_PROPERTIES from product import models ProductIndex = Index(settings.ELASTICSEARCH_INDEX_NAMES.get(__name__, 'product')) -ProductIndex.settings(number_of_shards=5, number_of_replicas=2, mapping={'total_fields':{'limit': 3000}}) +ProductIndex.settings(number_of_shards=5, number_of_replicas=2, mapping={'total_fields': {'limit': 3000}}) @ProductIndex.doc_type @@ -155,7 +155,7 @@ class ProductDocument(Document): name_ru = fields.TextField(attr='display_name', analyzer='russian') name_fr = fields.TextField(attr='display_name', analyzer='french') favorites_for_users = fields.ListField(field=fields.IntegerField()) - created = fields.DateField(attr='created') # publishing date (?) + created = fields.DateField(attr='created') # publishing date (?) class Django: model = models.Product diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 9c08b849..c9cacb0f 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -102,7 +102,7 @@ class ProductTypeDocumentSerializer(serializers.Serializer): @staticmethod def get_name_translated(obj): - return get_translated_value(obj.get['name']) + return get_translated_value(obj.get('name')) class CityDocumentShortSerializer(serializers.Serializer): diff --git a/docker-compose.mysql.yml b/docker-compose.mysql.yml index 3dc31d33..d62ff677 100644 --- a/docker-compose.mysql.yml +++ b/docker-compose.mysql.yml @@ -16,8 +16,6 @@ services: - .:/code - - # PostgreSQL database db: build: From 323c2628e02692033bbcc46aa81e54200d4ed601 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Tue, 10 Dec 2019 10:17:58 +0300 Subject: [PATCH 03/81] Add endpoint to get all cities with search string --- apps/location/urls/back.py | 1 + apps/location/views/back.py | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/apps/location/urls/back.py b/apps/location/urls/back.py index c5ef027b..2434dd26 100644 --- a/apps/location/urls/back.py +++ b/apps/location/urls/back.py @@ -10,6 +10,7 @@ urlpatterns = [ path('addresses//', views.AddressRUDView.as_view(), name='address-RUD'), path('cities/', views.CityListCreateView.as_view(), name='city-list-create'), + path('cities/all/', views.CityListSearchView.as_view(), name='city-list-create'), path('cities//', views.CityRUDView.as_view(), name='city-retrieve'), path('cities//gallery/', views.CityGalleryListView.as_view(), name='gallery-list'), diff --git a/apps/location/views/back.py b/apps/location/views/back.py index 8406dee3..fc4499ae 100644 --- a/apps/location/views/back.py +++ b/apps/location/views/back.py @@ -37,6 +37,15 @@ class CityListCreateView(common.CityViewMixin, generics.ListCreateAPIView): filter_class = filters.CityBackFilter +class CityListSearchView(common.CityViewMixin, generics.ListCreateAPIView): + """Create view for model City.""" + serializer_class = serializers.CitySerializer + permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin] + queryset = models.City.objects.all() + filter_class = filters.CityBackFilter + pagination_class = None + + class CityRUDView(common.CityViewMixin, generics.RetrieveUpdateDestroyAPIView): """RUD view for model City.""" serializer_class = serializers.CitySerializer From 0647dd45f8dc2a05423cb2236beb34ab42e8dace Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 10 Dec 2019 10:25:10 +0300 Subject: [PATCH 04/81] fix get_translated_value by type obj --- apps/search_indexes/serializers.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index c9cacb0f..6cb5692a 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -40,6 +40,8 @@ class ProductSubtypeDocumentSerializer(serializers.Serializer): name_translated = serializers.SerializerMethodField() def get_name_translated(self, obj): + if isinstance(obj, dict): + return get_translated_value(obj.get('name')) return get_translated_value(obj.name) @@ -102,7 +104,9 @@ class ProductTypeDocumentSerializer(serializers.Serializer): @staticmethod def get_name_translated(obj): - return get_translated_value(obj.get('name')) + if isinstance(obj, dict): + return get_translated_value(obj.get('name')) + return get_translated_value(obj.name) class CityDocumentShortSerializer(serializers.Serializer): @@ -114,7 +118,6 @@ class CityDocumentShortSerializer(serializers.Serializer): class CountryDocumentSerializer(serializers.Serializer): - id = serializers.IntegerField() code = serializers.CharField(allow_null=True) svg_image = serializers.CharField() @@ -122,11 +125,12 @@ class CountryDocumentSerializer(serializers.Serializer): @staticmethod def get_name_translated(obj): - return get_translated_value(obj.get('name')) + if isinstance(obj, dict): + return get_translated_value(obj.get('name')) + return get_translated_value(obj.name) class AnotherCityDocumentShortSerializer(CityDocumentShortSerializer): - country = CountryDocumentSerializer() def to_representation(self, instance): From 4ec812d617e9d2aa5c27feb5632f47e5360c61a9 Mon Sep 17 00:00:00 2001 From: dormantman Date: Tue, 10 Dec 2019 14:27:16 +0300 Subject: [PATCH 05/81] Created new category_test url --- apps/tag/serializers.py | 48 ++++++++++++-- apps/tag/urls/web.py | 1 + apps/tag/views.py | 134 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 176 insertions(+), 7 deletions(-) diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py index eb73291f..2da41d5c 100644 --- a/apps/tag/serializers.py +++ b/apps/tag/serializers.py @@ -2,11 +2,14 @@ from rest_framework import serializers from rest_framework.fields import SerializerMethodField -from establishment.models import (Establishment, EstablishmentType) -from news.models import News, NewsType +from establishment.models import Establishment +from establishment.models import EstablishmentType +from news.models import News +from news.models import NewsType from tag import models -from utils.exceptions import (ObjectAlreadyAdded, BindingObjectNotFound, - RemovedBindingObjectNotFound) +from utils.exceptions import BindingObjectNotFound +from utils.exceptions import ObjectAlreadyAdded +from utils.exceptions import RemovedBindingObjectNotFound from utils.serializers import TranslatedField @@ -95,6 +98,43 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer): return TagBaseSerializer(instance=tags, many=True, read_only=True).data +class TestBaseSerializer(serializers.ModelSerializer): + """Serializer for model TagCategory.""" + + label_translated = TranslatedField() + tags = SerializerMethodField() + + class Meta: + """Meta class.""" + + model = models.TagCategory + fields = ( + 'id', + 'transliterated_name', + 'name', + 'tags', + ) + + def get_filters(self, obj): + query_params = dict(self.context['request'].query_params) + + if len(query_params) > 1: + return [] + + params = {} + if 'establishment_type' in query_params: + params = { + 'establishments__isnull': False, + } + elif 'product_type' in query_params: + params = { + 'products__isnull': False, + } + + tags = obj.tags.filter(**params).distinct() + return TagBaseSerializer(instance=tags, many=True, read_only=True).data + + class TagCategoryShortSerializer(serializers.ModelSerializer): """Serializer for model TagCategory.""" diff --git a/apps/tag/urls/web.py b/apps/tag/urls/web.py index f83c593a..7ea45b17 100644 --- a/apps/tag/urls/web.py +++ b/apps/tag/urls/web.py @@ -7,6 +7,7 @@ app_name = 'tag' router = SimpleRouter() router.register(r'categories', views.TagCategoryViewSet) +router.register(r'categories_test', views.TestTagCategoryViewSet) router.register(r'chosen_tags', views.ChosenTagsView) urlpatterns = [ diff --git a/apps/tag/views.py b/apps/tag/views.py index 4a2f2613..886d4f9f 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -1,11 +1,25 @@ """Tag views.""" from django.conf import settings +from django_elasticsearch_dsl_drf import constants +from django_elasticsearch_dsl_drf.filter_backends import FilteringFilterBackend +from elasticsearch_dsl import TermsFacet +from rest_framework import generics +from rest_framework import mixins from rest_framework import permissions -from rest_framework import viewsets, mixins, status, generics +from rest_framework import status +from rest_framework import viewsets from rest_framework.decorators import action from rest_framework.response import Response -from tag import filters, models, serializers +from search_indexes import utils +from search_indexes.documents import EstablishmentDocument +from search_indexes.filters import CustomFacetedSearchFilterBackend +from search_indexes.filters import CustomSearchFilterBackend +from search_indexes.serializers import EstablishmentDocumentSerializer +from tag import filters +from tag import models +from tag import serializers +from utils.pagination import ESDocumentPagination class ChosenTagsView(generics.ListAPIView, viewsets.GenericViewSet): @@ -36,7 +50,8 @@ class ChosenTagsView(generics.ListAPIView, viewsets.GenericViewSet): serializer = self.get_serializer(queryset, many=True) result_list = serializer.data if request.query_params.get('type') and (settings.ESTABLISHMENT_CHOSEN_TAGS or settings.NEWS_CHOSEN_TAGS): - ordered_list = settings.ESTABLISHMENT_CHOSEN_TAGS if request.query_params.get('type') == 'establishment' else settings.NEWS_CHOSEN_TAGS + ordered_list = settings.ESTABLISHMENT_CHOSEN_TAGS if request.query_params.get( + 'type') == 'establishment' else settings.NEWS_CHOSEN_TAGS result_list = sorted(result_list, key=lambda x: ordered_list.index(x['index_name'])) return Response(result_list) @@ -53,6 +68,119 @@ class TagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): serializer_class = serializers.TagCategoryBaseSerializer +# User`s views & viewsets +class TestTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): + """ViewSet for TagCategory model.""" + + filterset_classes = [ + filters.TagCategoryFilterSet, + FilteringFilterBackend, + CustomSearchFilterBackend, + CustomFacetedSearchFilterBackend, + ] + + document = EstablishmentDocument + pagination_class = ESDocumentPagination + permission_classes = (permissions.AllowAny,) + queryset = models.TagCategory.objects.with_tags().with_base_related(). \ + distinct() + serializer_class = serializers.TestBaseSerializer + + faceted_search_fields = { + 'works_at_weekday': { + 'field': 'works_at_weekday', + 'facet': TermsFacet, + 'enabled': True, + }, + 'toque_number': { + 'field': 'toque_number', + 'enabled': True, + 'facet': TermsFacet, + }, + 'works_noon': { + 'field': 'works_noon', + 'facet': TermsFacet, + 'enabled': True, + }, + 'works_evening': { + 'field': 'works_evening', + 'facet': TermsFacet, + 'enabled': True, + }, + 'works_now': { + 'field': 'works_now', + 'facet': TermsFacet, + 'enabled': True, + }, + 'tag': { + 'field': 'tags.id', + 'facet': TermsFacet, + 'enabled': True, + 'options': { + 'size': utils.FACET_MAX_RESPONSE, + }, + } + } + + search_fields = { + 'name': { + 'fuzziness': 'auto:2,5', + 'boost': 4 + }, + 'transliterated_name': { + 'fuzziness': 'auto:2,5', + 'boost': 3 + }, + 'description': {'fuzziness': 'auto:2,5'}, + } + translated_search_fields = ( + 'description', + ) + filter_fields = { + 'slug': 'slug', + 'tag': { + 'field': 'tags.id', + 'lookups': [constants.LOOKUP_QUERY_IN] + }, + 'toque_number': { + 'field': 'toque_number', + 'lookups': [ + constants.LOOKUP_FILTER_RANGE, + constants.LOOKUP_QUERY_GT, + constants.LOOKUP_QUERY_GTE, + constants.LOOKUP_QUERY_LT, + constants.LOOKUP_QUERY_LTE, + constants.LOOKUP_QUERY_IN, + ] + }, + + 'works_noon': { + 'field': 'works_noon', + 'lookups': [ + constants.LOOKUP_QUERY_IN, + ], + }, + 'works_at_weekday': { + 'field': 'works_at_weekday', + 'lookups': [ + constants.LOOKUP_QUERY_IN, + ], + }, + 'works_evening': { + 'field': 'works_evening', + 'lookups': [ + constants.LOOKUP_QUERY_IN, + ], + }, + 'works_now': { + 'field': 'works_now', + 'lookups': [ + constants.LOOKUP_FILTER_TERM, + ] + }, + } + + # BackOffice user`s views & viewsets class BindObjectMixin: """Bind object mixin.""" From 47cb7f8e63f235f352a8a737188d98f2988c9783 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Tue, 10 Dec 2019 17:09:43 +0300 Subject: [PATCH 06/81] Tmp commit --- apps/account/urls/back.py | 1 + apps/account/views/back.py | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/apps/account/urls/back.py b/apps/account/urls/back.py index 30f21573..cf1d114e 100644 --- a/apps/account/urls/back.py +++ b/apps/account/urls/back.py @@ -10,4 +10,5 @@ urlpatterns = [ path('user-role/', views.UserRoleLstView.as_view(), name='user-role-list-create'), path('user/', views.UserLstView.as_view(), name='user-create-list'), path('user//', views.UserRUDView.as_view(), name='user-rud'), + path('user//csv', views.get_user_csv, name='user-csv'), ] diff --git a/apps/account/views/back.py b/apps/account/views/back.py index fbbc986e..c1eb32b5 100644 --- a/apps/account/views/back.py +++ b/apps/account/views/back.py @@ -1,6 +1,8 @@ from django_filters.rest_framework import DjangoFilterBackend from rest_framework import generics, permissions from rest_framework.filters import OrderingFilter +import csv +from django.http import HttpResponse, HttpResponseNotFound from account import models from account.models import User @@ -46,3 +48,22 @@ class UserRUDView(generics.RetrieveUpdateDestroyAPIView): serializer_class = serializers.BackDetailUserSerializer permission_classes = (permissions.IsAdminUser,) lookup_field = 'id' + + +def get_user_csv(request, user_id): + try: + user = User.objects.get(id=user_id).values_list() + except User.DoesNotExist: + return HttpResponseNotFound("User not found") + + # + # + # response = HttpResponse(content_type='text/csv') + # response['Content-Disposition'] = f'attachment; filename="{user}.csv"' + # + # writer = csv.writer(response) + # writer.writerow(['First row', 'Foo', 'Bar', 'Baz']) + # writer.writerow(['Second row', 'A', 'B', 'C', '"Testing"', "Here's a quote"]) + # + # return response + return HttpResponseNotFound(user) \ No newline at end of file From 096d5dab1829d6f4f1b0f3d34c17355fca9ee32a Mon Sep 17 00:00:00 2001 From: dormantman Date: Tue, 10 Dec 2019 19:24:56 +0300 Subject: [PATCH 07/81] Added tags/filters method --- apps/tag/serializers.py | 45 ++++++++-- apps/tag/urls/web.py | 2 +- apps/tag/views.py | 191 +++++++++++++++++----------------------- 3 files changed, 120 insertions(+), 118 deletions(-) diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py index 2da41d5c..2fea5ad0 100644 --- a/apps/tag/serializers.py +++ b/apps/tag/serializers.py @@ -98,11 +98,13 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer): return TagBaseSerializer(instance=tags, many=True, read_only=True).data -class TestBaseSerializer(serializers.ModelSerializer): +class FiltersTagCategoryBaseSerializer(serializers.ModelSerializer): """Serializer for model TagCategory.""" label_translated = TranslatedField() - tags = SerializerMethodField() + filters = SerializerMethodField() + param_name = SerializerMethodField() + type = SerializerMethodField() class Meta: """Meta class.""" @@ -110,17 +112,44 @@ class TestBaseSerializer(serializers.ModelSerializer): model = models.TagCategory fields = ( 'id', - 'transliterated_name', - 'name', - 'tags', + 'label_translated', + 'index_name', + 'param_name', + 'type', + 'filters', ) + def get_type(self, obj): + 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 + + def get_fields(self, *args, **kwargs): + fields = super(FiltersTagCategoryBaseSerializer, self).get_fields() + + if self.get_type(self): + fields.pop('filters', None) + else: + fields.pop('type', None) + + return fields + def get_filters(self, obj): query_params = dict(self.context['request'].query_params) - if len(query_params) > 1: - return [] - params = {} if 'establishment_type' in query_params: params = { diff --git a/apps/tag/urls/web.py b/apps/tag/urls/web.py index 7ea45b17..23298d3d 100644 --- a/apps/tag/urls/web.py +++ b/apps/tag/urls/web.py @@ -7,7 +7,7 @@ app_name = 'tag' router = SimpleRouter() router.register(r'categories', views.TagCategoryViewSet) -router.register(r'categories_test', views.TestTagCategoryViewSet) +router.register(r'filters', views.FiltersTagCategoryViewSet) router.register(r'chosen_tags', views.ChosenTagsView) urlpatterns = [ diff --git a/apps/tag/views.py b/apps/tag/views.py index 886d4f9f..fc870fe9 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -11,15 +11,10 @@ from rest_framework import viewsets from rest_framework.decorators import action from rest_framework.response import Response -from search_indexes import utils -from search_indexes.documents import EstablishmentDocument -from search_indexes.filters import CustomFacetedSearchFilterBackend -from search_indexes.filters import CustomSearchFilterBackend -from search_indexes.serializers import EstablishmentDocumentSerializer +from location.models import WineRegion from tag import filters from tag import models from tag import serializers -from utils.pagination import ESDocumentPagination class ChosenTagsView(generics.ListAPIView, viewsets.GenericViewSet): @@ -69,116 +64,94 @@ class TagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): # User`s views & viewsets -class TestTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): +class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): """ViewSet for TagCategory model.""" - filterset_classes = [ - filters.TagCategoryFilterSet, - FilteringFilterBackend, - CustomSearchFilterBackend, - CustomFacetedSearchFilterBackend, - ] - - document = EstablishmentDocument - pagination_class = ESDocumentPagination + filterset_class = filters.TagCategoryFilterSet + pagination_class = None permission_classes = (permissions.AllowAny,) queryset = models.TagCategory.objects.with_tags().with_base_related(). \ distinct() - serializer_class = serializers.TestBaseSerializer + serializer_class = serializers.FiltersTagCategoryBaseSerializer - faceted_search_fields = { - 'works_at_weekday': { - 'field': 'works_at_weekday', - 'facet': TermsFacet, - 'enabled': True, - }, - 'toque_number': { - 'field': 'toque_number', - 'enabled': True, - 'facet': TermsFacet, - }, - 'works_noon': { - 'field': 'works_noon', - 'facet': TermsFacet, - 'enabled': True, - }, - 'works_evening': { - 'field': 'works_evening', - 'facet': TermsFacet, - 'enabled': True, - }, - 'works_now': { - 'field': 'works_now', - 'facet': TermsFacet, - 'enabled': True, - }, - 'tag': { - 'field': 'tags.id', - 'facet': TermsFacet, - 'enabled': True, - 'options': { - 'size': utils.FACET_MAX_RESPONSE, - }, - } - } + def list(self, request, *args, **kwargs): + queryset = self.filter_queryset(self.get_queryset()) + serializer = self.get_serializer(queryset, many=True) - search_fields = { - 'name': { - 'fuzziness': 'auto:2,5', - 'boost': 4 - }, - 'transliterated_name': { - 'fuzziness': 'auto:2,5', - 'boost': 3 - }, - 'description': {'fuzziness': 'auto:2,5'}, - } - translated_search_fields = ( - 'description', - ) - filter_fields = { - 'slug': 'slug', - 'tag': { - 'field': 'tags.id', - 'lookups': [constants.LOOKUP_QUERY_IN] - }, - 'toque_number': { - 'field': 'toque_number', - 'lookups': [ - constants.LOOKUP_FILTER_RANGE, - constants.LOOKUP_QUERY_GT, - constants.LOOKUP_QUERY_GTE, - constants.LOOKUP_QUERY_LT, - constants.LOOKUP_QUERY_LTE, - constants.LOOKUP_QUERY_IN, - ] - }, + result_list = serializer.data + query_params = request.query_params - 'works_noon': { - 'field': 'works_noon', - 'lookups': [ - constants.LOOKUP_QUERY_IN, - ], - }, - 'works_at_weekday': { - 'field': 'works_at_weekday', - 'lookups': [ - constants.LOOKUP_QUERY_IN, - ], - }, - 'works_evening': { - 'field': 'works_evening', - 'lookups': [ - constants.LOOKUP_QUERY_IN, - ], - }, - 'works_now': { - 'field': 'works_now', - 'lookups': [ - constants.LOOKUP_FILTER_TERM, - ] - }, - } + if 'toque_number__in' in query_params: + toques = { + "index_name": "toque_number", + "label_translated": "Toques", + "param_name": "toque_number__in", + "filters": [{ + "id": toque_id, + "index_name": "toque_%d" % toque_id, + "label_translated": "Toque %d" % toque_id + } for toque_id in range(6)] + } + result_list.append(toques) + + if 'wine_region_id__in' in query_params: + try: + wine_region_id = int(query_params['wine_region_id__in']) + + wine_regions = { + "index_name": "wine_region", + "label_translated": "Wine region", + "param_name": "wine_region_id__in", + "filters": [{ + "id": obj.id, + "index_name": obj.name.lower().replace(' ', '_'), + "label_translated": obj.name + } for obj in WineRegion.objects.filter(id=wine_region_id)] + } + + result_list.append(wine_regions) + + except ValueError: + pass + + if 'works_noon__in' in query_params: + week_days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") + works_noon = { + "index_name": "works_noon", + "label_translated": "Open noon", + "param_name": "works_noon__in", + "filters": [{ + "id": weekday, + "index_name": week_days[weekday].lower(), + "label_translated": week_days[weekday] + } for weekday in range(7)] + } + result_list.append(works_noon) + + if 'works_evening__in' in query_params: + week_days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") + works_evening = { + "index_name": "works_evening", + "label_translated": "Open noon", + "param_name": "works_evening__in", + "filters": [{ + "id": weekday, + "index_name": week_days[weekday].lower(), + "label_translated": week_days[weekday] + } for weekday in range(7)] + } + result_list.append(works_evening) + + if 'works_now' in query_params: + works_now = { + "index_name": "open_now", + "label_translated": "Open now", + "param_name": "open_now", + "type": True + } + result_list.append(works_now) + + return Response(result_list) # BackOffice user`s views & viewsets @@ -271,4 +244,4 @@ class TagCategoryBackOfficeViewSet(mixins.CreateModelMixin, if obj_type == self.bind_object_serializer_class.ESTABLISHMENT_TYPE: tag_category.establishment_types.remove(related_object) elif obj_type == self.bind_object_serializer_class.NEWS_TYPE: - tag_category.news_types.remove(related_object) + tag_category.news_types.remove(related_object) \ No newline at end of file From 41659958f74c81bb4ce171e3fac8b2a96b41203f Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 11 Dec 2019 09:10:49 +0300 Subject: [PATCH 08/81] product establishment_detail address --- apps/product/models.py | 2 +- apps/search_indexes/documents/product.py | 28 ++++++------- apps/search_indexes/serializers.py | 50 +++++++++++++++--------- apps/search_indexes/signals.py | 12 +++--- 4 files changed, 52 insertions(+), 40 deletions(-) diff --git a/apps/product/models.py b/apps/product/models.py index b125c7eb..6923d6dd 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -171,7 +171,7 @@ class Product(GalleryModelMixin, TranslatedFieldsMixin, BaseAttributes, default=None, help_text='{"en-GB":"some text"}') available = models.BooleanField(_('available'), default=True) product_type = models.ForeignKey(ProductType, on_delete=models.PROTECT, - null=True, + null=True, blank=True, default=None, related_name='products', verbose_name=_('Type')) subtypes = models.ManyToManyField(ProductSubType, blank=True, related_name='products', diff --git a/apps/search_indexes/documents/product.py b/apps/search_indexes/documents/product.py index 6ce36bb7..2a88f4ad 100644 --- a/apps/search_indexes/documents/product.py +++ b/apps/search_indexes/documents/product.py @@ -14,12 +14,13 @@ class ProductDocument(Document): description = fields.ObjectField(attr='description_indexing', properties=OBJECT_FIELD_PROPERTIES) - product_type = fields.ObjectField(properties={ - 'id': fields.IntegerField(), - 'name': fields.ObjectField(attr='name_indexing', properties=OBJECT_FIELD_PROPERTIES), - 'index_name': fields.KeywordField(), - 'use_subtypes': fields.BooleanField(), - }) + product_type = fields.ObjectField( + properties={ + 'id': fields.IntegerField(), + 'name': fields.ObjectField(attr='name_indexing', properties=OBJECT_FIELD_PROPERTIES), + 'index_name': fields.KeywordField(), + } + ) subtypes = fields.ObjectField( properties={ 'id': fields.IntegerField(), @@ -54,15 +55,12 @@ class ProductDocument(Document): ), 'address': fields.ObjectField( properties={ - 'city': fields.ObjectField( - properties={ - 'country': fields.ObjectField( - properties={ - 'code': fields.KeywordField() - } - ) - } - ) + 'id': fields.IntegerField(), + 'street_name_1': fields.TextField( + fields={'raw': fields.KeywordField()} + ), + 'postal_code': fields.KeywordField(), + 'coordinates': fields.GeoPointField(attr='location_field_indexing'), } ) } diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 34574207..962c4032 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -95,7 +95,7 @@ class TagDocumentSerializer(serializers.Serializer): return get_translated_value(obj.label) -class ProductTypeDocumentSerializer(serializers.Serializer): +class ProductTypeSerializer(serializers.Serializer): """Product type ES document serializer.""" id = serializers.IntegerField() @@ -140,19 +140,6 @@ class AnotherCityDocumentShortSerializer(CityDocumentShortSerializer): return None -class ProductEstablishmentDocumentSerializer(serializers.Serializer): - """Related to Product Establishment ES document serializer.""" - - id = serializers.IntegerField() - name = serializers.CharField() - slug = serializers.CharField() - index_name = serializers.CharField() - city = AnotherCityDocumentShortSerializer() - - def get_attribute(self, instance): - return instance.establishment if instance and instance.establishment else None - - class AddressDocumentSerializer(serializers.Serializer): """Address serializer for ES Document.""" @@ -175,6 +162,34 @@ class AddressDocumentSerializer(serializers.Serializer): return None +class PSAddressDocumentSerializer(serializers.Serializer): + """Address serializer for ES Document.""" + + id = serializers.IntegerField() + street_name_1 = serializers.CharField() + postal_code = serializers.CharField() + + def to_representation(self, instance): + if instance != AttrDict(d={}) or \ + (isinstance(instance, dict) and len(instance) != 0): + return super().to_representation(instance) + return None + + +class ProductEstablishmentSerializer(serializers.Serializer): + """Related to Product Establishment ES document serializer.""" + + id = serializers.IntegerField() + name = serializers.CharField() + slug = serializers.CharField() + index_name = serializers.CharField() + city = AnotherCityDocumentShortSerializer() + address = PSAddressDocumentSerializer(allow_null=True) + + def get_attribute(self, instance): + return instance.establishment if instance and instance.establishment else None + + class ScheduleDocumentSerializer(serializers.Serializer): """Schedule serializer for ES Document""" @@ -289,15 +304,15 @@ class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer): ) -class ProductDocumentSerializer(InFavoritesMixin, DocumentSerializer): +class ProductDocumentSerializer(InFavoritesMixin): """Product document serializer""" tags = TagsDocumentSerializer(many=True, source='related_tags') subtypes = ProductSubtypeDocumentSerializer(many=True, allow_null=True) wine_colors = TagDocumentSerializer(many=True) grape_variety = TagDocumentSerializer(many=True) - product_type = ProductTypeDocumentSerializer(allow_null=True) - establishment_detail = ProductEstablishmentDocumentSerializer(source='establishment', allow_null=True) + # product_type = ProductTypeSerializer(allow_null=True) + establishment_detail = ProductEstablishmentSerializer(source='establishment', allow_null=True) wine_origins = WineOriginSerializer(many=True) class Meta: @@ -321,7 +336,6 @@ class ProductDocumentSerializer(InFavoritesMixin, DocumentSerializer): 'subtypes', 'wine_colors', 'grape_variety', - 'establishment_detail', 'average_price', 'created', 'wine_origins', diff --git a/apps/search_indexes/signals.py b/apps/search_indexes/signals.py index 5bee4e17..ce7de7f7 100644 --- a/apps/search_indexes/signals.py +++ b/apps/search_indexes/signals.py @@ -12,8 +12,8 @@ def update_document(sender, **kwargs): instance = kwargs['instance'] app_label_model_name_to_filter = { - ('location','country'): 'address__city__country', - ('location','city'): 'address__city', + ('location', 'country'): 'address__city__country', + ('location', 'city'): 'address__city', ('location', 'address'): 'address', # todo: remove after migration ('establishment', 'establishmenttype'): 'establishment_type', @@ -34,8 +34,8 @@ def update_news(sender, **kwargs): model_name = sender._meta.model_name instance = kwargs['instance'] app_label_model_name_to_filter = { - ('location','country'): 'country', - ('news','newstype'): 'news_type', + ('location', 'country'): 'country', + ('news', 'newstype'): 'news_type', ('tag', 'tag'): 'tags', } filter_name = app_label_model_name_to_filter.get((app_label, model_name)) @@ -52,9 +52,9 @@ def update_product(sender, **kwargs): model_name = sender._meta.model_name instance = kwargs['instance'] app_label_model_name_to_filter = { - ('product','productstandard'): 'standards', + ('product', 'productstandard'): 'standards', ('product', 'producttype'): 'product_type', - ('tag','tag'): 'tags', + ('tag', 'tag'): 'tags', ('location', 'wineregion'): 'wine_region', ('location', 'winesubregion'): 'wine_sub_region', ('location', 'winevillage'): 'wine_village', From ca2ac15cdbca5399e96e42a49dfe9332c7e2d63a Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 11 Dec 2019 10:23:59 +0300 Subject: [PATCH 09/81] fix product type --- apps/search_indexes/documents/product.py | 2 +- apps/search_indexes/serializers.py | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/apps/search_indexes/documents/product.py b/apps/search_indexes/documents/product.py index 2a88f4ad..aa8fc999 100644 --- a/apps/search_indexes/documents/product.py +++ b/apps/search_indexes/documents/product.py @@ -19,7 +19,7 @@ class ProductDocument(Document): 'id': fields.IntegerField(), 'name': fields.ObjectField(attr='name_indexing', properties=OBJECT_FIELD_PROPERTIES), 'index_name': fields.KeywordField(), - } + }, ) subtypes = fields.ObjectField( properties={ diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 962c4032..4fc5a027 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -108,6 +108,9 @@ class ProductTypeSerializer(serializers.Serializer): return get_translated_value(obj.get('name')) return get_translated_value(obj.name) + def get_attribute(self, instance): + return instance.product_type if instance and instance.product_type else None + class CityDocumentShortSerializer(serializers.Serializer): """City serializer for ES Document,""" @@ -169,12 +172,6 @@ class PSAddressDocumentSerializer(serializers.Serializer): street_name_1 = serializers.CharField() postal_code = serializers.CharField() - def to_representation(self, instance): - if instance != AttrDict(d={}) or \ - (isinstance(instance, dict) and len(instance) != 0): - return super().to_representation(instance) - return None - class ProductEstablishmentSerializer(serializers.Serializer): """Related to Product Establishment ES document serializer.""" @@ -311,7 +308,7 @@ class ProductDocumentSerializer(InFavoritesMixin): subtypes = ProductSubtypeDocumentSerializer(many=True, allow_null=True) wine_colors = TagDocumentSerializer(many=True) grape_variety = TagDocumentSerializer(many=True) - # product_type = ProductTypeSerializer(allow_null=True) + product_type = ProductTypeSerializer(allow_null=True) establishment_detail = ProductEstablishmentSerializer(source='establishment', allow_null=True) wine_origins = WineOriginSerializer(many=True) From a254160f439c67481815676e37be6eed5c218ed7 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Wed, 11 Dec 2019 12:10:26 +0300 Subject: [PATCH 10/81] Add account csv endpoint --- apps/account/views/back.py | 74 +++++++++++++++++++++++++++++++------- 1 file changed, 61 insertions(+), 13 deletions(-) diff --git a/apps/account/views/back.py b/apps/account/views/back.py index c1eb32b5..92dca84d 100644 --- a/apps/account/views/back.py +++ b/apps/account/views/back.py @@ -3,6 +3,7 @@ from rest_framework import generics, permissions from rest_framework.filters import OrderingFilter import csv from django.http import HttpResponse, HttpResponseNotFound +from rest_framework.authtoken.models import Token from account import models from account.models import User @@ -50,20 +51,67 @@ class UserRUDView(generics.RetrieveUpdateDestroyAPIView): lookup_field = 'id' -def get_user_csv(request, user_id): +def get_user_csv(request, id): + # fields = ["id", "uuid", "nickname", "locale", "country_code", "city", "role", "consent_purpose", "consent_at", + # "last_seen_at", "created_at", "updated_at", "email", "is_admin", "ezuser_id", "ez_user_id", + # "encrypted_password", "reset_password_token", "reset_password_sent_at", "remember_created_at", + # "sign_in_count", "current_sign_in_at", "last_sign_in_at", "current_sign_in_ip", "last_sign_in_ip", + # "confirmation_token", "confirmed_at", "confirmation_sent_at", "unconfirmed_email", "webpush_subscription"] + + # uuid == id + # + # Не найдены: + # consent_purpose + # consent_at + # ezuser_id + # ez_user_id + # remember_created_at + # sign_in_count + # current_sign_in_at + # current_sign_in_ip + # last_sign_in_ip + # confirmed_at + # confirmation_sent_at + # webpush_subscription + # + # country_code не получить - клиент не привязан к стране + try: - user = User.objects.get(id=user_id).values_list() + user = User.objects.get(id=id) except User.DoesNotExist: return HttpResponseNotFound("User not found") - # - # - # response = HttpResponse(content_type='text/csv') - # response['Content-Disposition'] = f'attachment; filename="{user}.csv"' - # - # writer = csv.writer(response) - # writer.writerow(['First row', 'Foo', 'Bar', 'Baz']) - # writer.writerow(['Second row', 'A', 'B', 'C', '"Testing"', "Here's a quote"]) - # - # return response - return HttpResponseNotFound(user) \ No newline at end of file + try: + roles = " ".join([role for role in user.roles]) + except: + roles = "" + + token, _ = Token.objects.get_or_create(user=user) + + fields = { + "id": user.id, + "uuid": user.id, + "username": getattr(user, "username", ""), + "locale": getattr(user, "locale", ""), + "city": getattr(user, "city", ""), + "role": roles, + "created_at": getattr(user, "date_joined", ""), + "updated_at": user.last_login, + "email": user.email, + "is_admin": user.is_superuser, + "encrypted_password": user.password, + "reset_password_token": token.key, + "reset_password_sent_at": token.created, # TODO: не уверен в назначении поля, лучше проверить + "last_sign_in_at": user.last_login, # Повтор? + "confirmation_token": user.confirm_email_token, + "unconfirmed_email": 1 if user.unconfirmed_email else 0 + } + + response = HttpResponse(content_type='text/csv') + response['Content-Disposition'] = f'attachment; filename="{user.email}.csv"' + + writer = csv.writer(response) + writer.writerow(fields.keys()) + writer.writerow(fields.values()) + + return response From 907a9365e1b73abaa0f55f1cf14ec6fcd0ef22d3 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 11 Dec 2019 12:14:23 +0300 Subject: [PATCH 11/81] fix multislug news update --- apps/news/serializers.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 846bc31a..f22c7129 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -177,13 +177,23 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer): 'backoffice_title': {'allow_null': False}, } - def validate(self, attrs): - slugs = attrs.get('slugs', {}) - if models.News.objects.filter( - slugs__values__contains=list(slugs.values()) - ).exclude(id=attrs.get('id', 0)).exists(): - raise serializers.ValidationError({'slugs': _('News with this slug already exists.')}) - return attrs + def create(self, validated_data): + slugs = validated_data.get('slugs') + if slugs: + if models.News.objects.filter( + slugs__values__contains=list(slugs.values()) + ).exists(): + raise serializers.ValidationError({'slugs': _('News with this slug already exists.')}) + return super().create(validated_data) + + def update(self, instance, validated_data): + slugs = validated_data.get('slugs') + if slugs: + if models.News.objects.filter( + slugs__values__contains=list(slugs.values()) + ).exclude(pk=instance.pk).exists(): + raise serializers.ValidationError({'slugs': _('News with this slug already exists.')}) + return super().update(instance, validated_data) class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer, From fb5a9d4302581a356258626a0be12a3eefb88ca0 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 11 Dec 2019 12:26:22 +0300 Subject: [PATCH 12/81] refactored ads --- apps/advertisement/admin.py | 5 +++ apps/advertisement/models.py | 8 ++--- apps/advertisement/serializers/back.py | 30 +++++----------- apps/advertisement/serializers/common.py | 25 ++++++------- apps/advertisement/serializers/mobile.py | 12 +++---- apps/advertisement/serializers/web.py | 12 +++---- apps/advertisement/urls/back.py | 8 ++--- apps/advertisement/views/back.py | 35 +++++++++++-------- apps/advertisement/views/common.py | 6 ++-- apps/main/admin.py | 3 ++ .../migrations/0041_auto_20191211_0631.py | 18 ++++++++++ apps/main/models.py | 3 +- apps/main/serializers.py | 20 +++++++++-- apps/main/urls/back.py | 2 ++ apps/main/views/back.py | 10 +++++- 15 files changed, 121 insertions(+), 76 deletions(-) create mode 100644 apps/main/migrations/0041_auto_20191211_0631.py diff --git a/apps/advertisement/admin.py b/apps/advertisement/admin.py index 3754dca9..2728f4f9 100644 --- a/apps/advertisement/admin.py +++ b/apps/advertisement/admin.py @@ -14,3 +14,8 @@ class PageInline(admin.TabularInline): class AdvertisementModelAdmin(admin.ModelAdmin): """Admin model for model Advertisement""" inlines = (PageInline, ) + list_display = ('id', '__str__', 'block_level', + 'start', 'end', 'page_type') + list_filter = ('url', 'block_level', 'start', 'end', 'page_type', + 'pages__source') + date_hierarchy = 'created' diff --git a/apps/advertisement/models.py b/apps/advertisement/models.py index 66cd0024..b86a6168 100644 --- a/apps/advertisement/models.py +++ b/apps/advertisement/models.py @@ -71,11 +71,11 @@ class Advertisement(ProjectBaseMixin): return super().delete(using, keep_parents) @property - def mobile_page(self): + def mobile_pages(self): """Return mobile page""" - return self.pages.by_platform(Page.MOBILE).first() + return self.pages.by_platform(Page.MOBILE) @property - def web_page(self): + def web_pages(self): """Return web page""" - return self.pages.by_platform(Page.WEB).first() + return self.pages.by_platform(Page.WEB) diff --git a/apps/advertisement/serializers/back.py b/apps/advertisement/serializers/back.py index 9dc8b029..2ca5a18f 100644 --- a/apps/advertisement/serializers/back.py +++ b/apps/advertisement/serializers/back.py @@ -1,26 +1,14 @@ """Serializers for back office app advertisements""" -from main.serializers import PageBaseSerializer +from advertisement.serializers import AdvertisementBaseSerializer +from main.serializers import PageExtendedSerializer -class AdvertisementPageBaseSerializer(PageBaseSerializer): - """Base serializer for linking page w/ advertisement.""" +class AdvertisementDetailSerializer(AdvertisementBaseSerializer): + """Advertisement serializer for back office.""" + pages = PageExtendedSerializer(many=True, read_only=True) - class Meta(PageBaseSerializer.Meta): + class Meta(AdvertisementBaseSerializer.Meta): """Meta class.""" - - PageBaseSerializer.Meta.extra_kwargs.update({ - 'advertisement': {'write_only': True}, - 'image_url': {'required': True}, - 'width': {'required': True}, - 'height': {'required': True}, - }) - - -class AdvertisementPageListCreateSerializer(AdvertisementPageBaseSerializer): - """Serializer for linking page w/ advertisement.""" - - def create(self, validated_data): - """Overridden create method.""" - - validated_data['advertisement'] = self.context.get('view').get_object() - return super().create(validated_data) + fields = AdvertisementBaseSerializer.Meta.fields + [ + 'pages', + ] diff --git a/apps/advertisement/serializers/common.py b/apps/advertisement/serializers/common.py index 9caee0c2..b673475e 100644 --- a/apps/advertisement/serializers/common.py +++ b/apps/advertisement/serializers/common.py @@ -2,15 +2,15 @@ from rest_framework import serializers from advertisement import models -from translation.serializers import LanguageSerializer -from main.serializers import SiteShortSerializer, PageBaseSerializer -from translation.models import Language from main.models import SiteSettings +from main.serializers import PageTypeBaseSerializer +from translation.models import Language class AdvertisementBaseSerializer(serializers.ModelSerializer): """Base serializer for model Advertisement.""" - + page_type_detail = PageTypeBaseSerializer(read_only=True, + source='page_type') target_languages = serializers.PrimaryKeyRelatedField( queryset=Language.objects.all(), many=True, @@ -34,16 +34,17 @@ class AdvertisementBaseSerializer(serializers.ModelSerializer): 'target_sites', 'start', 'end', + 'page_type', + 'page_type_detail', ] + extra_kwargs = { + 'page_type': {'required': True, 'write_only': True} + } -class AdvertisementPageTypeCommonListSerializer(AdvertisementBaseSerializer): - """Serializer for AdvertisementPageTypeCommonView.""" - - page = PageBaseSerializer(source='common_page', read_only=True) - +class AdvertisementSerializer(AdvertisementBaseSerializer): + """Serializer for model Advertisement.""" class Meta(AdvertisementBaseSerializer.Meta): """Meta class.""" - fields = AdvertisementBaseSerializer.Meta.fields + [ - 'page', - ] + fields = AdvertisementBaseSerializer.Meta.fields.copy() + fields.pop(fields.index('page_type_detail')) diff --git a/apps/advertisement/serializers/mobile.py b/apps/advertisement/serializers/mobile.py index 80a19b82..37d810b4 100644 --- a/apps/advertisement/serializers/mobile.py +++ b/apps/advertisement/serializers/mobile.py @@ -1,15 +1,15 @@ """Serializers for mobile app advertisements""" -from advertisement.serializers import AdvertisementBaseSerializer +from advertisement.serializers import AdvertisementSerializer from main.serializers import PageBaseSerializer -class AdvertisementPageTypeMobileListSerializer(AdvertisementBaseSerializer): +class AdvertisementPageTypeMobileListSerializer(AdvertisementSerializer): """Serializer for AdvertisementPageTypeMobileView.""" - page = PageBaseSerializer(source='mobile_page', read_only=True) + pages = PageBaseSerializer(many=True, source='mobile_pages', read_only=True) - class Meta(AdvertisementBaseSerializer.Meta): + class Meta(AdvertisementSerializer.Meta): """Meta class.""" - fields = AdvertisementBaseSerializer.Meta.fields + [ - 'page', + fields = AdvertisementSerializer.Meta.fields + [ + 'pages', ] diff --git a/apps/advertisement/serializers/web.py b/apps/advertisement/serializers/web.py index 175f1875..a78c1f53 100644 --- a/apps/advertisement/serializers/web.py +++ b/apps/advertisement/serializers/web.py @@ -1,15 +1,15 @@ """Serializers for web app advertisements""" -from advertisement.serializers import AdvertisementBaseSerializer +from advertisement.serializers import AdvertisementSerializer from main.serializers import PageBaseSerializer -class AdvertisementPageTypeWebListSerializer(AdvertisementBaseSerializer): +class AdvertisementPageTypeWebListSerializer(AdvertisementSerializer): """Serializer for AdvertisementPageTypeWebView.""" - page = PageBaseSerializer(source='web_page', read_only=True) + pages = PageBaseSerializer(many=True, source='web_pages', read_only=True) - class Meta(AdvertisementBaseSerializer.Meta): + class Meta(AdvertisementSerializer.Meta): """Meta class.""" - fields = AdvertisementBaseSerializer.Meta.fields + [ - 'page', + fields = AdvertisementSerializer.Meta.fields + [ + 'pages', ] diff --git a/apps/advertisement/urls/back.py b/apps/advertisement/urls/back.py index 2502da0d..7a5cb133 100644 --- a/apps/advertisement/urls/back.py +++ b/apps/advertisement/urls/back.py @@ -9,10 +9,10 @@ app_name = 'advertisements' urlpatterns = [ path('', views.AdvertisementListCreateView.as_view(), name='list-create'), path('/', views.AdvertisementRUDView.as_view(), name='rud'), - path('/pages/', views.AdvertisementPageListCreateView.as_view(), - name='page-list-create'), - path('/pages//', views.AdvertisementPageRUDView.as_view(), - name='page-rud') + path('/pages/', views.AdvertisementPageCreateView.as_view(), + name='ad-page-create'), + path('/pages//', views.AdvertisementPageUDView.as_view(), + name='ad-page-update-destroy') ] urlpatterns += common_urlpatterns diff --git a/apps/advertisement/views/back.py b/apps/advertisement/views/back.py index a2973589..27a016a2 100644 --- a/apps/advertisement/views/back.py +++ b/apps/advertisement/views/back.py @@ -1,19 +1,19 @@ """Back office views for app advertisement""" -from rest_framework import generics +from rest_framework import generics, status +from rest_framework.response import Response from rest_framework import permissions from django.shortcuts import get_object_or_404 +from main.serializers import PageExtendedSerializer from advertisement.models import Advertisement -from rest_framework.response import Response -from rest_framework import status from advertisement.serializers import (AdvertisementBaseSerializer, - AdvertisementPageBaseSerializer, - AdvertisementPageListCreateSerializer) + AdvertisementDetailSerializer) class AdvertisementBackOfficeViewMixin(generics.GenericAPIView): """Base back office advertisement view.""" + pagination_class = None permission_classes = (permissions.IsAuthenticated, ) def get_queryset(self): @@ -31,14 +31,14 @@ class AdvertisementRUDView(AdvertisementBackOfficeViewMixin, generics.RetrieveUpdateDestroyAPIView): """Retrieve|Update|Destroy advertisement page view.""" - serializer_class = AdvertisementBaseSerializer + serializer_class = AdvertisementDetailSerializer -class AdvertisementPageListCreateView(AdvertisementBackOfficeViewMixin, - generics.ListCreateAPIView): - """Retrieve|Update|Destroy advertisement page view.""" +class AdvertisementPageCreateView(AdvertisementBackOfficeViewMixin, + generics.CreateAPIView): + """Create advertisement page view.""" - serializer_class = AdvertisementPageListCreateSerializer + serializer_class = PageExtendedSerializer def get_object(self): """Returns the object the view is displaying.""" @@ -56,12 +56,19 @@ class AdvertisementPageListCreateView(AdvertisementBackOfficeViewMixin, """Overridden get_queryset method.""" return self.get_object().pages.all() + def create(self, request, *args, **kwargs): + """Overridden create method.""" + request.data.update({'advertisement': self.get_object().pk}) + super().create(request, *args, **kwargs) + return Response(status=status.HTTP_200_OK) -class AdvertisementPageRUDView(AdvertisementBackOfficeViewMixin, - generics.RetrieveUpdateDestroyAPIView): - """Create|Retrieve|Update|Destroy advertisement page view.""" - serializer_class = AdvertisementPageBaseSerializer +class AdvertisementPageUDView(AdvertisementBackOfficeViewMixin, + generics.UpdateAPIView, + generics.DestroyAPIView): + """Update|Destroy advertisement page view.""" + + serializer_class = PageExtendedSerializer def get_object(self): """Returns the object the view is displaying.""" diff --git a/apps/advertisement/views/common.py b/apps/advertisement/views/common.py index 02c61873..12702d4b 100644 --- a/apps/advertisement/views/common.py +++ b/apps/advertisement/views/common.py @@ -3,8 +3,7 @@ from rest_framework import generics from rest_framework import permissions from advertisement.models import Advertisement -from advertisement.serializers import AdvertisementBaseSerializer, \ - AdvertisementPageTypeCommonListSerializer +from advertisement.serializers import AdvertisementBaseSerializer class AdvertisementBaseView(generics.GenericAPIView): @@ -16,8 +15,7 @@ class AdvertisementBaseView(generics.GenericAPIView): def get_queryset(self): """Overridden get queryset method.""" - return Advertisement.objects.with_base_related() \ - .by_locale(self.request.locale) + return Advertisement.objects.with_base_related() class AdvertisementPageTypeListView(AdvertisementBaseView, generics.ListAPIView): diff --git a/apps/main/admin.py b/apps/main/admin.py index 4b7038e7..315d1c2b 100644 --- a/apps/main/admin.py +++ b/apps/main/admin.py @@ -51,3 +51,6 @@ class PageTypeAdmin(admin.ModelAdmin): @admin.register(models.Page) class PageAdmin(admin.ModelAdmin): """Page admin.""" + list_display = ('id', '__str__', 'advertisement') + list_filter = ('advertisement__url', 'source') + date_hierarchy = 'created' diff --git a/apps/main/migrations/0041_auto_20191211_0631.py b/apps/main/migrations/0041_auto_20191211_0631.py new file mode 100644 index 00000000..973f4afd --- /dev/null +++ b/apps/main/migrations/0041_auto_20191211_0631.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.7 on 2019-12-11 06:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('advertisement', '0008_auto_20191116_1135'), + ('main', '0040_footer'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='page', + unique_together={('advertisement', 'source')}, + ), + ] diff --git a/apps/main/models.py b/apps/main/models.py index 0869169e..ae4168ea 100644 --- a/apps/main/models.py +++ b/apps/main/models.py @@ -305,7 +305,7 @@ class PageQuerySet(models.QuerySet): def by_platform(self, platform: int): """Filter by platform.""" - return self.filter(source=platform) + return self.filter(source__in=[Page.ALL, platform]) class Page(URLImageMixin, PlatformMixin, ProjectBaseMixin): @@ -325,6 +325,7 @@ class Page(URLImageMixin, PlatformMixin, ProjectBaseMixin): """Meta class.""" verbose_name = _('page') verbose_name_plural = _('pages') + unique_together = ('advertisement', 'source') def __str__(self): """Overridden dunder method.""" diff --git a/apps/main/serializers.py b/apps/main/serializers.py index 033da976..d586d71a 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -152,8 +152,6 @@ class SiteShortSerializer(serializers.ModelSerializer): ] - - class AwardBaseSerializer(serializers.ModelSerializer): """Award base serializer.""" @@ -234,10 +232,26 @@ class PageBaseSerializer(serializers.ModelSerializer): 'advertisement', ] extra_kwargs = { - 'establishment': {'write_only': True} + 'advertisement': {'write_only': True}, + 'image_url': {'required': True}, + 'width': {'required': True}, + 'height': {'required': True}, } +class PageExtendedSerializer(PageBaseSerializer): + """Extended serializer for model Page.""" + source_display = serializers.CharField(read_only=True, + source='get_source_display') + + class Meta(PageBaseSerializer.Meta): + """Meta class.""" + fields = PageBaseSerializer.Meta.fields + [ + 'source', + 'source_display', + ] + + class PageTypeBaseSerializer(serializers.ModelSerializer): """Serializer fro model PageType.""" diff --git a/apps/main/urls/back.py b/apps/main/urls/back.py index a9e55311..b4e196a3 100644 --- a/apps/main/urls/back.py +++ b/apps/main/urls/back.py @@ -20,6 +20,8 @@ urlpatterns = [ name='site-feature-rud'), path('footer/', views.FooterBackView.as_view(), name='footer-list-create'), path('footer//', views.FooterRUDBackView.as_view(), name='footer-rud'), + path('page-types/', views.PageTypeListCreateView.as_view(), + name='page-types-list-create') ] diff --git a/apps/main/views/back.py b/apps/main/views/back.py index 3d73f88c..f6f987af 100644 --- a/apps/main/views/back.py +++ b/apps/main/views/back.py @@ -4,7 +4,7 @@ from rest_framework import generics, permissions from main import serializers from main.filters import AwardFilter -from main.models import Award, Footer +from main.models import Award, Footer, PageType from main.views import SiteSettingsView, SiteListView @@ -81,3 +81,11 @@ class FooterRUDBackView(generics.RetrieveUpdateDestroyAPIView): permission_classes = (permissions.IsAuthenticatedOrReadOnly,) serializer_class = serializers.FooterBackSerializer queryset = Footer.objects.all() + + +class PageTypeListCreateView(generics.ListCreateAPIView): + """PageType back office view.""" + permission_classes = (permissions.IsAuthenticatedOrReadOnly, ) + pagination_class = None + serializer_class = serializers.PageTypeBaseSerializer + queryset = PageType.objects.all() From 19c42ab1d7f39c87ea37e1ade639b4d9c567197c Mon Sep 17 00:00:00 2001 From: dormantman Date: Wed, 11 Dec 2019 13:03:31 +0300 Subject: [PATCH 13/81] Added page types --- apps/tag/views.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/apps/tag/views.py b/apps/tag/views.py index fc870fe9..db3970e5 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -1,8 +1,5 @@ """Tag views.""" from django.conf import settings -from django_elasticsearch_dsl_drf import constants -from django_elasticsearch_dsl_drf.filter_backends import FilteringFilterBackend -from elasticsearch_dsl import TermsFacet from rest_framework import generics from rest_framework import mixins from rest_framework import permissions @@ -81,7 +78,9 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): result_list = serializer.data query_params = request.query_params - if 'toque_number__in' in query_params: + params_type = query_params['type'] + + if params_type == 'restaurant' and 'toque_number__in' in query_params: toques = { "index_name": "toque_number", "label_translated": "Toques", @@ -94,7 +93,7 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): } result_list.append(toques) - if 'wine_region_id__in' in query_params: + if params_type == 'winery' and 'wine_region_id__in' in query_params: try: wine_region_id = int(query_params['wine_region_id__in']) @@ -114,7 +113,7 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): except ValueError: pass - if 'works_noon__in' in query_params: + if params_type == 'restaurant' and 'works_noon__in' in query_params: week_days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") works_noon = { "index_name": "works_noon", @@ -128,11 +127,11 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): } result_list.append(works_noon) - if 'works_evening__in' in query_params: + if params_type == 'restaurant' and 'works_evening__in' in query_params: week_days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") works_evening = { "index_name": "works_evening", - "label_translated": "Open noon", + "label_translated": "Open evening", "param_name": "works_evening__in", "filters": [{ "id": weekday, @@ -142,7 +141,7 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): } result_list.append(works_evening) - if 'works_now' in query_params: + if params_type in ('restaurant', 'artisan') and 'works_now' in query_params: works_now = { "index_name": "open_now", "label_translated": "Open now", @@ -151,6 +150,11 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): } result_list.append(works_now) + if 'tags_id__in' in query_params: + # filtering by params_type and tags id + # todo: result_list.append( filtering_data ) + pass + return Response(result_list) @@ -244,4 +248,4 @@ class TagCategoryBackOfficeViewSet(mixins.CreateModelMixin, if obj_type == self.bind_object_serializer_class.ESTABLISHMENT_TYPE: tag_category.establishment_types.remove(related_object) elif obj_type == self.bind_object_serializer_class.NEWS_TYPE: - tag_category.news_types.remove(related_object) \ No newline at end of file + tag_category.news_types.remove(related_object) From 822b18e2f40b1e6c27525fcf94dd5db24b0cf19c Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 11 Dec 2019 14:58:31 +0300 Subject: [PATCH 14/81] create new image after crop --- apps/gallery/serializers.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/apps/gallery/serializers.py b/apps/gallery/serializers.py index 36360180..0aa4389f 100644 --- a/apps/gallery/serializers.py +++ b/apps/gallery/serializers.py @@ -1,8 +1,9 @@ from django.conf import settings from django.core.validators import MinValueValidator, MaxValueValidator +from django.core.files.base import ContentFile from rest_framework import serializers -from sorl.thumbnail.parsers import parse_crop -from sorl.thumbnail.parsers import ThumbnailParseError +from sorl.thumbnail import get_thumbnail +from sorl.thumbnail.parsers import parse_crop, ThumbnailParseError from django.utils.translation import gettext_lazy as _ from . import models @@ -88,15 +89,23 @@ class CropImageSerializer(ImageSerializer): quality = validated_data.pop('quality') crop = validated_data.pop('crop') + crop_params = { + 'geometry': f'{width}x{height}', + 'quality': quality, + 'crop': crop, + } + cropped_image = self._image.get_cropped_image(**crop_params) image = self._image + image.pk = None + crop_params['geometry_string'] = crop_params.pop('geometry') + resized = get_thumbnail(self._image.image, **crop_params) + image.image.save(resized.name, ContentFile(resized.read()), True) + image.save() if image and width and height: setattr(image, 'cropped_image', - image.get_cropped_image( - geometry=f'{width}x{height}', - quality=quality, - crop=crop)) + cropped_image) return image @property From 165509bc642d6bf1beac648ddc450abfc09386d9 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 11 Dec 2019 15:13:06 +0300 Subject: [PATCH 15/81] update news_gallery model --- apps/news/serializers.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index f22c7129..b45bfa3f 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -243,6 +243,15 @@ class NewsBackOfficeGallerySerializer(serializers.ModelSerializer): """Get url kwargs from request.""" return self.context.get('request').parser_context.get('kwargs') + def create(self, validated_data): + news_pk = self.get_request_kwargs().get('pk') + image_id = self.get_request_kwargs().get('image_id') + news_gallery_model = models.NewsGallery.objects.filter(image_id=image_id, news_id=news_pk).first() + if news_gallery_model: + news_gallery_model.update(**validated_data) + return news_gallery_model + return super().create(validated_data) + def validate(self, attrs): """Override validate method.""" news_pk = self.get_request_kwargs().get('pk') From 39c8f6d15294495e10a2c1723a2e0dcfd5a1d6d4 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 11 Dec 2019 15:47:08 +0300 Subject: [PATCH 16/81] remove carousel hardcode --- apps/main/views/common.py | 3 --- project/settings/base.py | 3 --- 2 files changed, 6 deletions(-) diff --git a/apps/main/views/common.py b/apps/main/views/common.py index 674d045e..20033661 100644 --- a/apps/main/views/common.py +++ b/apps/main/views/common.py @@ -70,9 +70,6 @@ class CarouselListView(generics.ListAPIView): def get_queryset(self): country_code = self.request.country_code - if hasattr(settings, 'CAROUSEL_ITEMS') and country_code in settings.INTERNATIONAL_COUNTRY_CODES: - qs = models.Carousel.objects.filter(id__in=settings.CAROUSEL_ITEMS) - return qs qs = models.Carousel.objects.is_parsed().active() if country_code: qs = qs.by_country_code(country_code) diff --git a/project/settings/base.py b/project/settings/base.py index 31b7b8f7..2dfe1ba1 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -516,9 +516,6 @@ PHONENUMBER_DEFAULT_REGION = "FR" FALLBACK_LOCALE = 'en-GB' -# TMP TODO remove it later -# Временный хардкод для демонстрации > 15 ноября, потом удалить! -CAROUSEL_ITEMS = [465] ESTABLISHMENT_CHOSEN_TAGS = ['gastronomic', 'en_vogue', 'terrace', 'streetfood', 'business', 'bar_cocktail', 'brunch', 'pop'] NEWS_CHOSEN_TAGS = ['eat', 'drink', 'cook', 'style', 'international', 'event', 'partnership'] INTERNATIONAL_COUNTRY_CODES = ['www', 'main', 'next'] From f0709fa409fc029d2ef1191ef7b4ff746b882c8b Mon Sep 17 00:00:00 2001 From: dormantman Date: Wed, 11 Dec 2019 16:16:08 +0300 Subject: [PATCH 17/81] Fixed tag filter type --- apps/tag/views.py | 77 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 23 deletions(-) diff --git a/apps/tag/views.py b/apps/tag/views.py index db3970e5..dad2582f 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -78,9 +78,26 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): result_list = serializer.data query_params = request.query_params - params_type = query_params['type'] + params_type = query_params.get('type') - if params_type == 'restaurant' and 'toque_number__in' in query_params: + week_days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") + flags = ('toque_number', 'wine_region', 'works_noon', 'works_evening', 'works_now') + filter_flags = {flag_name: False for flag_name in flags} + additional_flags = [] + + if params_type == 'restaurant': + additional_flags += ['toque_number', 'works_noon', 'works_evening', 'works_now'] + + elif params_type == 'winery': + additional_flags += ['wine_region'] + + elif params_type == 'artisan': + additional_flags += ['works_now', 'works_at_weekday'] + + for flag_name in additional_flags: + filter_flags[flag_name] = True + + if filter_flags['toque_number']: toques = { "index_name": "toque_number", "label_translated": "Toques", @@ -93,28 +110,29 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): } result_list.append(toques) - if params_type == 'winery' and 'wine_region_id__in' in query_params: - try: - wine_region_id = int(query_params['wine_region_id__in']) + if filter_flags['wine_region']: + wine_region_id = query_params.get('wine_region_id__in') - wine_regions = { - "index_name": "wine_region", - "label_translated": "Wine region", - "param_name": "wine_region_id__in", - "filters": [{ - "id": obj.id, - "index_name": obj.name.lower().replace(' ', '_'), - "label_translated": obj.name - } for obj in WineRegion.objects.filter(id=wine_region_id)] - } + if str(wine_region_id).isdigit(): + queryset = WineRegion.objects.filter(id=int(wine_region_id)) - result_list.append(wine_regions) + else: + queryset = WineRegion.objects.all() - except ValueError: - pass + wine_regions = { + "index_name": "wine_region", + "label_translated": "Wine region", + "param_name": "wine_region_id__in", + "filters": [{ + "id": obj.id, + "index_name": obj.name.lower().replace(' ', '_'), + "label_translated": obj.name + } for obj in queryset] + } - if params_type == 'restaurant' and 'works_noon__in' in query_params: - week_days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") + result_list.append(wine_regions) + + if filter_flags['works_noon']: works_noon = { "index_name": "works_noon", "label_translated": "Open noon", @@ -127,8 +145,8 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): } result_list.append(works_noon) - if params_type == 'restaurant' and 'works_evening__in' in query_params: - week_days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") + if filter_flags['works_evening']: + works_evening = { "index_name": "works_evening", "label_translated": "Open evening", @@ -141,7 +159,7 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): } result_list.append(works_evening) - if params_type in ('restaurant', 'artisan') and 'works_now' in query_params: + if filter_flags['works_now']: works_now = { "index_name": "open_now", "label_translated": "Open now", @@ -150,6 +168,19 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): } result_list.append(works_now) + if filter_flags['works_at_weekday']: + works_at_weekday = { + "index_name": "works_at_weekday", + "label_translated": "Works at weekday", + "param_name": "works_at_weekday__in", + "filters": [{ + "id": weekday, + "index_name": week_days[weekday].lower(), + "label_translated": week_days[weekday] + } for weekday in range(7)] + } + 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 ) From d8e3ad0bb5179c87d3afb85985de7b06ae8c02e1 Mon Sep 17 00:00:00 2001 From: dormantman Date: Wed, 11 Dec 2019 16:21:30 +0300 Subject: [PATCH 18/81] Added works_at_weekday --- apps/tag/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/tag/views.py b/apps/tag/views.py index dad2582f..75472fff 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -81,7 +81,7 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): params_type = query_params.get('type') week_days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") - flags = ('toque_number', 'wine_region', 'works_noon', 'works_evening', 'works_now') + flags = ('toque_number', 'wine_region', 'works_noon', 'works_evening', 'works_now', 'works_at_weekday') filter_flags = {flag_name: False for flag_name in flags} additional_flags = [] From 9f27b0c5d9e8246ae65b644ff724191fcdab615d Mon Sep 17 00:00:00 2001 From: dormantman Date: Wed, 11 Dec 2019 16:27:18 +0300 Subject: [PATCH 19/81] Added different types --- apps/tag/views.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/tag/views.py b/apps/tag/views.py index 75472fff..d506e23f 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -79,6 +79,10 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): query_params = request.query_params params_type = query_params.get('type') + if query_params.get('establishment_type'): + params_type = query_params.get('establishment_type') + elif query_params.get('product_type'): + params_type = query_params.get('product_type') week_days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") flags = ('toque_number', 'wine_region', 'works_noon', 'works_evening', 'works_now', 'works_at_weekday') @@ -97,6 +101,8 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): for flag_name in additional_flags: filter_flags[flag_name] = True + print(filter_flags) + if filter_flags['toque_number']: toques = { "index_name": "toque_number", From 0b1b960108b970c02562e15da1bf5e5a14b79ec5 Mon Sep 17 00:00:00 2001 From: dormantman Date: Wed, 11 Dec 2019 16:27:52 +0300 Subject: [PATCH 20/81] Remove excess output --- apps/tag/views.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/tag/views.py b/apps/tag/views.py index d506e23f..e56447bc 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -101,8 +101,6 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): for flag_name in additional_flags: filter_flags[flag_name] = True - print(filter_flags) - if filter_flags['toque_number']: toques = { "index_name": "toque_number", From f182ec4981a1fe9739613f29f435027a9437d5d8 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 11 Dec 2019 17:07:29 +0300 Subject: [PATCH 21/81] remove wrong unique constraint --- .../news/migrations/0041_auto_20191211_1406.py | 18 ++++++++++++++++++ apps/news/models.py | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 apps/news/migrations/0041_auto_20191211_1406.py diff --git a/apps/news/migrations/0041_auto_20191211_1406.py b/apps/news/migrations/0041_auto_20191211_1406.py new file mode 100644 index 00000000..70ca5cf1 --- /dev/null +++ b/apps/news/migrations/0041_auto_20191211_1406.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.7 on 2019-12-11 14:06 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('gallery', '0006_merge_20191027_1758'), + ('news', '0040_remove_news_slug'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='newsgallery', + unique_together={('news', 'image')}, + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index f54378b6..69ccf392 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -320,4 +320,4 @@ class NewsGallery(IntermediateGalleryModelMixin): """NewsGallery meta class.""" verbose_name = _('news gallery') verbose_name_plural = _('news galleries') - unique_together = (('news', 'is_main'), ('news', 'image')) + unique_together = (('news', 'image'),) From 90f75c6da53287bf3050a8787099f1b69660edd4 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 11 Dec 2019 17:54:53 +0300 Subject: [PATCH 22/81] update news gallery --- ..._20191211_1406.py => 0041_auto_20191211_1424.py} | 4 ++-- apps/news/models.py | 2 +- apps/news/serializers.py | 13 +++++++------ 3 files changed, 10 insertions(+), 9 deletions(-) rename apps/news/migrations/{0041_auto_20191211_1406.py => 0041_auto_20191211_1424.py} (75%) diff --git a/apps/news/migrations/0041_auto_20191211_1406.py b/apps/news/migrations/0041_auto_20191211_1424.py similarity index 75% rename from apps/news/migrations/0041_auto_20191211_1406.py rename to apps/news/migrations/0041_auto_20191211_1424.py index 70ca5cf1..87d3295d 100644 --- a/apps/news/migrations/0041_auto_20191211_1406.py +++ b/apps/news/migrations/0041_auto_20191211_1424.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.7 on 2019-12-11 14:06 +# Generated by Django 2.2.7 on 2019-12-11 14:24 from django.db import migrations @@ -13,6 +13,6 @@ class Migration(migrations.Migration): operations = [ migrations.AlterUniqueTogether( name='newsgallery', - unique_together={('news', 'image')}, + unique_together={('news', 'image'),}, ), ] diff --git a/apps/news/models.py b/apps/news/models.py index 69ccf392..b4e1b5d5 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -320,4 +320,4 @@ class NewsGallery(IntermediateGalleryModelMixin): """NewsGallery meta class.""" verbose_name = _('news gallery') verbose_name_plural = _('news galleries') - unique_together = (('news', 'image'),) + unique_together = [['news', 'image'],] diff --git a/apps/news/serializers.py b/apps/news/serializers.py index b45bfa3f..41b6ebc4 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -246,10 +246,11 @@ class NewsBackOfficeGallerySerializer(serializers.ModelSerializer): def create(self, validated_data): news_pk = self.get_request_kwargs().get('pk') image_id = self.get_request_kwargs().get('image_id') - news_gallery_model = models.NewsGallery.objects.filter(image_id=image_id, news_id=news_pk).first() - if news_gallery_model: - news_gallery_model.update(**validated_data) - return news_gallery_model + qs = models.NewsGallery.objects.filter(image_id=image_id, news_id=news_pk) + instance = qs.first() + if instance: + qs.update(**validated_data) + return instance return super().create(validated_data) def validate(self, attrs): @@ -268,8 +269,8 @@ 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.')}) + # if image in news.gallery.all(): + # raise serializers.ValidationError({'detail': _('Image is already added.')}) attrs['news'] = news attrs['image'] = image From 6cbfc38c9c159dde2c48b7278ac3dd3a3b1c1139 Mon Sep 17 00:00:00 2001 From: dormantman Date: Wed, 11 Dec 2019 17:58:39 +0300 Subject: [PATCH 23/81] Added back collections method for collection detail --- apps/collection/urls/back.py | 8 +++++++- apps/collection/views/back.py | 10 ++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/collection/urls/back.py b/apps/collection/urls/back.py index 6a6dbd54..d7f77b9a 100644 --- a/apps/collection/urls/back.py +++ b/apps/collection/urls/back.py @@ -1,4 +1,5 @@ """Collection common urlpaths.""" +from django.urls import path from rest_framework.routers import SimpleRouter from collection.views import back as views @@ -7,4 +8,9 @@ app_name = 'collection' router = SimpleRouter() router.register(r'', views.CollectionBackOfficeViewSet) -urlpatterns = router.urls +urlpatterns = [ + path('//', views.CollectionBackOfficeView.as_view(), name='detail'), +] + +urlpatterns += router.urls + diff --git a/apps/collection/views/back.py b/apps/collection/views/back.py index a989ec56..65bd6c8b 100644 --- a/apps/collection/views/back.py +++ b/apps/collection/views/back.py @@ -1,3 +1,4 @@ +from rest_framework import generics from rest_framework import permissions from rest_framework import viewsets, mixins @@ -47,3 +48,12 @@ class CollectionBackOfficeViewSet(mixins.CreateModelMixin, collection.establishments.remove(related_object) elif obj_type == self.bind_object_serializer_class.PRODUCT: collection.products.remove(related_object) + + +class CollectionBackOfficeView(generics.GenericAPIView): + """ViewS for Collection model.""" + + pagination_class = None + permission_classes = (permissions.IsAuthenticated,) + queryset = models.Collection.objects.all() + serializer_class = serializers.CollectionBackOfficeSerializer From a6b77e74b541546c9f7a09e2fc8f7e519f8bdd16 Mon Sep 17 00:00:00 2001 From: dormantman Date: Wed, 11 Dec 2019 17:59:42 +0300 Subject: [PATCH 24/81] Remove unnecessary slash --- apps/collection/urls/back.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/collection/urls/back.py b/apps/collection/urls/back.py index d7f77b9a..f97a3945 100644 --- a/apps/collection/urls/back.py +++ b/apps/collection/urls/back.py @@ -9,7 +9,7 @@ router = SimpleRouter() router.register(r'', views.CollectionBackOfficeViewSet) urlpatterns = [ - path('//', views.CollectionBackOfficeView.as_view(), name='detail'), + path('/', views.CollectionBackOfficeView.as_view(), name='detail'), ] urlpatterns += router.urls From e0a66f300b76f8d60531f070aeb06c9b50c4cfb4 Mon Sep 17 00:00:00 2001 From: dormantman Date: Wed, 11 Dec 2019 18:07:04 +0300 Subject: [PATCH 25/81] Hide unnecessary fields --- apps/collection/serializers/back.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/collection/serializers/back.py b/apps/collection/serializers/back.py index 48c25f6c..6cedb087 100644 --- a/apps/collection/serializers/back.py +++ b/apps/collection/serializers/back.py @@ -33,11 +33,11 @@ class CollectionBackOfficeSerializer(CollectionBaseSerializer): 'on_top', 'country', 'country_id', - 'block_size', - 'description', + # 'block_size', + # 'description', 'slug', - 'start', - 'end', + # 'start', + # 'end', 'count_related_objects', 'related_object_names', ] From d2f011417afb38acdfc5deaf5e18f8c9d8831254 Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Wed, 11 Dec 2019 18:11:39 +0300 Subject: [PATCH 26/81] add datamigrate and change for models --- apps/account/management/commands/add_ownership.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/account/management/commands/add_ownership.py diff --git a/apps/account/management/commands/add_ownership.py b/apps/account/management/commands/add_ownership.py new file mode 100644 index 00000000..e69de29b From 86b2e708f05162c0791fbc81c40eae3c060d5980 Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Wed, 11 Dec 2019 18:12:26 +0300 Subject: [PATCH 27/81] add datamigrate and change for models --- .../management/commands/add_ownership.py | 49 +++++++++++++++ .../migrations/0026_auto_20191211_1134.py | 25 ++++++++ .../migrations/0027_auto_20191211_1444.py | 18 ++++++ apps/account/models.py | 59 ++++++++++++++----- apps/account/serializers/back.py | 4 ++ apps/account/urls/back.py | 1 + apps/account/views/back.py | 6 ++ apps/transfer/models.py | 16 +++++ 8 files changed, 163 insertions(+), 15 deletions(-) create mode 100644 apps/account/migrations/0026_auto_20191211_1134.py create mode 100644 apps/account/migrations/0027_auto_20191211_1444.py diff --git a/apps/account/management/commands/add_ownership.py b/apps/account/management/commands/add_ownership.py index e69de29b..84926d9c 100644 --- a/apps/account/management/commands/add_ownership.py +++ b/apps/account/management/commands/add_ownership.py @@ -0,0 +1,49 @@ +from django.core.management.base import BaseCommand +from tqdm import tqdm + +from account.models import User, UserRole, Role +from transfer.models import OwnershipAffs +from establishment.models import Establishment + + +class Command(BaseCommand): + help = """Add ownership to UserRoles.""" + + def handle(self, *args, **kwarg): + create_user_roles = [] + # filter owner records with state not null only + owners = OwnershipAffs.objects.filter(state__isnull=False) + + # Get role, may be more then 1 + role = Role.objects.filter(role=Role.ESTABLISHMENT_MANAGER).first() + if not role: + role = Role.objects.create( + role=Role.ESTABLISHMENT_MANAGER + ) + + for owner in tqdm(owners): + user = User.objects.filter( + old_id=owner.account_id).first() + requester = User.objects.filter( + old_id=owner.requester_id).first() + establishment = Establishment.objects.filter( + old_id=owner.establishment_id).first() + + if user and establishment: + user_role = UserRole.objects.filter( + user=user, role=role, establishment=establishment, state=owner.state).first() + if not user_role: + # add to bulk_create + create_user_roles.append(UserRole( + user=user, + role=role, + establishment=establishment, + state=owner.state, + requester=requester + )) + + UserRole.objects.bulk_create(create_user_roles) + self.stdout.write( + self.style.WARNING( + f'Created roles: {len(create_user_roles)}') + ) diff --git a/apps/account/migrations/0026_auto_20191211_1134.py b/apps/account/migrations/0026_auto_20191211_1134.py new file mode 100644 index 00000000..7325c9a7 --- /dev/null +++ b/apps/account/migrations/0026_auto_20191211_1134.py @@ -0,0 +1,25 @@ +# Generated by Django 2.2.7 on 2019-12-11 11:34 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0025_auto_20191210_0623'), + ] + + operations = [ + migrations.AddField( + model_name='userrole', + name='requester', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='roles_requested', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='userrole', + name='state', + field=models.CharField(choices=[('validated', 'validated'), ('pending', 'pending'), ('cancelled', 'cancelled'), ('rejected', 'rejected')], default='pending', max_length=10, verbose_name='state'), + ), + ] diff --git a/apps/account/migrations/0027_auto_20191211_1444.py b/apps/account/migrations/0027_auto_20191211_1444.py new file mode 100644 index 00000000..ed0886e7 --- /dev/null +++ b/apps/account/migrations/0027_auto_20191211_1444.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.7 on 2019-12-11 14:44 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0067_auto_20191122_1244'), + ('account', '0026_auto_20191211_1134'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='userrole', + unique_together={('user', 'role', 'establishment', 'state')}, + ), + ] diff --git a/apps/account/models.py b/apps/account/models.py index 70a3cd69..b4744bd6 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -34,16 +34,16 @@ class Role(ProjectBaseMixin): REVIEWER_MANGER = 6 RESTAURANT_REVIEWER = 7 SALES_MAN = 8 - WINERY_REVIEWER = 9 # Establishments subtype "winery" + WINERY_REVIEWER = 9 # Establishments subtype "winery" SELLER = 10 ROLE_CHOICES = ( - (STANDARD_USER, 'Standard user'), - (COMMENTS_MODERATOR, 'Comments moderator'), - (COUNTRY_ADMIN, 'Country admin'), - (CONTENT_PAGE_MANAGER, 'Content page manager'), - (ESTABLISHMENT_MANAGER, 'Establishment manager'), - (REVIEWER_MANGER, 'Reviewer manager'), + (STANDARD_USER, _('Standard user')), + (COMMENTS_MODERATOR, _('Comments moderator')), + (COUNTRY_ADMIN, _('Country admin')), + (CONTENT_PAGE_MANAGER, _('Content page manager')), + (ESTABLISHMENT_MANAGER, _('Establishment manager')), + (REVIEWER_MANGER, _('Reviewer manager')), (RESTAURANT_REVIEWER, 'Restaurant reviewer'), (SALES_MAN, 'Sales man'), (WINERY_REVIEWER, 'Winery reviewer'), @@ -93,6 +93,14 @@ class UserQuerySet(models.QuerySet): return self.filter(oauth2_provider_refreshtoken__token=token, oauth2_provider_refreshtoken__expires__gt=timezone.now()) + def by_role(self, role): + """Filter by role.""" + return self.filter(userrole__role=role).dictinct() + + def by_roles(self, roles: list): + """Filter by roles.""" + return self.filter(userrole__role__in=roles).dictinct() + class User(AbstractUser): """Base user model.""" @@ -116,7 +124,9 @@ class User(AbstractUser): USERNAME_FIELD = 'username' REQUIRED_FIELDS = ['email'] - roles = models.ManyToManyField(Role, verbose_name=_('Roles'), through='UserRole') + roles = models.ManyToManyField( + Role, verbose_name=_('Roles'), symmetrical=False, + through_fields=('user', 'role'), through='UserRole') objects = UserManager.from_queryset(UserQuerySet)() class Meta: @@ -301,14 +311,33 @@ class User(AbstractUser): class UserRole(ProjectBaseMixin): """UserRole model.""" - user = models.ForeignKey('account.User', - verbose_name=_('User'), - on_delete=models.CASCADE) - role = models.ForeignKey(Role, verbose_name=_('Role'), on_delete=models.SET_NULL, null=True) - establishment = models.ForeignKey(Establishment, verbose_name=_('Establishment'), - on_delete=models.SET_NULL, null=True, blank=True) + VALIDATED = 'validated' + PENDING = 'pending' + CANCELLED = 'cancelled' + REJECTED = 'rejected' + + STATE_CHOICES = ( + (VALIDATED, _('validated')), + (PENDING, _('pending')), + (CANCELLED, _('cancelled')), + (REJECTED, _('rejected')) + ) + user = models.ForeignKey( + 'account.User', verbose_name=_('User'), on_delete=models.CASCADE) + role = models.ForeignKey( + Role, verbose_name=_('Role'), on_delete=models.SET_NULL, null=True) + establishment = models.ForeignKey( + Establishment, verbose_name=_('Establishment'), + on_delete=models.SET_NULL, null=True, blank=True) + + state = models.CharField( + _('state'), choices=STATE_CHOICES, max_length=10, default=PENDING) + requester = models.ForeignKey( + 'account.User', blank=True, null=True, default=None, related_name='roles_requested', + on_delete=models.SET_NULL) + class Meta: - unique_together = ['user', 'role'] + unique_together = ['user', 'role', 'establishment', 'state'] class OldRole(models.Model): diff --git a/apps/account/serializers/back.py b/apps/account/serializers/back.py index 01889411..1ee6a8b1 100644 --- a/apps/account/serializers/back.py +++ b/apps/account/serializers/back.py @@ -70,3 +70,7 @@ class UserRoleSerializer(serializers.ModelSerializer): 'user', 'establishment' ] + + +class OwnerSerializer(BackUserSerializer): + """Owner serializer.""" diff --git a/apps/account/urls/back.py b/apps/account/urls/back.py index 30f21573..5c87e525 100644 --- a/apps/account/urls/back.py +++ b/apps/account/urls/back.py @@ -10,4 +10,5 @@ urlpatterns = [ path('user-role/', views.UserRoleLstView.as_view(), name='user-role-list-create'), path('user/', views.UserLstView.as_view(), name='user-create-list'), path('user//', views.UserRUDView.as_view(), name='user-rud'), + path('owner/', views.OwnerListView.as_view(), name='owner-list'), ] diff --git a/apps/account/views/back.py b/apps/account/views/back.py index fbbc986e..08d3713d 100644 --- a/apps/account/views/back.py +++ b/apps/account/views/back.py @@ -46,3 +46,9 @@ class UserRUDView(generics.RetrieveUpdateDestroyAPIView): serializer_class = serializers.BackDetailUserSerializer permission_classes = (permissions.IsAdminUser,) lookup_field = 'id' + + +class OwnerListView(generics.ListAPIView): + serializer_class = serializers.OwnerSerializer + queryset = models.User.objects.active() + permission_classes = (permissions.IsAuthenticatedOrReadOnly, ) diff --git a/apps/transfer/models.py b/apps/transfer/models.py index d8268d6d..4f2c7ba9 100644 --- a/apps/transfer/models.py +++ b/apps/transfer/models.py @@ -1222,3 +1222,19 @@ class Footers(MigrateMixin): class Meta: managed = False db_table = 'footers' + + +class OwnershipAffs(MigrateMixin): + using = 'legacy' + + role = models.CharField(max_length=255, blank=True, null=True) + state = models.CharField(max_length=255, blank=True, null=True) + account_id = models.IntegerField(blank=True, null=True) + establishment_id = models.IntegerField(blank=True, null=True) + created_at = models.DateTimeField() + updated_at = models.DateTimeField() + requester_id = models.IntegerField(blank=True, null=True) + + class Meta: + managed = False + db_table = 'ownership_affs' \ No newline at end of file From 56501d76f28297b6bb5b722e553533457f5c710b Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 11 Dec 2019 18:20:53 +0300 Subject: [PATCH 28/81] order images --- apps/gallery/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/gallery/models.py b/apps/gallery/models.py index baed48fc..22c5b5e7 100644 --- a/apps/gallery/models.py +++ b/apps/gallery/models.py @@ -34,6 +34,7 @@ class Image(ProjectBaseMixin, SORLImageMixin, PlatformMixin): """Meta class.""" verbose_name = _('Image') verbose_name_plural = _('Images') + ordering = ['-modified'] def __str__(self): """String representation""" From 370f9cc7ce21300a3b39120e5f5e4420f4282f65 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 11 Dec 2019 18:26:29 +0300 Subject: [PATCH 29/81] remove wrong migration --- .../news/migrations/0041_auto_20191211_1424.py | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 apps/news/migrations/0041_auto_20191211_1424.py diff --git a/apps/news/migrations/0041_auto_20191211_1424.py b/apps/news/migrations/0041_auto_20191211_1424.py deleted file mode 100644 index 87d3295d..00000000 --- a/apps/news/migrations/0041_auto_20191211_1424.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.7 on 2019-12-11 14:24 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('gallery', '0006_merge_20191027_1758'), - ('news', '0040_remove_news_slug'), - ] - - operations = [ - migrations.AlterUniqueTogether( - name='newsgallery', - unique_together={('news', 'image'),}, - ), - ] From 60f6c8d811266d3153b0386ef368d214f5320e21 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 11 Dec 2019 19:15:19 +0300 Subject: [PATCH 30/81] fix migrations --- .../news/migrations/0041_auto_20191211_1528.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 apps/news/migrations/0041_auto_20191211_1528.py diff --git a/apps/news/migrations/0041_auto_20191211_1528.py b/apps/news/migrations/0041_auto_20191211_1528.py new file mode 100644 index 00000000..0093d406 --- /dev/null +++ b/apps/news/migrations/0041_auto_20191211_1528.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.7 on 2019-12-11 15:28 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('gallery', '0007_auto_20191211_1528'), + ('news', '0040_remove_news_slug'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='newsgallery', + unique_together={('news', 'image')}, + ), + ] From 9d2b7ff77cdfc5a972562370de2d97cee926fbd0 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 11 Dec 2019 19:18:04 +0300 Subject: [PATCH 31/81] add migration --- .../migrations/0007_auto_20191211_1528.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 apps/gallery/migrations/0007_auto_20191211_1528.py diff --git a/apps/gallery/migrations/0007_auto_20191211_1528.py b/apps/gallery/migrations/0007_auto_20191211_1528.py new file mode 100644 index 00000000..714ef664 --- /dev/null +++ b/apps/gallery/migrations/0007_auto_20191211_1528.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.7 on 2019-12-11 15:28 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('gallery', '0006_merge_20191027_1758'), + ] + + operations = [ + migrations.AlterModelOptions( + name='image', + options={'ordering': ['-modified'], 'verbose_name': 'Image', 'verbose_name_plural': 'Images'}, + ), + ] From 3e83bdff59f848b6df86ccd81a4d65e5d96c3362 Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 12 Dec 2019 15:34:36 +0300 Subject: [PATCH 32/81] pagination & ordering & country code --- apps/collection/views/back.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/collection/views/back.py b/apps/collection/views/back.py index a989ec56..ff924073 100644 --- a/apps/collection/views/back.py +++ b/apps/collection/views/back.py @@ -9,11 +9,17 @@ from utils.views import BindObjectMixin class CollectionViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): """ViewSet for Collection model.""" - pagination_class = None + # pagination_class = None permission_classes = (permissions.AllowAny,) - queryset = models.Collection.objects.all() serializer_class = serializers.CollectionBackOfficeSerializer + def get_queryset(self): + """Overridden method 'get_queryset'.""" + qs = models.Collection.objects.all().order_by('-created') + if self.request.country_code: + qs = qs.by_country_code(self.request.country_code) + return qs + class CollectionBackOfficeViewSet(mixins.CreateModelMixin, mixins.UpdateModelMixin, From b03cd4fe0951e6529ba2a6ed8eb089105fbc7b28 Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Thu, 12 Dec 2019 15:37:42 +0300 Subject: [PATCH 33/81] add transfer command, models and crud api. --- apps/main/management/commands/add_panels.py | 38 ++++++++++++++++ apps/main/migrations/0042_panel.py | 36 ++++++++++++++++ apps/main/models.py | 43 +++++++++++++++++++ apps/main/serializers.py | 28 +++++++++++- apps/main/urls/back.py | 6 ++- apps/main/views/back.py | 20 ++++++++- .../migrations/0021_auto_20191212_0926.py | 19 ++++++++ apps/transfer/models.py | 17 ++++++++ apps/utils/methods.py | 8 ++++ 9 files changed, 212 insertions(+), 3 deletions(-) create mode 100644 apps/main/management/commands/add_panels.py create mode 100644 apps/main/migrations/0042_panel.py create mode 100644 apps/product/migrations/0021_auto_20191212_0926.py diff --git a/apps/main/management/commands/add_panels.py b/apps/main/management/commands/add_panels.py new file mode 100644 index 00000000..85e50616 --- /dev/null +++ b/apps/main/management/commands/add_panels.py @@ -0,0 +1,38 @@ +from django.core.management.base import BaseCommand +from tqdm import tqdm + +from account.models import User +from main.models import Panel, SiteSettings +from transfer.models import Panels + + +class Command(BaseCommand): + help = '''Add panels from legacy DB.''' + + def handle(self, *args, **kwargs): + objects = [] + deleted = 0 + panels_list = Panels.objects.filter(name__isnull=False) + # remove existing panel + exist_panel = Panel.objects.filter(old_id__isnull=False) + if exist_panel.exists(): + deleted = exist_panel.count() + exist_panel.delete() + + for old_panel in tqdm(panels_list, desc='Add panels'): + site = SiteSettings.objects.filter(old_id=old_panel.site_id).first() + user = User.objects.filter(old_id=old_panel.site_id).first() + if site: + new_panel = Panel( + old_id=old_panel.id, + user=user, + site=site, + name=old_panel.name, + display=old_panel.display, + description=old_panel.description, + query=old_panel.query, + ) + objects.append(new_panel) + Panel.objects.bulk_create(objects) + self.stdout.write( + self.style.WARNING(f'Created {len(objects)}/Deleted {deleted} footer objects.')) diff --git a/apps/main/migrations/0042_panel.py b/apps/main/migrations/0042_panel.py new file mode 100644 index 00000000..3b2da2a0 --- /dev/null +++ b/apps/main/migrations/0042_panel.py @@ -0,0 +1,36 @@ +# Generated by Django 2.2.7 on 2019-12-12 12:00 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('main', '0041_auto_20191211_0631'), + ] + + operations = [ + migrations.CreateModel( + name='Panel', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')), + ('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('name', models.CharField(max_length=255, verbose_name='name')), + ('display', models.CharField(blank=True, choices=[('table', 'table'), ('table', 'mailing')], default=None, max_length=255, null=True, verbose_name='display')), + ('description', models.CharField(blank=True, default=None, max_length=255, null=True, verbose_name='description')), + ('query', models.TextField(blank=True, default=None, null=True, verbose_name='query')), + ('old_id', models.IntegerField(blank=True, default=None, null=True, verbose_name='old id')), + ('site', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='main.SiteSettings', verbose_name='site')), + ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='user')), + ], + options={ + 'verbose_name': 'panel', + 'verbose_name_plural': 'panels', + }, + ), + ] diff --git a/apps/main/models.py b/apps/main/models.py index ae4168ea..b39a6037 100644 --- a/apps/main/models.py +++ b/apps/main/models.py @@ -361,3 +361,46 @@ class Footer(ProjectBaseMixin): ) about_us = models.TextField(_('about_us')) copyright = models.TextField(_('copyright')) + + +class PanelQuerySet(models.QuerySet): + """Panels QuerySet.""" + + +class Panel(ProjectBaseMixin): + """Custom panel model with stored SQL query.""" + TABLE = 'table' + MAILING = 'table' + + DISPLAY_CHOICES = ( + (TABLE, _('table')), + (MAILING, _('mailing')) + ) + name = models.CharField(_('name'), max_length=255) + display = models.CharField( + _('display'), max_length=255, choices=DISPLAY_CHOICES, + blank=True, null=True, default=None + ) + description = models.CharField( + _('description'), max_length=255, blank=True, null=True, default=None) + query = models.TextField(_('query'), blank=True, null=True, default=None) + user = models.ForeignKey( + 'account.User', verbose_name=_('user'), null=True, + on_delete=models.SET_NULL) + site = models.ForeignKey( + 'main.SiteSettings', verbose_name=_('site'), null=True, + on_delete=models.SET_NULL) + old_id = models.IntegerField( + _('old id'), null=True, blank=True, default=None) + + objects = PanelQuerySet.as_manager() + + class Meta: + verbose_name = _('panel') + verbose_name_plural = _('panels') + + def __str__(self): + return self.name + + def execute_query(self): + pass diff --git a/apps/main/serializers.py b/apps/main/serializers.py index d586d71a..39c51845 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -5,6 +5,8 @@ from rest_framework import serializers from location.serializers import CountrySerializer from main import models from utils.serializers import ProjectModelSerializer, TranslatedField, RecursiveFieldSerializer +from account.serializers.back import BackUserSerializer +from account.models import User class FeatureSerializer(serializers.ModelSerializer): @@ -265,8 +267,32 @@ class PageTypeBaseSerializer(serializers.ModelSerializer): class ContentTypeBackSerializer(serializers.ModelSerializer): - """Serializer fro model ContentType.""" + """Serializer for model ContentType.""" class Meta: model = ContentType fields = '__all__' + + +class PanelSerializer(serializers.ModelSerializer): + """Serializer for Custom panel.""" + user_id = serializers.PrimaryKeyRelatedField( + queryset=User.objects.all(), + source='user', + write_only=True + ) + user = BackUserSerializer(read_only=True) + + class Meta: + model = models.Panel + fields = [ + 'id', + 'name', + 'display', + 'description', + 'query', + 'created', + 'modified', + 'user', + 'user_id' + ] diff --git a/apps/main/urls/back.py b/apps/main/urls/back.py index b4e196a3..26afd1a6 100644 --- a/apps/main/urls/back.py +++ b/apps/main/urls/back.py @@ -21,7 +21,11 @@ urlpatterns = [ path('footer/', views.FooterBackView.as_view(), name='footer-list-create'), path('footer//', views.FooterRUDBackView.as_view(), name='footer-rud'), path('page-types/', views.PageTypeListCreateView.as_view(), - name='page-types-list-create') + name='page-types-list-create'), + path('panels/', views.PanelsListCreateView.as_view(), name='panels'), + path('panels//', views.PanelsListCreateView.as_view(), name='panels-rud'), + # path('panels//execute/', views.PanelsView.as_view(), name='panels-execute') + ] diff --git a/apps/main/views/back.py b/apps/main/views/back.py index f6f987af..0a2b7377 100644 --- a/apps/main/views/back.py +++ b/apps/main/views/back.py @@ -4,7 +4,7 @@ from rest_framework import generics, permissions from main import serializers from main.filters import AwardFilter -from main.models import Award, Footer, PageType +from main.models import Award, Footer, PageType, Panel from main.views import SiteSettingsView, SiteListView @@ -89,3 +89,21 @@ class PageTypeListCreateView(generics.ListCreateAPIView): pagination_class = None serializer_class = serializers.PageTypeBaseSerializer queryset = PageType.objects.all() + + +class PanelsListCreateView(generics.ListCreateAPIView): + """Custom panels view.""" + permission_classes = ( + permissions.IsAdminUser, + ) + serializer_class = serializers.PanelSerializer + queryset = Panel.objects.all() + + +class PanelsRUDView(generics.RetrieveUpdateDestroyAPIView): + """Custom panels view.""" + permission_classes = ( + permissions.IsAdminUser, + ) + serializer_class = serializers.PanelSerializer + queryset = Panel.objects.all() \ No newline at end of file diff --git a/apps/product/migrations/0021_auto_20191212_0926.py b/apps/product/migrations/0021_auto_20191212_0926.py new file mode 100644 index 00000000..749ffa08 --- /dev/null +++ b/apps/product/migrations/0021_auto_20191212_0926.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.7 on 2019-12-12 09:26 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('product', '0020_merge_20191209_0911'), + ] + + operations = [ + migrations.AlterField( + model_name='product', + name='product_type', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='products', to='product.ProductType', verbose_name='Type'), + ), + ] diff --git a/apps/transfer/models.py b/apps/transfer/models.py index d8268d6d..7f640a5c 100644 --- a/apps/transfer/models.py +++ b/apps/transfer/models.py @@ -1222,3 +1222,20 @@ class Footers(MigrateMixin): class Meta: managed = False db_table = 'footers' + + +class Panels(MigrateMixin): + using = 'legacy' + + name = models.CharField(max_length=255, blank=True, null=True) + display = models.CharField(max_length=255, blank=True, null=True) + description = models.CharField(max_length=255, blank=True, null=True) + query = models.TextField(blank=True, null=True) + created_at = models.DateTimeField(blank=True, null=True) + updated_at = models.DateTimeField(blank=True, null=True) + account_id = models.IntegerField(blank=True, null=True) + site_id = models.IntegerField(blank=True, null=True) + + class Meta: + managed = False + db_table = 'panels' diff --git a/apps/utils/methods.py b/apps/utils/methods.py index dc027317..227bd1ee 100644 --- a/apps/utils/methods.py +++ b/apps/utils/methods.py @@ -3,6 +3,7 @@ import logging import random import re import string +from collections import namedtuple import requests from django.conf import settings @@ -124,3 +125,10 @@ def absolute_url_decorator(func): def get_point_from_coordinates(latitude: str, longitude: str): if latitude and longitude: return Point(x=longitude, y=latitude, srid=4326) + + +def namedtuplefetchall(cursor): + """Return all rows from a cursor as a namedtuple.""" + desc = cursor.description + nt_result = namedtuple('Result', [col[0] for col in desc]) + return [nt_result(*row) for row in cursor.fetchall()] From 755f007d25324e1cfb3c0de5f1010c8a9327fcb3 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 12 Dec 2019 16:09:43 +0300 Subject: [PATCH 34/81] 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 35/81] 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 36/81] 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 37/81] 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 38/81] 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 39/81] 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 40/81] 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 41/81] 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 42/81] 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 450ca4cf52aa105a082c1817756b65ebf31b057f Mon Sep 17 00:00:00 2001 From: dormantman Date: Fri, 13 Dec 2019 03:50:12 +0300 Subject: [PATCH 43/81] Added new field rank to collection --- apps/collection/models.py | 2 ++ apps/collection/serializers/back.py | 13 +++++++------ apps/collection/views/back.py | 8 +++++++- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/apps/collection/models.py b/apps/collection/models.py index 7acd9991..14bd99c0 100644 --- a/apps/collection/models.py +++ b/apps/collection/models.py @@ -80,6 +80,8 @@ class Collection(ProjectBaseMixin, CollectionDateMixin, verbose_name=_('Collection slug'), editable=True, null=True) old_id = models.IntegerField(null=True, blank=True) + rank = models.IntegerField(null=True, default=None) + objects = CollectionQuerySet.as_manager() class Meta: diff --git a/apps/collection/serializers/back.py b/apps/collection/serializers/back.py index 6cedb087..f41e3875 100644 --- a/apps/collection/serializers/back.py +++ b/apps/collection/serializers/back.py @@ -40,6 +40,7 @@ class CollectionBackOfficeSerializer(CollectionBaseSerializer): # 'end', 'count_related_objects', 'related_object_names', + 'rank', ] @@ -68,15 +69,15 @@ class CollectionBindObjectSerializer(serializers.Serializer): attrs['collection'] = collection if obj_type == self.ESTABLISHMENT: - establishment = Establishment.objects.filter(pk=obj_id).\ + establishment = Establishment.objects.filter(pk=obj_id). \ first() if not establishment: raise BindingObjectNotFound() - if request.method == 'POST' and collection.establishments.\ + if request.method == 'POST' and collection.establishments. \ filter(pk=establishment.pk).exists(): raise ObjectAlreadyAdded() - if request.method == 'DELETE' and not collection.\ - establishments.filter(pk=establishment.pk).\ + if request.method == 'DELETE' and not collection. \ + establishments.filter(pk=establishment.pk). \ exists(): raise RemovedBindingObjectNotFound() attrs['related_object'] = establishment @@ -84,10 +85,10 @@ class CollectionBindObjectSerializer(serializers.Serializer): product = Product.objects.filter(pk=obj_id).first() if not product: raise BindingObjectNotFound() - if request.method == 'POST' and collection.products.\ + if request.method == 'POST' and collection.products. \ filter(pk=product.pk).exists(): raise ObjectAlreadyAdded() - if request.method == 'DELETE' and not collection.products.\ + if request.method == 'DELETE' and not collection.products. \ filter(pk=product.pk).exists(): raise RemovedBindingObjectNotFound() attrs['related_object'] = product diff --git a/apps/collection/views/back.py b/apps/collection/views/back.py index 65bd6c8b..67548c83 100644 --- a/apps/collection/views/back.py +++ b/apps/collection/views/back.py @@ -1,6 +1,7 @@ from rest_framework import generics from rest_framework import permissions -from rest_framework import viewsets, mixins +from rest_framework import viewsets +from rest_framework.response import Response from collection import models from collection.serializers import back as serializers @@ -49,6 +50,11 @@ class CollectionBackOfficeViewSet(mixins.CreateModelMixin, elif obj_type == self.bind_object_serializer_class.PRODUCT: collection.products.remove(related_object) + def list(self, request, *args, **kwargs): + queryset = self.filter_queryset(self.get_queryset()).order_by('rank', '-start') + serializer = self.get_serializer(queryset, many=True) + return Response(serializer.data) + class CollectionBackOfficeView(generics.GenericAPIView): """ViewS for Collection model.""" From 5c171c644cbc99a6905cbe0840b95f8c7416dd6f Mon Sep 17 00:00:00 2001 From: dormantman Date: Fri, 13 Dec 2019 03:52:52 +0300 Subject: [PATCH 44/81] Added mixins --- apps/collection/views/back.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/collection/views/back.py b/apps/collection/views/back.py index 67548c83..fdb441c2 100644 --- a/apps/collection/views/back.py +++ b/apps/collection/views/back.py @@ -1,4 +1,5 @@ from rest_framework import generics +from rest_framework import mixins from rest_framework import permissions from rest_framework import viewsets from rest_framework.response import Response From bd6f8a47a1aa00fb99ce3e4da73c92d839209af9 Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Fri, 13 Dec 2019 11:02:37 +0300 Subject: [PATCH 45/81] add api for establishment admin --- apps/account/models.py | 8 ++++++-- apps/account/serializers/back.py | 4 ---- apps/account/urls/back.py | 1 - apps/account/views/back.py | 6 ------ apps/establishment/serializers/back.py | 11 +++++++++++ apps/establishment/urls/back.py | 2 ++ apps/establishment/views/back.py | 19 +++++++++++++++---- 7 files changed, 34 insertions(+), 17 deletions(-) diff --git a/apps/account/models.py b/apps/account/models.py index b4744bd6..b48a419c 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -95,11 +95,15 @@ class UserQuerySet(models.QuerySet): def by_role(self, role): """Filter by role.""" - return self.filter(userrole__role=role).dictinct() + return self.filter(userrole__role=role) def by_roles(self, roles: list): """Filter by roles.""" - return self.filter(userrole__role__in=roles).dictinct() + return self.filter(userrole__role__in=roles) + + def establishment_admin(self, establishment): + role = Role.objects.filter(role=Role.ESTABLISHMENT_MANAGER).first() + return self.by_role(role).filter(userrole__establishment=establishment) class User(AbstractUser): diff --git a/apps/account/serializers/back.py b/apps/account/serializers/back.py index 1ee6a8b1..01889411 100644 --- a/apps/account/serializers/back.py +++ b/apps/account/serializers/back.py @@ -70,7 +70,3 @@ class UserRoleSerializer(serializers.ModelSerializer): 'user', 'establishment' ] - - -class OwnerSerializer(BackUserSerializer): - """Owner serializer.""" diff --git a/apps/account/urls/back.py b/apps/account/urls/back.py index 5c87e525..30f21573 100644 --- a/apps/account/urls/back.py +++ b/apps/account/urls/back.py @@ -10,5 +10,4 @@ urlpatterns = [ path('user-role/', views.UserRoleLstView.as_view(), name='user-role-list-create'), path('user/', views.UserLstView.as_view(), name='user-create-list'), path('user//', views.UserRUDView.as_view(), name='user-rud'), - path('owner/', views.OwnerListView.as_view(), name='owner-list'), ] diff --git a/apps/account/views/back.py b/apps/account/views/back.py index 08d3713d..fbbc986e 100644 --- a/apps/account/views/back.py +++ b/apps/account/views/back.py @@ -46,9 +46,3 @@ class UserRUDView(generics.RetrieveUpdateDestroyAPIView): serializer_class = serializers.BackDetailUserSerializer permission_classes = (permissions.IsAdminUser,) lookup_field = 'id' - - -class OwnerListView(generics.ListAPIView): - serializer_class = serializers.OwnerSerializer - queryset = models.User.objects.active() - permission_classes = (permissions.IsAuthenticatedOrReadOnly, ) diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index a1b6cc4a..b7d429da 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -324,3 +324,14 @@ class EstablishmentNoteListCreateSerializer(EstablishmentNoteBaseSerializer): """Return establishment instance from view.""" if self.serializer_view: return self.serializer_view.get_object() + + +class EstablishmentAdminListSerializer(UserShortSerializer): + """Establishment admin serializer.""" + class Meta: + model = UserShortSerializer.Meta.model + fields = [ + 'id', + 'username', + 'email' + ] diff --git a/apps/establishment/urls/back.py b/apps/establishment/urls/back.py index ce1c7b27..b2a30917 100644 --- a/apps/establishment/urls/back.py +++ b/apps/establishment/urls/back.py @@ -28,6 +28,8 @@ urlpatterns = [ name='note-list-create'), path('slug//notes//', views.EstablishmentNoteRUDView.as_view(), name='note-rud'), + path('slug//admin/', views.EstablishmentAdminView.as_view(), + name='establishment-admin-list'), path('menus/', views.MenuListCreateView.as_view(), name='menu-list'), path('menus//', views.MenuRUDView.as_view(), name='menu-rud'), path('plates/', views.PlateListCreateView.as_view(), name='plates'), diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index 6e2c953a..91d40422 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -1,15 +1,15 @@ """Establishment app views.""" from django.http import Http404, HttpResponse from django.shortcuts import get_object_or_404 -from rest_framework import generics, permissions, status +from rest_framework import generics, permissions +from rest_framework import status +from account.models import User from establishment import filters, models, serializers +from timetable.models import Timetable from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer from utils.permissions import IsCountryAdmin, IsEstablishmentManager, IsWineryReviewer from utils.views import CreateDestroyGalleryViewMixin -from timetable.models import Timetable -from rest_framework import status -from rest_framework.response import Response class EstablishmentMixinViews: @@ -374,3 +374,14 @@ class EstablishmentPositionListView(generics.ListAPIView): permission_classes = (permissions.AllowAny, ) queryset = models.Position.objects.all() serializer_class = serializers.PositionBackSerializer + + +class EstablishmentAdminView(generics.ListAPIView): + """Establishment admin list view.""" + serializer_class = serializers.EstablishmentAdminListSerializer + permission_classes = (permissions.IsAuthenticatedOrReadOnly, ) + + def get_queryset(self): + establishment = get_object_or_404( + models.Establishment, slug=self.kwargs['slug']) + return User.objects.establishment_admin(establishment).distinct() From 8aa2cb71f36d366529b8bdc5c9be52613f6a9a89 Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Fri, 13 Dec 2019 12:08:24 +0300 Subject: [PATCH 46/81] 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 47/81] 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 48/81] 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 49/81] 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 50/81] 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 From 8f6dd7e5c1c7bd8cd2cd14b21698119f0d298149 Mon Sep 17 00:00:00 2001 From: dormantman Date: Fri, 13 Dec 2019 16:17:35 +0300 Subject: [PATCH 51/81] Added related_object_ids --- apps/collection/models.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/apps/collection/models.py b/apps/collection/models.py index 14bd99c0..d9256743 100644 --- a/apps/collection/models.py +++ b/apps/collection/models.py @@ -24,7 +24,8 @@ class CollectionNameMixin(models.Model): class CollectionDateMixin(models.Model): """CollectionDate mixin""" - start = models.DateTimeField(_('start')) + start = models.DateTimeField(blank=True, null=True, default=None, + verbose_name=_('start')) end = models.DateTimeField(blank=True, null=True, default=None, verbose_name=_('end')) @@ -110,20 +111,29 @@ class Collection(ProjectBaseMixin, CollectionDateMixin, @property def related_object_names(self) -> list: """Return related object names.""" - raw_object_names = [] - for related_object in [related_object.name for related_object in self._related_objects]: - instances = getattr(self, f'{related_object}') + raw_object_names = {} + for related_object in [(related_object.id, related_object.name) for related_object in self._related_objects]: + instances = getattr(self, f'{related_object[1]}') if instances.exists(): for instance in instances.all(): - raw_object_names.append(instance.slug if hasattr(instance, 'slug') else None) + raw_object_names[related_object[0]] = instance.slug if hasattr(instance, 'slug') else None # parse slugs - object_names = [] + related_objects = [] + object_names = set() re_pattern = r'[\w]+' - for raw_name in raw_object_names: - result = re.findall(re_pattern, raw_name) - if result: object_names.append(' '.join(result).capitalize()) - return set(object_names) + for object_id in raw_object_names: + result = re.findall(re_pattern, raw_object_names[object_id]) + if result: + name = ' '.join(result).capitalize() + if name not in object_names: + related_objects.append({ + 'id': object_id, + 'name': name + }) + object_names.add(name) + + return related_objects class GuideTypeQuerySet(models.QuerySet): From f7e4effb9f2091686fda7771590cdad92b4fd67f Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 13 Dec 2019 16:48:52 +0300 Subject: [PATCH 52/81] dynamic filters --- apps/tag/views.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/apps/tag/views.py b/apps/tag/views.py index 3bb33975..40118c2d 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -65,7 +65,7 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet): serializer_class = serializers.FiltersTagCategoryBaseSerializer def list(self, request, *args, **kwargs): - queryset = self.filter_queryset(self.get_queryset()) + queryset = self.filter_queryset(self.get_queryset().exclude(public=False)) serializer = self.get_serializer(queryset, many=True) result_list = serializer.data @@ -99,6 +99,7 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet): "index_name": "toque_number", "label_translated": "Toques", "param_name": "toque_number__in", + 'type': 'toque', "filters": [{ "id": toque_id, "index_name": "toque_%d" % toque_id, @@ -134,6 +135,7 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet): "index_name": "works_noon", "label_translated": "Open noon", "param_name": "works_noon__in", + 'type': 'weekday', "filters": [{ "id": weekday, "index_name": week_days[weekday].lower(), @@ -148,6 +150,7 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet): "index_name": "works_evening", "label_translated": "Open evening", "param_name": "works_evening__in", + 'type': 'weekday', "filters": [{ "id": weekday, "index_name": week_days[weekday].lower(), @@ -161,7 +164,7 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet): "index_name": "open_now", "label_translated": "Open now", "param_name": "open_now", - "type": True + "type": 'bool', } result_list.append(works_now) @@ -170,6 +173,7 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet): "index_name": "works_at_weekday", "label_translated": "Works at weekday", "param_name": "works_at_weekday__in", + 'type': 'weekday', "filters": [{ "id": weekday, "index_name": week_days[weekday].lower(), @@ -180,7 +184,16 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet): 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)) + result_list = self.remove_empty_filters(result_list, facets) + tag_category = list(filter(lambda x: x.get('index_name') == 'tag', result_list)) + result_list = [category for category in result_list if category.get('index_name') != 'tag'] + if len(tag_category): + tag_category = list(filter(lambda x: x.get('index_name') == 'pop', tag_category[0]['filters'])) + if len(tag_category): # we have Pop tag in our results + tag_category = tag_category[0] + tag_category['param_name'] = 'tags_id__in' + result_list.append(tag_category) + return Response(result_list) @staticmethod def mutate_request(request): From 525658f39bae148af0e4845eacbd2d822338ba68 Mon Sep 17 00:00:00 2001 From: dormantman Date: Fri, 13 Dec 2019 18:32:06 +0300 Subject: [PATCH 53/81] Reformat ordering --- apps/collection/filters.py | 8 ++++++++ apps/collection/urls/back.py | 6 +----- apps/collection/views/back.py | 18 ++---------------- 3 files changed, 11 insertions(+), 21 deletions(-) create mode 100644 apps/collection/filters.py diff --git a/apps/collection/filters.py b/apps/collection/filters.py new file mode 100644 index 00000000..a97b47d1 --- /dev/null +++ b/apps/collection/filters.py @@ -0,0 +1,8 @@ +from rest_framework import filters + + +class CollectionBackOfficeOrderingFilter(filters.BaseFilterBackend): + """ Filter ordering """ + + def filter_queryset(self, request, queryset, view): + return queryset.order_by('rank', '-start') diff --git a/apps/collection/urls/back.py b/apps/collection/urls/back.py index f97a3945..972f5285 100644 --- a/apps/collection/urls/back.py +++ b/apps/collection/urls/back.py @@ -8,9 +8,5 @@ app_name = 'collection' router = SimpleRouter() router.register(r'', views.CollectionBackOfficeViewSet) -urlpatterns = [ - path('/', views.CollectionBackOfficeView.as_view(), name='detail'), -] - -urlpatterns += router.urls +urlpatterns = router.urls diff --git a/apps/collection/views/back.py b/apps/collection/views/back.py index fdb441c2..f9b119aa 100644 --- a/apps/collection/views/back.py +++ b/apps/collection/views/back.py @@ -1,9 +1,8 @@ -from rest_framework import generics from rest_framework import mixins from rest_framework import permissions from rest_framework import viewsets -from rest_framework.response import Response +from collection import filters from collection import models from collection.serializers import back as serializers from utils.views import BindObjectMixin @@ -28,6 +27,7 @@ class CollectionBackOfficeViewSet(mixins.CreateModelMixin, permission_classes = (permissions.IsAuthenticated,) queryset = models.Collection.objects.all() + filter_backends = [filters.CollectionBackOfficeOrderingFilter] serializer_class = serializers.CollectionBackOfficeSerializer bind_object_serializer_class = serializers.CollectionBindObjectSerializer @@ -50,17 +50,3 @@ class CollectionBackOfficeViewSet(mixins.CreateModelMixin, collection.establishments.remove(related_object) elif obj_type == self.bind_object_serializer_class.PRODUCT: collection.products.remove(related_object) - - def list(self, request, *args, **kwargs): - queryset = self.filter_queryset(self.get_queryset()).order_by('rank', '-start') - serializer = self.get_serializer(queryset, many=True) - return Response(serializer.data) - - -class CollectionBackOfficeView(generics.GenericAPIView): - """ViewS for Collection model.""" - - pagination_class = None - permission_classes = (permissions.IsAuthenticated,) - queryset = models.Collection.objects.all() - serializer_class = serializers.CollectionBackOfficeSerializer From c5056224e07b487ef1f8c07192644136ed6bfd98 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 13 Dec 2019 21:19:15 +0300 Subject: [PATCH 54/81] sort filters --- apps/tag/views.py | 61 +++++++++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/apps/tag/views.py b/apps/tag/views.py index 40118c2d..2b8eb4ef 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -63,6 +63,17 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet): """ViewSet for TagCategory model.""" serializer_class = serializers.FiltersTagCategoryBaseSerializer + index_name_to_order = { + 'open_now': 9, + 'works_noon': 8, + 'works_evening': 7, + 'pop': 6, + 'category': 5, + 'toque_number': 4, + 'cuisine': 3, + 'moment': 2, + 'service': 1, + } def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset().exclude(public=False)) @@ -77,7 +88,7 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet): elif query_params.get('product_type'): params_type = query_params.get('product_type') - week_days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") + week_days = tuple(map(_, ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"))) flags = ('toque_number', 'wine_region', 'works_noon', 'works_evening', 'works_now', 'works_at_weekday') filter_flags = {flag_name: False for flag_name in flags} additional_flags = [] @@ -94,20 +105,6 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet): for flag_name in additional_flags: filter_flags[flag_name] = True - if filter_flags['toque_number']: - toques = { - "index_name": "toque_number", - "label_translated": "Toques", - "param_name": "toque_number__in", - 'type': 'toque', - "filters": [{ - "id": toque_id, - "index_name": "toque_%d" % toque_id, - "label_translated": "Toque %d" % toque_id - } for toque_id in range(6)] - } - result_list.append(toques) - if request.query_params.get('product_type') == ProductType.WINE: wine_region_id = query_params.get('wine_region_id__in') @@ -130,6 +127,24 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet): result_list.append(wine_regions) + for item in result_list: + if 'filters' in item: + item['filters'].sort(key=lambda x: x.get('label_translated')) + + if filter_flags['toque_number']: + toques = { + "index_name": "toque_number", + "label_translated": "Toques", + "param_name": "toque_number__in", + 'type': 'toque', + "filters": [{ + "id": toque_id, + "index_name": "toque_%d" % toque_id, + "label_translated": "Toque %d" % toque_id + } for toque_id in range(6)] + } + result_list.append(toques) + if filter_flags['works_noon']: works_noon = { "index_name": "works_noon", @@ -193,6 +208,7 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet): tag_category = tag_category[0] tag_category['param_name'] = 'tags_id__in' result_list.append(tag_category) + result_list.sort(key=lambda x: self.index_name_to_order.get(x.get('index_name'), 0), reverse=True) return Response(result_list) @staticmethod @@ -230,9 +246,11 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet): 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'])) + 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'])) + 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'): @@ -240,7 +258,8 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet): 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'])) + 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'])) @@ -250,9 +269,11 @@ class FiltersTagCategoryViewSet(TagCategoryViewSet): 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'])) + 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'])) + 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': From 4eff4883697a3e06dd3fc2100007b6365d703b15 Mon Sep 17 00:00:00 2001 From: dormantman Date: Fri, 13 Dec 2019 21:39:09 +0300 Subject: [PATCH 55/81] Added description field --- apps/collection/serializers/back.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/collection/serializers/back.py b/apps/collection/serializers/back.py index f41e3875..c11f9fbf 100644 --- a/apps/collection/serializers/back.py +++ b/apps/collection/serializers/back.py @@ -34,7 +34,7 @@ class CollectionBackOfficeSerializer(CollectionBaseSerializer): 'country', 'country_id', # 'block_size', - # 'description', + 'description', 'slug', # 'start', # 'end', From a120ea1f1bf181f4a478cdb5d1ada90caf95df1a Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 13 Dec 2019 22:05:16 +0300 Subject: [PATCH 56/81] fix search for products --- apps/search_indexes/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index cb5b448c..4b18bea6 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -390,7 +390,7 @@ class ProductDocumentViewSet(BaseDocumentViewSet): 'lookups': [constants.LOOKUP_QUERY_IN], }, 'country': { - 'field': 'establishment.address.city.country.code', + 'field': 'establishment.city.country.code', }, 'wine_colors_id': { 'field': 'wine_colors.id', From b847de11831fdc03dc6b3355b05d2881d76d8c08 Mon Sep 17 00:00:00 2001 From: dormantman Date: Mon, 16 Dec 2019 01:01:56 +0300 Subject: [PATCH 57/81] Added collection migration --- .../migrations/0024_auto_20191215_2156.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 apps/collection/migrations/0024_auto_20191215_2156.py diff --git a/apps/collection/migrations/0024_auto_20191215_2156.py b/apps/collection/migrations/0024_auto_20191215_2156.py new file mode 100644 index 00000000..6e11668e --- /dev/null +++ b/apps/collection/migrations/0024_auto_20191215_2156.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.7 on 2019-12-15 21:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('collection', '0023_advertorial'), + ] + + operations = [ + migrations.AddField( + model_name='collection', + name='rank', + field=models.IntegerField(default=None, null=True), + ), + migrations.AlterField( + model_name='collection', + name='start', + field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='start'), + ), + ] From def1372317f409d457fd0a69c0b3fa740a54159f Mon Sep 17 00:00:00 2001 From: dormantman Date: Mon, 16 Dec 2019 01:02:21 +0300 Subject: [PATCH 58/81] Change ordering format --- apps/collection/filters.py | 8 -------- apps/collection/views/back.py | 8 ++++++-- 2 files changed, 6 insertions(+), 10 deletions(-) delete mode 100644 apps/collection/filters.py diff --git a/apps/collection/filters.py b/apps/collection/filters.py deleted file mode 100644 index a97b47d1..00000000 --- a/apps/collection/filters.py +++ /dev/null @@ -1,8 +0,0 @@ -from rest_framework import filters - - -class CollectionBackOfficeOrderingFilter(filters.BaseFilterBackend): - """ Filter ordering """ - - def filter_queryset(self, request, queryset, view): - return queryset.order_by('rank', '-start') diff --git a/apps/collection/views/back.py b/apps/collection/views/back.py index f9b119aa..787570cc 100644 --- a/apps/collection/views/back.py +++ b/apps/collection/views/back.py @@ -1,8 +1,9 @@ +from django_filters.rest_framework import DjangoFilterBackend from rest_framework import mixins from rest_framework import permissions from rest_framework import viewsets +from rest_framework.filters import OrderingFilter -from collection import filters from collection import models from collection.serializers import back as serializers from utils.views import BindObjectMixin @@ -27,10 +28,13 @@ class CollectionBackOfficeViewSet(mixins.CreateModelMixin, permission_classes = (permissions.IsAuthenticated,) queryset = models.Collection.objects.all() - filter_backends = [filters.CollectionBackOfficeOrderingFilter] + filter_backends = [DjangoFilterBackend, OrderingFilter] 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') From a9f5a5b98f9338ac298e30e9b3ecf1784cff510f Mon Sep 17 00:00:00 2001 From: dormantman Date: Mon, 16 Dec 2019 13:34:54 +0300 Subject: [PATCH 59/81] Deleted description field --- apps/collection/migrations/0024_auto_20191215_2156.py | 4 ++++ apps/collection/models.py | 6 +++--- apps/collection/serializers/back.py | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/collection/migrations/0024_auto_20191215_2156.py b/apps/collection/migrations/0024_auto_20191215_2156.py index 6e11668e..1b494867 100644 --- a/apps/collection/migrations/0024_auto_20191215_2156.py +++ b/apps/collection/migrations/0024_auto_20191215_2156.py @@ -20,4 +20,8 @@ class Migration(migrations.Migration): name='start', field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='start'), ), + migrations.RemoveField( + model_name='collection', + name='description', + ) ] diff --git a/apps/collection/models.py b/apps/collection/models.py index d9256743..be445394 100644 --- a/apps/collection/models.py +++ b/apps/collection/models.py @@ -74,9 +74,9 @@ class Collection(ProjectBaseMixin, CollectionDateMixin, block_size = JSONField( _('collection block properties'), null=True, blank=True, default=None, help_text='{"width": "250px", "height":"250px"}') - description = TJSONField( - _('description'), null=True, blank=True, - default=None, help_text='{"en-GB":"some text"}') + # description = TJSONField( + # _('description'), null=True, blank=True, + # default=None, help_text='{"en-GB":"some text"}') slug = models.SlugField(max_length=50, unique=True, verbose_name=_('Collection slug'), editable=True, null=True) old_id = models.IntegerField(null=True, blank=True) diff --git a/apps/collection/serializers/back.py b/apps/collection/serializers/back.py index c11f9fbf..f41e3875 100644 --- a/apps/collection/serializers/back.py +++ b/apps/collection/serializers/back.py @@ -34,7 +34,7 @@ class CollectionBackOfficeSerializer(CollectionBaseSerializer): 'country', 'country_id', # 'block_size', - 'description', + # 'description', 'slug', # 'start', # 'end', From c2a520b518655b6f6a3ce4443e717dd5979464fa Mon Sep 17 00:00:00 2001 From: dormantman Date: Mon, 16 Dec 2019 13:59:04 +0300 Subject: [PATCH 60/81] Reformat code style --- apps/collection/models.py | 7 ++++--- apps/collection/serializers/back.py | 3 ++- apps/collection/views/back.py | 4 +--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/collection/models.py b/apps/collection/models.py index be445394..099491ac 100644 --- a/apps/collection/models.py +++ b/apps/collection/models.py @@ -6,9 +6,10 @@ from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.utils.translation import gettext_lazy as _ -from utils.models import ProjectBaseMixin, URLImageMixin -from utils.models import TJSONField -from utils.models import TranslatedFieldsMixin +from utils.models import ( + ProjectBaseMixin, TJSONField, TranslatedFieldsMixin, + URLImageMixin, +) from utils.querysets import RelatedObjectsCountMixin diff --git a/apps/collection/serializers/back.py b/apps/collection/serializers/back.py index f41e3875..bdc97e91 100644 --- a/apps/collection/serializers/back.py +++ b/apps/collection/serializers/back.py @@ -7,7 +7,8 @@ from location.models import Country from location.serializers import CountrySimpleSerializer from product.models import Product from utils.exceptions import ( - BindingObjectNotFound, RemovedBindingObjectNotFound, ObjectAlreadyAdded + BindingObjectNotFound, ObjectAlreadyAdded, + RemovedBindingObjectNotFound, ) diff --git a/apps/collection/views/back.py b/apps/collection/views/back.py index 787570cc..0b13034b 100644 --- a/apps/collection/views/back.py +++ b/apps/collection/views/back.py @@ -1,7 +1,5 @@ from django_filters.rest_framework import DjangoFilterBackend -from rest_framework import mixins -from rest_framework import permissions -from rest_framework import viewsets +from rest_framework import mixins, permissions, viewsets from rest_framework.filters import OrderingFilter from collection import models From 33ac0ad3e6df423098efbe8dc9d4c50fa3f2d14d Mon Sep 17 00:00:00 2001 From: dormantman Date: Mon, 16 Dec 2019 14:03:44 +0300 Subject: [PATCH 61/81] Delete useless import --- apps/collection/urls/back.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/collection/urls/back.py b/apps/collection/urls/back.py index 972f5285..6a6dbd54 100644 --- a/apps/collection/urls/back.py +++ b/apps/collection/urls/back.py @@ -1,5 +1,4 @@ """Collection common urlpaths.""" -from django.urls import path from rest_framework.routers import SimpleRouter from collection.views import back as views @@ -9,4 +8,3 @@ router = SimpleRouter() router.register(r'', views.CollectionBackOfficeViewSet) urlpatterns = router.urls - From 586f6e373bc7563301e6ad177e21cfcc621f4617 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 16 Dec 2019 15:30:05 +0300 Subject: [PATCH 62/81] Anatoly, dont erase working code during refactoring pls --- apps/establishment/models.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index ab7f14fa..5ff6f921 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -541,9 +541,15 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin, def visible_tags(self): return super().visible_tags \ .exclude(category__index_name__in=['guide', 'collection', 'purchased_item', - 'business_tag', 'business_tags_de', 'tag']) + 'business_tag', 'business_tags_de']) \ + .exclude(value__in=['rss', 'rss_selection']) # todo: recalculate toque_number + @property + def visible_tags_detail(self): + """Removes some tags from detail Establishment representation""" + return self.visible_tags.exclude(category__index_name__in=['tag']) + def recalculate_toque_number(self): toque_number = 0 if self.address and self.public_mark: From fb05243281e36565dd01781e23fda398457f9fec Mon Sep 17 00:00:00 2001 From: dormantman Date: Mon, 16 Dec 2019 16:55:11 +0300 Subject: [PATCH 63/81] Change related_object field --- apps/collection/serializers/back.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/collection/serializers/back.py b/apps/collection/serializers/back.py index bdc97e91..d8472af1 100644 --- a/apps/collection/serializers/back.py +++ b/apps/collection/serializers/back.py @@ -21,7 +21,7 @@ class CollectionBackOfficeSerializer(CollectionBaseSerializer): source='get_collection_type_display', read_only=True) country = CountrySimpleSerializer(read_only=True) count_related_objects = serializers.IntegerField(read_only=True) - related_object_names = serializers.ListField(read_only=True) + related_object_names = serializers.JSONField(read_only=True) class Meta: model = models.Collection @@ -35,7 +35,7 @@ class CollectionBackOfficeSerializer(CollectionBaseSerializer): 'country', 'country_id', # 'block_size', - # 'description', + 'description', 'slug', # 'start', # 'end', From 6c483dd22821c90bc1c3c5e39116a239407ebf95 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 16 Dec 2019 17:02:20 +0300 Subject: [PATCH 64/81] recipe transfer --- apps/recipe/migrations/0002_recipe_old_id.py | 18 +++ apps/recipe/migrations/0003_recipe_slug.py | 18 +++ apps/recipe/models.py | 19 ++- apps/recipe/transfer_data.py | 23 +++- apps/transfer/management/commands/transfer.py | 2 +- apps/transfer/serializers/recipe.py | 114 +++++++++++------- 6 files changed, 136 insertions(+), 58 deletions(-) create mode 100644 apps/recipe/migrations/0002_recipe_old_id.py create mode 100644 apps/recipe/migrations/0003_recipe_slug.py diff --git a/apps/recipe/migrations/0002_recipe_old_id.py b/apps/recipe/migrations/0002_recipe_old_id.py new file mode 100644 index 00000000..d5313561 --- /dev/null +++ b/apps/recipe/migrations/0002_recipe_old_id.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.7 on 2019-12-16 06:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('recipe', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='recipe', + name='old_id', + field=models.PositiveIntegerField(blank=True, default=None, null=True, verbose_name='old id'), + ), + ] diff --git a/apps/recipe/migrations/0003_recipe_slug.py b/apps/recipe/migrations/0003_recipe_slug.py new file mode 100644 index 00000000..11a5a102 --- /dev/null +++ b/apps/recipe/migrations/0003_recipe_slug.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.7 on 2019-12-16 13:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('recipe', '0002_recipe_old_id'), + ] + + operations = [ + migrations.AddField( + model_name='recipe', + name='slug', + field=models.SlugField(max_length=255, null=True, unique=True, verbose_name='Slug'), + ), + ] diff --git a/apps/recipe/models.py b/apps/recipe/models.py index 349fed7b..6eaa2155 100644 --- a/apps/recipe/models.py +++ b/apps/recipe/models.py @@ -43,22 +43,19 @@ class Recipe(TranslatedFieldsMixin, ImageMixin, BaseAttributes): STR_FIELD_NAME = 'title' - title = TJSONField(blank=True, null=True, default=None, verbose_name=_('Title'), - help_text='{"en-GB": "some text"}') + title = TJSONField(blank=True, null=True, default=None, verbose_name=_('Title'), help_text='{"en-GB": "some text"}') subtitle = TJSONField(blank=True, null=True, default=None, verbose_name=_('Subtitle'), help_text='{"en-GB": "some text"}') description = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'), help_text='{"en-GB": "some text"}') - state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES, - verbose_name=_('State')) - author = models.CharField(max_length=255, blank=True, null=True, default=None, - verbose_name=_('Author')) - published_at = models.DateTimeField(verbose_name=_('Published at'), - blank=True, default=None, null=True, + state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES, verbose_name=_('State')) + author = models.CharField(max_length=255, blank=True, null=True, default=None, verbose_name=_('Author')) + published_at = models.DateTimeField(verbose_name=_('Published at'), blank=True, default=None, null=True, help_text=_('Published at')) - published_scheduled_at = models.DateTimeField(verbose_name=_('Published scheduled at'), - blank=True, default=None, null=True, - help_text=_('Published scheduled at')) + published_scheduled_at = models.DateTimeField(verbose_name=_('Published scheduled at'), blank=True, default=None, + null=True, help_text=_('Published scheduled at')) + old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None) + slug = models.SlugField(unique=True, max_length=255, null=True, verbose_name=_('Slug')) objects = RecipeQuerySet.as_manager() diff --git a/apps/recipe/transfer_data.py b/apps/recipe/transfer_data.py index 4a2e97d6..22b44baa 100644 --- a/apps/recipe/transfer_data.py +++ b/apps/recipe/transfer_data.py @@ -1,19 +1,32 @@ -from django.db.models import Value, IntegerField, F from pprint import pprint + from transfer.models import PageTexts from transfer.serializers.recipe import RecipeSerializer def transfer_recipe(): - queryset = PageTexts.objects.filter(page__type="Recipe") + queryset = PageTexts.objects.filter( + page__type='Recipe', + ).values( + 'id', + 'title', + 'summary', + 'body', + 'locale', + 'state', + 'slug', + 'created_at', + 'page__attachment_suffix_url', + 'page__account_id', + ) - serialized_data = RecipeSerializer(data=list(queryset.values()), many=True) + serialized_data = RecipeSerializer(data=list(queryset), many=True) if serialized_data.is_valid(): serialized_data.save() else: - pprint(f"News serializer errors: {serialized_data.errors}") + pprint(f'Recipe serializer errors: {serialized_data.errors}') data_types = { - "recipe": [transfer_recipe] + 'recipe': [transfer_recipe] } diff --git a/apps/transfer/management/commands/transfer.py b/apps/transfer/management/commands/transfer.py index d59bacfc..b4a42349 100644 --- a/apps/transfer/management/commands/transfer.py +++ b/apps/transfer/management/commands/transfer.py @@ -13,7 +13,7 @@ class Command(BaseCommand): 'news', # перенос новостей (после №2) 'account', # №1 - перенос пользователей 'subscriber', - 'recipe', + 'recipe', # №2 - рецепты 'partner', 'establishment', # №3 - перенос заведений 'gallery', diff --git a/apps/transfer/serializers/recipe.py b/apps/transfer/serializers/recipe.py index 11698ba1..ad2d34cb 100644 --- a/apps/transfer/serializers/recipe.py +++ b/apps/transfer/serializers/recipe.py @@ -1,55 +1,87 @@ from rest_framework import serializers + +from account.models import User from recipe.models import Recipe from utils.legacy_parser import parse_legacy_news_content -class RecipeSerializer(serializers.ModelSerializer): - locale = serializers.CharField() +class RecipeSerializer(serializers.Serializer): + id = serializers.IntegerField() + title = serializers.CharField(allow_null=True) + summary = serializers.CharField(allow_null=True, allow_blank=True) body = serializers.CharField(allow_null=True) - title = serializers.CharField() - state = serializers.CharField() - created_at = serializers.DateTimeField(source="published_at", format='%m-%d-%Y %H:%M:%S') - - class Meta: - model = Recipe - fields = ( - "body", - "title", - "state", - "created_at", - 'locale', - ) + locale = serializers.CharField(allow_null=True) + state = serializers.CharField(allow_null=True) + slug = serializers.CharField(allow_null=True) + created_at = serializers.DateTimeField(format='%m-%d-%Y %H:%M:%S') + page__attachment_suffix_url = serializers.CharField(allow_null=True) + page__account_id = serializers.IntegerField(allow_null=True) def validate(self, data): - data["state"] = self.get_state(data) - data["title"] = self.get_title(data) - data["description"] = self.get_description(data) - data.pop("body") - data.pop("locale") + data.update({ + 'old_id': data.pop('id'), + 'title': self.get_title(data), + 'subtitle': self.get_subtitle(data), + 'description': self.get_description(data), + 'state': self.get_state(data), + 'created': data.pop('created_at'), + 'image': self.get_image(data), + 'created_by': self.get_account(data), + 'modified_by': self.get_account(data), + }) + + data.pop('page__account_id') + data.pop('page__attachment_suffix_url') + data.pop('summary') + data.pop('body') + data.pop('locale') return data def create(self, validated_data): - return Recipe.objects.create(**validated_data) + obj, _ = Recipe.objects.update_or_create( + old_id=validated_data['old_id'], + defaults=validated_data, + ) + return obj - def get_state(self, obj): - if obj["state"] == "published": - return Recipe.PUBLISHED - elif obj["state"] == "hidden": - return Recipe.HIDDEN - elif obj["state"] == "published_exclusive": - return Recipe.PUBLISHED_EXCLUSIVE - else: - return Recipe.WAITING + @staticmethod + def get_title(data): + if data.get('title') and data.get('locale'): + return {data['locale']: data['title']} + return None - def get_title(self, obj): - # tit = obj.get("title") - # return {"en-GB": tit} - return {obj['locale']: obj['title']} + @staticmethod + def get_subtitle(data): + if data.get('summary') and data.get('locale'): + return {data['locale']: data['summary']} + return None - def get_description(self, obj): - # desc = obj.get("body") - # return {"en-GB": desc} - content = None - if obj['body']: - content = parse_legacy_news_content(obj['body']) - return {obj['locale']: content} + @staticmethod + def get_description(data): + if data.get('body') and data.get('locale'): + content = parse_legacy_news_content(data['body']) + return {data['locale']: content} + return None + + @staticmethod + def get_state(data): + value = data.get('state') + states = { + 'published': Recipe.PUBLISHED, + 'hidden': Recipe.HIDDEN, + 'published_exclusive': Recipe.PUBLISHED_EXCLUSIVE + } + return states.get(value, Recipe.WAITING) + + @staticmethod + def get_image(data): + values = (None, 'default/missing.png') + if data.get('page__attachment_suffix_url') not in values: + return data['page__attachment_suffix_url'] + return None + + @staticmethod + def get_account(data): + if data.get('page__account_id'): + return User.objects.filter(old_id=data['page__account_id']).first() + return None From cf001b3aabbb215a833aef67ea5a691a1562fd35 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 16 Dec 2019 17:02:45 +0300 Subject: [PATCH 65/81] return news object after binding --- apps/news/views.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/news/views.py b/apps/news/views.py index c8acb3ac..732228b4 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -1,7 +1,7 @@ """News app views.""" from django.conf import settings from django.shortcuts import get_object_or_404 -from rest_framework import generics, permissions +from rest_framework import generics, permissions, response from news import filters, models, serializers from rating.tasks import add_rating @@ -106,6 +106,14 @@ class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView, CreateDestroyGalleryViewMixin): """Resource for a create gallery for news for back-office users.""" serializer_class = serializers.NewsBackOfficeGallerySerializer + permission_classes = (permissions.AllowAny, ) + + def create(self, request, *args, **kwargs): + _ = super().create(request, *args, **kwargs) + news_qs = self.filter_queryset(self.get_queryset()) + return response.Response( + data=serializers.NewsDetailSerializer(get_object_or_404(news_qs, pk=kwargs.get('pk'))).data + ) def get_object(self): """ From f4fff47d68565a935da4f3165037874e49ef3379 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 16 Dec 2019 17:04:52 +0300 Subject: [PATCH 66/81] return news object after binding #2 --- apps/news/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/news/views.py b/apps/news/views.py index 732228b4..f59ad2d6 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -106,7 +106,6 @@ class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView, CreateDestroyGalleryViewMixin): """Resource for a create gallery for news for back-office users.""" serializer_class = serializers.NewsBackOfficeGallerySerializer - permission_classes = (permissions.AllowAny, ) def create(self, request, *args, **kwargs): _ = super().create(request, *args, **kwargs) From b25f387c9be6af1dbfbdb3e09cf1dc5db3d682fa Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 16 Dec 2019 17:29:01 +0300 Subject: [PATCH 67/81] news search --- apps/search_indexes/views.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index 4b18bea6..f088bf8b 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -61,11 +61,18 @@ class NewsDocumentViewSet(BaseDocumentViewSet): ) filter_fields = { + 'tags_id': { + 'field': 'tags.id', + 'lookups': [ + constants.LOOKUP_QUERY_IN, + constants.LOOKUP_QUERY_EXCLUDE, + ], + }, 'tag': { 'field': 'tags.id', 'lookups': [ constants.LOOKUP_QUERY_IN, - constants.LOOKUP_QUERY_EXCLUDE + constants.LOOKUP_QUERY_EXCLUDE, ] }, 'tag_value': { From e68142bf2eb63549c08288b900fd01cc1bb3a5f2 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 16 Dec 2019 18:43:15 +0300 Subject: [PATCH 68/81] BO news list by country --- apps/news/views.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/news/views.py b/apps/news/views.py index f59ad2d6..fe9a75e7 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -99,7 +99,10 @@ class NewsBackOfficeLCView(NewsBackOfficeMixinView, def get_queryset(self): """Override get_queryset method.""" - return super().get_queryset().with_extended_related() + qs = super().get_queryset().with_extended_related() + if self.request.country_code: + qs = qs.by_country_code(self.request.country_code) + return qs class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView, From b09d3af35f20ee06593d5497b49dd53630d79130 Mon Sep 17 00:00:00 2001 From: dormantman Date: Mon, 16 Dec 2019 22:00:42 +0300 Subject: [PATCH 69/81] Hardcode collection description migrate --- .../migrations/0025_collection_description.py | 171 ++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 apps/collection/migrations/0025_collection_description.py diff --git a/apps/collection/migrations/0025_collection_description.py b/apps/collection/migrations/0025_collection_description.py new file mode 100644 index 00000000..9aba311f --- /dev/null +++ b/apps/collection/migrations/0025_collection_description.py @@ -0,0 +1,171 @@ +# Generated by Django 2.2.7 on 2019-12-16 17:25 + +from django.db import migrations + +import utils.models + +DESCRIPTION_DATA = { + 1123: '{"en-GB": "Test description"}', + 1100: '{"en-GB": "東京 寿司 2019"}', + 1101: '{"en-GB": "東京 中国料理 2019"}', + 1102: '{"en-GB": "北海道セレクション 2019"}', + 1107: '{"en-GB": "Arad"}', + 1103: '{"en-GB": "北陸セレクション 2019"}', + 1104: '{"en-GB": "東海セレクション 2019"}', + 1105: '{"en-GB": "京都・神戸セレクション 2019"}', + 1106: '{"en-GB": "Noaptea Muzeelor"}', + 1108: '{"en-GB": "Bacău"}', + 1109: '{"en-GB": "Brăila"}', + 1110: '{"en-GB": "Constanța și litoral"}', + 1111: '{"en-GB": "Craiova"}', + 1112: '{"en-GB": "Galați"}', + 1113: '{"en-GB": "Iași"}', + 1114: '{"en-GB": "Pitești"}', + 1115: '{"en-GB": "Ploiești"}', + 1116: '{"en-GB": "17+ Club"}', + 1117: '{"en-GB": "Partener G&M: foodpanda"}', + 1118: '{"en-GB": "Partener G&M: Alpha Gold"}', + 1119: '{"en-GB": "Partener G&M: Bit Soft"}', + 1120: '{"en-GB": "Premiile Gault&Millau 2019"}', + 992: '{"en-GB": "Restaurants avec terrasse"}', + 994: '{"en-GB": "Restaurants ouverts le midi"}', + 986: '{"en-GB": "Les restaurants des lauréats"}', + 988: '{"en-GB": "Gagas de Pizza"}', + 993: '{"en-GB": "Restaurants apportez votre vin"}', + 997: '{"en-GB": "Les restaurants d\'hôtels"}', + 1033: '{"en-GB": "Restos: ça vient d\'ouvrir"}', + 998: '{"en-GB": "Restaurants de femmes chefs"}', + 990: '{"en-GB": "Ma claque du burger ?"}', + 989: '{"en-GB": "Ouverts avec Gault&Millau"}', + 1121: '{"en-GB": "POP"}', + 991: '{"en-GB": "Ma langue au matcha"}', + 987: '{"en-GB": "Marmites cosmopolites"}', + 985: '{"en-GB": "Nos tartares préférés"}', + 996: '{"en-GB": "Gastro-Bistro"}', + 1036: '{"en-GB": "Les belles viandes dans l\'assiette"}', + 995: '{"en-GB": "Cure de terrasses"}', + 1005: '{"en-GB": "Carte de vins remarquable"}', + 1009: '{"en-GB": "JRE"}', + 1007: '{"en-GB": "Embraisez qui vous voudrez"}', + 1022: '{"en-GB": "Les coffee shop"}', + 1000: '{"en-GB": "Great Views"}', + 1016: '{"en-GB": "Les terrasses de l\'été"}', + 1018: '{"en-GB": "POP"}', + 1004: '{"en-GB": "Prix-Plaisir"}', + 999: '{"en-GB": "Prix spéciaux fin de soirée"}', + 1001: '{"en-GB": "Exceptional Wine Lists"}', + 1012: '{"en-GB": "NorthSeaChefs"}', + 1011: '{"en-GB": "Ramen japonais et nouilles asiatiques"}', + 1008: '{"en-GB": "Pickles et cuisines acides"}', + 1002: '"{""en-GB"": ""Poke bowl', + 1013: '{"en-GB": "Coquillages et crustacés"}', + 1014: '{"en-GB": "Génération W"}', + 1015: '"{""en-GB"": ""Pita', + 1017: '{"en-GB": "Belles cartes des vins"}', + 1021: '{"en-GB": "Smiley FAVV/AFSCA"}', + 1010: '{"en-GB": "Mastercooks"}', + 1019: '{"en-GB": "Où manger les pieds dans l\'eau ?"}', + 1020: '{"en-GB": "Où siroter une petite mousse ? "}', + 1006: '{"en-GB": "Vegetarian Menus"}', + 1003: '{"en-GB": "BYO"}', + 1025: '{"en-GB": "Traditional Georgian"}', + 1024: '{"en-GB": "Les Grands de demain 2018"}', + 1043: '{"en-GB": "Top Gastro"}', + 1026: '{"en-GB": "Top Luxury"}', + 1028: '{"en-GB": "Gastronomic"}', + 1031: '{"en-GB": "Les dotés qui viennent d\'ouvrir"}', + 1029: '{"en-GB": "Wine Restaurant"}', + 1027: '{"en-GB": "Iconic Places"}', + 1032: '{"en-GB": "Les 5 dotés à 3 toques et plus"}', + 1047: '{"en-GB": "Our Mother\'s Day picks"}', + 1038: '{"en-GB": "Gastronomique"}', + 1023: '{"en-GB": "Jeunes Talents 2018"}', + 1040: '{"en-GB": "Our selection of pastry shops"}', + 1035: '{"en-GB": "Romantic Restaurants"}', + 1045: '{"en-GB": "Traditional cuisine"}', + 1039: '{"en-GB": "Les plus grands gastros français"}', + 1030: '{"en-GB": "Dotation G&M"}', + 1044: '{"en-GB": "Ca va ouvrir"}', + 1041: '{"en-GB": "Delicious lunch in Split"}', + 1034: '{"en-GB": "POPセレクション 2018"}', + 1046: '{"en-GB": "Romantic restaurants"}', + 1042: '{"en-GB": "Top Gastro"}', + 1037: '{"en-GB": "アワード受賞レストランに行きたい!"}', + 1048: '{"en-GB": "Top Long Lunch choices"}', + 1049: '{"en-GB": "Creative Degustation Menus"}', + 1050: '{"en-GB": "2018年 高評価レストラン"}', + 1069: '{"en-GB": "Discovery Cheque"}', + 1054: '{"en-GB": "restaurant vegan"}', + 1070: '{"en-GB": "Les Grands de Demain 2019"}', + 1063: '{"en-GB": "Kosher"}', + 1058: '{"en-GB": "Trophees 2018"}', + 1064: '{"en-GB": "Asian Restaurants"}', + 1061: '{"en-GB": "Business"}', + 1065: '{"en-GB": "Italian Restaurants"}', + 1051: '{"en-GB": "PoP"}', + 1057: '{"en-GB": "Les Patrons Cuisiniers"}', + 1067: '{"en-GB": "POP"}', + 1053: '{"en-GB": "Cocktail Bar"}', + 1062: '{"en-GB": "Romantic Restaurants"}', + 1072: '{"en-GB": "Top Gastro Antilles"}', + 1052: '{"en-GB": "Top Gastro"}', + 1078: '{"en-GB": "POPセレクション2019"}', + 1081: '{"en-GB": "Oradea"}', + 1083: '{"en-GB": "Sibiu"}', + 1085: '{"en-GB": "Destinații speciale"}', + 1086: '{"en-GB": "Premiile Gault&Millau 2018"}', + 1087: '{"en-GB": "Partener G&M: Nespresso"}', + 1088: '{"en-GB": "アワード受賞レストランに行きたい!2019"}', + 1089: '{"en-GB": "高評価レストラン 2019"}', + 1090: '{"en-GB": "Gault&Millau Croatia Trophies 2019"}', + 1091: '{"en-GB": "The best restaurants 2019"}', + 1092: '{"en-GB": "\"Yummy\" lunch in Rijeka"}', + 1093: '{"en-GB": "POP places in Zagreb"}', + 1094: '{"en-GB": "東京 フランス料理 2019"}', + 1095: '{"en-GB": "東京 日本料理 2019"}', + 1096: '{"en-GB": "東京 イタリア料理 2019"}', + 1097: '{"en-GB": "東京 スペイン料理 2019"}', + 1098: '{"en-GB": "東京 イノベーティブ 2019"}', + 1099: '{"en-GB": "Pește și fructe de mare"}', + 1056: '{"en-GB": "Alliance Gastronomique"}', + 1060: '{"en-GB": "Meat Restaurants"}', + 1068: '{"en-GB": "The Young Masters"}', + 1055: '{"en-GB": "JRE"}', + 1071: '{"en-GB": "Les Jeunes Talents 2019"}', + 1059: '{"en-GB": "Fish Restaurants"}', + 1066: '{"en-GB": "Best Cocktail Bars"}', + 1077: '{"en-GB": "POP"}', + 1074: '{"en-GB": "Vue Exceptionnelle"}', + 1073: '{"en-GB": "Peyi"}', + 1075: '{"en-GB": "Top Bar"}', + 1076: '{"en-GB": "Euro-Toques"}', + 1080: '{"en-GB": "Cluj-Napoca"}', + 1079: '{"en-GB": "București"}', + 1084: '{"en-GB": "Timișoara"}', + 1082: '{"en-GB": "Brașov"}', + 1128: '{"en-GB": "asdad2"}', +} + + +def adding_description(apps, schema_editor): + Collection = apps.get_model("collection", "Collection") + for collection in Collection.objects.all(): + collection.description = DESCRIPTION_DATA.get(collection.id) + collection.save() + + +class Migration(migrations.Migration): + dependencies = [ + ('collection', '0024_auto_20191215_2156'), + ] + + operations = [ + migrations.AddField( + model_name='collection', + name='description', + field=utils.models.TJSONField(blank=True, default=None, + help_text='{"en-GB":"some text"}', null=True, + verbose_name='description'), + ), + migrations.RunPython(adding_description), + ] From 10ca6d8932012a4bf6c86bc75c66957446cbd236 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 16 Dec 2019 22:13:25 +0300 Subject: [PATCH 70/81] remove hardcode from migration --- .../migrations/0025_collection_description.py | 151 ------------------ 1 file changed, 151 deletions(-) diff --git a/apps/collection/migrations/0025_collection_description.py b/apps/collection/migrations/0025_collection_description.py index 9aba311f..d7638db3 100644 --- a/apps/collection/migrations/0025_collection_description.py +++ b/apps/collection/migrations/0025_collection_description.py @@ -4,156 +4,6 @@ from django.db import migrations import utils.models -DESCRIPTION_DATA = { - 1123: '{"en-GB": "Test description"}', - 1100: '{"en-GB": "東京 寿司 2019"}', - 1101: '{"en-GB": "東京 中国料理 2019"}', - 1102: '{"en-GB": "北海道セレクション 2019"}', - 1107: '{"en-GB": "Arad"}', - 1103: '{"en-GB": "北陸セレクション 2019"}', - 1104: '{"en-GB": "東海セレクション 2019"}', - 1105: '{"en-GB": "京都・神戸セレクション 2019"}', - 1106: '{"en-GB": "Noaptea Muzeelor"}', - 1108: '{"en-GB": "Bacău"}', - 1109: '{"en-GB": "Brăila"}', - 1110: '{"en-GB": "Constanța și litoral"}', - 1111: '{"en-GB": "Craiova"}', - 1112: '{"en-GB": "Galați"}', - 1113: '{"en-GB": "Iași"}', - 1114: '{"en-GB": "Pitești"}', - 1115: '{"en-GB": "Ploiești"}', - 1116: '{"en-GB": "17+ Club"}', - 1117: '{"en-GB": "Partener G&M: foodpanda"}', - 1118: '{"en-GB": "Partener G&M: Alpha Gold"}', - 1119: '{"en-GB": "Partener G&M: Bit Soft"}', - 1120: '{"en-GB": "Premiile Gault&Millau 2019"}', - 992: '{"en-GB": "Restaurants avec terrasse"}', - 994: '{"en-GB": "Restaurants ouverts le midi"}', - 986: '{"en-GB": "Les restaurants des lauréats"}', - 988: '{"en-GB": "Gagas de Pizza"}', - 993: '{"en-GB": "Restaurants apportez votre vin"}', - 997: '{"en-GB": "Les restaurants d\'hôtels"}', - 1033: '{"en-GB": "Restos: ça vient d\'ouvrir"}', - 998: '{"en-GB": "Restaurants de femmes chefs"}', - 990: '{"en-GB": "Ma claque du burger ?"}', - 989: '{"en-GB": "Ouverts avec Gault&Millau"}', - 1121: '{"en-GB": "POP"}', - 991: '{"en-GB": "Ma langue au matcha"}', - 987: '{"en-GB": "Marmites cosmopolites"}', - 985: '{"en-GB": "Nos tartares préférés"}', - 996: '{"en-GB": "Gastro-Bistro"}', - 1036: '{"en-GB": "Les belles viandes dans l\'assiette"}', - 995: '{"en-GB": "Cure de terrasses"}', - 1005: '{"en-GB": "Carte de vins remarquable"}', - 1009: '{"en-GB": "JRE"}', - 1007: '{"en-GB": "Embraisez qui vous voudrez"}', - 1022: '{"en-GB": "Les coffee shop"}', - 1000: '{"en-GB": "Great Views"}', - 1016: '{"en-GB": "Les terrasses de l\'été"}', - 1018: '{"en-GB": "POP"}', - 1004: '{"en-GB": "Prix-Plaisir"}', - 999: '{"en-GB": "Prix spéciaux fin de soirée"}', - 1001: '{"en-GB": "Exceptional Wine Lists"}', - 1012: '{"en-GB": "NorthSeaChefs"}', - 1011: '{"en-GB": "Ramen japonais et nouilles asiatiques"}', - 1008: '{"en-GB": "Pickles et cuisines acides"}', - 1002: '"{""en-GB"": ""Poke bowl', - 1013: '{"en-GB": "Coquillages et crustacés"}', - 1014: '{"en-GB": "Génération W"}', - 1015: '"{""en-GB"": ""Pita', - 1017: '{"en-GB": "Belles cartes des vins"}', - 1021: '{"en-GB": "Smiley FAVV/AFSCA"}', - 1010: '{"en-GB": "Mastercooks"}', - 1019: '{"en-GB": "Où manger les pieds dans l\'eau ?"}', - 1020: '{"en-GB": "Où siroter une petite mousse ? "}', - 1006: '{"en-GB": "Vegetarian Menus"}', - 1003: '{"en-GB": "BYO"}', - 1025: '{"en-GB": "Traditional Georgian"}', - 1024: '{"en-GB": "Les Grands de demain 2018"}', - 1043: '{"en-GB": "Top Gastro"}', - 1026: '{"en-GB": "Top Luxury"}', - 1028: '{"en-GB": "Gastronomic"}', - 1031: '{"en-GB": "Les dotés qui viennent d\'ouvrir"}', - 1029: '{"en-GB": "Wine Restaurant"}', - 1027: '{"en-GB": "Iconic Places"}', - 1032: '{"en-GB": "Les 5 dotés à 3 toques et plus"}', - 1047: '{"en-GB": "Our Mother\'s Day picks"}', - 1038: '{"en-GB": "Gastronomique"}', - 1023: '{"en-GB": "Jeunes Talents 2018"}', - 1040: '{"en-GB": "Our selection of pastry shops"}', - 1035: '{"en-GB": "Romantic Restaurants"}', - 1045: '{"en-GB": "Traditional cuisine"}', - 1039: '{"en-GB": "Les plus grands gastros français"}', - 1030: '{"en-GB": "Dotation G&M"}', - 1044: '{"en-GB": "Ca va ouvrir"}', - 1041: '{"en-GB": "Delicious lunch in Split"}', - 1034: '{"en-GB": "POPセレクション 2018"}', - 1046: '{"en-GB": "Romantic restaurants"}', - 1042: '{"en-GB": "Top Gastro"}', - 1037: '{"en-GB": "アワード受賞レストランに行きたい!"}', - 1048: '{"en-GB": "Top Long Lunch choices"}', - 1049: '{"en-GB": "Creative Degustation Menus"}', - 1050: '{"en-GB": "2018年 高評価レストラン"}', - 1069: '{"en-GB": "Discovery Cheque"}', - 1054: '{"en-GB": "restaurant vegan"}', - 1070: '{"en-GB": "Les Grands de Demain 2019"}', - 1063: '{"en-GB": "Kosher"}', - 1058: '{"en-GB": "Trophees 2018"}', - 1064: '{"en-GB": "Asian Restaurants"}', - 1061: '{"en-GB": "Business"}', - 1065: '{"en-GB": "Italian Restaurants"}', - 1051: '{"en-GB": "PoP"}', - 1057: '{"en-GB": "Les Patrons Cuisiniers"}', - 1067: '{"en-GB": "POP"}', - 1053: '{"en-GB": "Cocktail Bar"}', - 1062: '{"en-GB": "Romantic Restaurants"}', - 1072: '{"en-GB": "Top Gastro Antilles"}', - 1052: '{"en-GB": "Top Gastro"}', - 1078: '{"en-GB": "POPセレクション2019"}', - 1081: '{"en-GB": "Oradea"}', - 1083: '{"en-GB": "Sibiu"}', - 1085: '{"en-GB": "Destinații speciale"}', - 1086: '{"en-GB": "Premiile Gault&Millau 2018"}', - 1087: '{"en-GB": "Partener G&M: Nespresso"}', - 1088: '{"en-GB": "アワード受賞レストランに行きたい!2019"}', - 1089: '{"en-GB": "高評価レストラン 2019"}', - 1090: '{"en-GB": "Gault&Millau Croatia Trophies 2019"}', - 1091: '{"en-GB": "The best restaurants 2019"}', - 1092: '{"en-GB": "\"Yummy\" lunch in Rijeka"}', - 1093: '{"en-GB": "POP places in Zagreb"}', - 1094: '{"en-GB": "東京 フランス料理 2019"}', - 1095: '{"en-GB": "東京 日本料理 2019"}', - 1096: '{"en-GB": "東京 イタリア料理 2019"}', - 1097: '{"en-GB": "東京 スペイン料理 2019"}', - 1098: '{"en-GB": "東京 イノベーティブ 2019"}', - 1099: '{"en-GB": "Pește și fructe de mare"}', - 1056: '{"en-GB": "Alliance Gastronomique"}', - 1060: '{"en-GB": "Meat Restaurants"}', - 1068: '{"en-GB": "The Young Masters"}', - 1055: '{"en-GB": "JRE"}', - 1071: '{"en-GB": "Les Jeunes Talents 2019"}', - 1059: '{"en-GB": "Fish Restaurants"}', - 1066: '{"en-GB": "Best Cocktail Bars"}', - 1077: '{"en-GB": "POP"}', - 1074: '{"en-GB": "Vue Exceptionnelle"}', - 1073: '{"en-GB": "Peyi"}', - 1075: '{"en-GB": "Top Bar"}', - 1076: '{"en-GB": "Euro-Toques"}', - 1080: '{"en-GB": "Cluj-Napoca"}', - 1079: '{"en-GB": "București"}', - 1084: '{"en-GB": "Timișoara"}', - 1082: '{"en-GB": "Brașov"}', - 1128: '{"en-GB": "asdad2"}', -} - - -def adding_description(apps, schema_editor): - Collection = apps.get_model("collection", "Collection") - for collection in Collection.objects.all(): - collection.description = DESCRIPTION_DATA.get(collection.id) - collection.save() - - class Migration(migrations.Migration): dependencies = [ ('collection', '0024_auto_20191215_2156'), @@ -167,5 +17,4 @@ class Migration(migrations.Migration): help_text='{"en-GB":"some text"}', null=True, verbose_name='description'), ), - migrations.RunPython(adding_description), ] From 1545ca9035d5aca1fb949ee853264cc354d37c91 Mon Sep 17 00:00:00 2001 From: dormantman Date: Mon, 16 Dec 2019 22:26:14 +0300 Subject: [PATCH 71/81] Added description field --- apps/collection/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/collection/models.py b/apps/collection/models.py index 099491ac..5c2e081c 100644 --- a/apps/collection/models.py +++ b/apps/collection/models.py @@ -75,9 +75,9 @@ class Collection(ProjectBaseMixin, CollectionDateMixin, block_size = JSONField( _('collection block properties'), null=True, blank=True, default=None, help_text='{"width": "250px", "height":"250px"}') - # description = TJSONField( - # _('description'), null=True, blank=True, - # default=None, help_text='{"en-GB":"some text"}') + description = TJSONField( + _('description'), null=True, blank=True, + default=None, help_text='{"en-GB":"some text"}') slug = models.SlugField(max_length=50, unique=True, verbose_name=_('Collection slug'), editable=True, null=True) old_id = models.IntegerField(null=True, blank=True) From 913da37b84934ded119f4ce26489d9e90beaeb99 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 16 Dec 2019 22:26:38 +0300 Subject: [PATCH 72/81] return collections dsc field --- apps/collection/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/collection/models.py b/apps/collection/models.py index 099491ac..5c2e081c 100644 --- a/apps/collection/models.py +++ b/apps/collection/models.py @@ -75,9 +75,9 @@ class Collection(ProjectBaseMixin, CollectionDateMixin, block_size = JSONField( _('collection block properties'), null=True, blank=True, default=None, help_text='{"width": "250px", "height":"250px"}') - # description = TJSONField( - # _('description'), null=True, blank=True, - # default=None, help_text='{"en-GB":"some text"}') + description = TJSONField( + _('description'), null=True, blank=True, + default=None, help_text='{"en-GB":"some text"}') slug = models.SlugField(max_length=50, unique=True, verbose_name=_('Collection slug'), editable=True, null=True) old_id = models.IntegerField(null=True, blank=True) From 04bada1cd186698847512e3dad66343aed644e36 Mon Sep 17 00:00:00 2001 From: dormantman Date: Mon, 16 Dec 2019 22:32:38 +0300 Subject: [PATCH 73/81] Fixed object names --- apps/collection/models.py | 15 +++++++++------ apps/collection/serializers/back.py | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/apps/collection/models.py b/apps/collection/models.py index 5c2e081c..41e0118a 100644 --- a/apps/collection/models.py +++ b/apps/collection/models.py @@ -112,19 +112,22 @@ class Collection(ProjectBaseMixin, CollectionDateMixin, @property def related_object_names(self) -> list: """Return related object names.""" - raw_object_names = {} - for related_object in [(related_object.id, related_object.name) for related_object in self._related_objects]: - instances = getattr(self, f'{related_object[1]}') + raw_objects = [] + for related_object in [related_object.name for related_object in self._related_objects]: + instances = getattr(self, f'{related_object}') if instances.exists(): for instance in instances.all(): - raw_object_names[related_object[0]] = instance.slug if hasattr(instance, 'slug') else None + raw_object = (instance.id, instance.slug) if hasattr(instance, 'slug') else ( + instance.id, None + ) + raw_objects.append(raw_object) # parse slugs related_objects = [] object_names = set() re_pattern = r'[\w]+' - for object_id in raw_object_names: - result = re.findall(re_pattern, raw_object_names[object_id]) + for object_id, raw_name, in raw_objects: + result = re.findall(re_pattern, raw_name) if result: name = ' '.join(result).capitalize() if name not in object_names: diff --git a/apps/collection/serializers/back.py b/apps/collection/serializers/back.py index d8472af1..35917142 100644 --- a/apps/collection/serializers/back.py +++ b/apps/collection/serializers/back.py @@ -21,7 +21,7 @@ class CollectionBackOfficeSerializer(CollectionBaseSerializer): source='get_collection_type_display', read_only=True) country = CountrySimpleSerializer(read_only=True) count_related_objects = serializers.IntegerField(read_only=True) - related_object_names = serializers.JSONField(read_only=True) + related_object_names = serializers.ListField(read_only=True) class Meta: model = models.Collection From 0a25ec3e7ca421aba50017a65d778f4723f62a17 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 16 Dec 2019 23:16:12 +0300 Subject: [PATCH 74/81] news duplication info --- .../migrations/0043_auto_20191216_1920.py | 37 +++++++++++++++++++ apps/news/models.py | 19 ++++++++-- apps/news/serializers.py | 16 ++++++++ 3 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 apps/news/migrations/0043_auto_20191216_1920.py diff --git a/apps/news/migrations/0043_auto_20191216_1920.py b/apps/news/migrations/0043_auto_20191216_1920.py new file mode 100644 index 00000000..03f5a991 --- /dev/null +++ b/apps/news/migrations/0043_auto_20191216_1920.py @@ -0,0 +1,37 @@ +# Generated by Django 2.2.7 on 2019-12-16 19:20 + +import django.contrib.postgres.fields.hstore +from django.db import migrations, models +import uuid + +def fill_uuid(apps, schemaeditor): + News = apps.get_model('news', 'News') + for news in News.objects.all(): + news.duplication_uuid = uuid.uuid4() + news.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0042_news_duplication_date'), + ] + + operations = [ + migrations.AddField( + model_name='news', + name='description_to_locale_is_active', + field=django.contrib.postgres.fields.hstore.HStoreField(blank=True, default=dict, help_text='{"en-GB": true, "fr-FR": false}', null=True, verbose_name='Is description for certain locale active'), + ), + migrations.AddField( + model_name='news', + name='duplication_uuid', + field=models.UUIDField(default=uuid.uuid4, verbose_name='Field to detect doubles'), + ), + migrations.AlterField( + model_name='news', + name='slugs', + field=django.contrib.postgres.fields.hstore.HStoreField(blank=True, default=dict, help_text='{"en-GB":"some slug"}', null=True, verbose_name='Slugs for current news obj'), + ), + migrations.RunPython(fill_uuid, migrations.RunPython.noop), + ] diff --git a/apps/news/models.py b/apps/news/models.py index 6ebdca76..9a463026 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -1,5 +1,9 @@ """News app models.""" +import uuid + +from django.conf import settings from django.contrib.contenttypes import fields as generic +from django.contrib.postgres.fields import HStoreField from django.db import models from django.db.models import Case, When from django.utils import timezone @@ -11,8 +15,6 @@ from utils.models import (BaseAttributes, TJSONField, TranslatedFieldsMixin, Has ProjectBaseMixin, GalleryModelMixin, IntermediateGalleryModelMixin, FavoritesMixin) from utils.querysets import TranslationQuerysetMixin -from django.conf import settings -from django.contrib.postgres.fields import HStoreField class Agenda(ProjectBaseMixin, TranslatedFieldsMixin): @@ -177,11 +179,14 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi description = TJSONField(blank=True, null=True, default=None, verbose_name=_('description'), help_text='{"en-GB":"some text"}') + description_to_locale_is_active = HStoreField(null=True, default=dict, blank=True, + verbose_name=_('Is description for certain locale active'), + help_text='{"en-GB": true, "fr-FR": false}') start = models.DateTimeField(blank=True, null=True, default=None, verbose_name=_('Start')) end = models.DateTimeField(blank=True, null=True, default=None, verbose_name=_('End')) - slugs = HStoreField(null=True, blank=True, default=None, + slugs = HStoreField(null=True, blank=True, default=dict, verbose_name=_('Slugs for current news obj'), help_text='{"en-GB":"some slug"}') state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES, @@ -213,6 +218,8 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi on_delete=models.SET_NULL, verbose_name=_('site settings')) duplication_date = models.DateTimeField(blank=True, null=True, default=None, verbose_name=_('Duplication datetime')) + duplication_uuid = models.UUIDField(default=uuid.uuid4, editable=True, unique=False, + verbose_name=_('Field to detect doubles')) objects = NewsQuerySet.as_manager() class Meta: @@ -233,6 +240,12 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi self.duplication_date = timezone.now() self.save() + + @property + def duplicates(self): + """Duplicates for this news item excluding same country code labeled""" + return News.objects.filter(duplication_uuid=self.duplication_uuid).exclude(country=self.country) + @property def is_publish(self): return self.state in self.PUBLISHED_STATES diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 5b9a8162..59076776 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -209,6 +209,20 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer): return super().update(instance, validated_data) +class NewsBackOfficeDuplicationInfoSerializer(serializers.ModelSerializer): + """Duplication info for news detail.""" + + country = CountrySimpleSerializer(read_only=True) + + class Meta: + model = models.News + fields = ( + 'id', + 'duplication_date', + 'country', + ) + + class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer, NewsDetailSerializer): """News detail serializer for back-office users.""" @@ -224,6 +238,7 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer, queryset=SiteSettings.objects.all()) template_display = serializers.CharField(source='get_template_display', read_only=True) + duplicates = NewsBackOfficeDuplicationInfoSerializer(many=True, allow_null=True, read_only=True) class Meta(NewsBackOfficeBaseSerializer.Meta, NewsDetailSerializer.Meta): """Meta class.""" @@ -237,6 +252,7 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer, 'template', 'template_display', 'is_international', + 'duplicates', ) From b34ab11fd881ef98244895254ff84c6326b93260 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 16 Dec 2019 23:48:55 +0300 Subject: [PATCH 75/81] description news availability management from BO --- .../news/migrations/0044_auto_20191216_2044.py | 18 ++++++++++++++++++ apps/news/models.py | 2 +- apps/news/serializers.py | 1 + 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 apps/news/migrations/0044_auto_20191216_2044.py diff --git a/apps/news/migrations/0044_auto_20191216_2044.py b/apps/news/migrations/0044_auto_20191216_2044.py new file mode 100644 index 00000000..3854cc70 --- /dev/null +++ b/apps/news/migrations/0044_auto_20191216_2044.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.7 on 2019-12-16 20:44 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0043_auto_20191216_1920'), + ] + + operations = [ + migrations.RenameField( + model_name='news', + old_name='description_to_locale_is_active', + new_name='locale_to_description_is_active', + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index 9a463026..149b8480 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -179,7 +179,7 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi description = TJSONField(blank=True, null=True, default=None, verbose_name=_('description'), help_text='{"en-GB":"some text"}') - description_to_locale_is_active = HStoreField(null=True, default=dict, blank=True, + locale_to_description_is_active = HStoreField(null=True, default=dict, blank=True, verbose_name=_('Is description for certain locale active'), help_text='{"en-GB": true, "fr-FR": false}') start = models.DateTimeField(blank=True, null=True, default=None, diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 59076776..ad29f1ac 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -182,6 +182,7 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer): 'backoffice_title', 'subtitle', 'slugs', + 'locale_to_description_is_active', 'is_published', 'duplication_date', ) From 6a987532bad9b81657734a0754364848f5521c9d Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 17 Dec 2019 11:54:50 +0300 Subject: [PATCH 76/81] fix recipe duplicates --- apps/recipe/models.py | 3 +++ apps/recipe/serializers/common.py | 11 +++++++++-- apps/recipe/transfer_data.py | 17 +++++++++++++++++ apps/recipe/views/common.py | 14 +++++++++++++- apps/review/transfer_data.py | 4 +++- apps/transfer/management/commands/transfer.py | 1 + 6 files changed, 46 insertions(+), 4 deletions(-) diff --git a/apps/recipe/models.py b/apps/recipe/models.py index 6eaa2155..c419be4c 100644 --- a/apps/recipe/models.py +++ b/apps/recipe/models.py @@ -25,6 +25,9 @@ class RecipeQuerySet(models.QuerySet): default=False, output_field=models.BooleanField(default=False))) + def by_locale(self, locale): + return self.filter(title__icontains=locale) + class Recipe(TranslatedFieldsMixin, ImageMixin, BaseAttributes): """Recipe model.""" diff --git a/apps/recipe/serializers/common.py b/apps/recipe/serializers/common.py index fec5978d..a0ec4363 100644 --- a/apps/recipe/serializers/common.py +++ b/apps/recipe/serializers/common.py @@ -14,8 +14,15 @@ class RecipeListSerializer(serializers.ModelSerializer): """Meta class.""" model = models.Recipe - fields = ('id', 'title_translated', 'subtitle_translated', 'author', - 'published_at', 'in_favorites') + fields = ( + 'id', + 'title_translated', + 'subtitle_translated', + 'author', + 'created_by', + 'published_at', + 'in_favorites', + ) read_only_fields = fields diff --git a/apps/recipe/transfer_data.py b/apps/recipe/transfer_data.py index 22b44baa..4c1c3a5a 100644 --- a/apps/recipe/transfer_data.py +++ b/apps/recipe/transfer_data.py @@ -1,5 +1,8 @@ from pprint import pprint +from django.db.models import Count + +from recipe.models import Recipe from transfer.models import PageTexts from transfer.serializers.recipe import RecipeSerializer @@ -25,6 +28,20 @@ def transfer_recipe(): serialized_data.save() else: pprint(f'Recipe serializer errors: {serialized_data.errors}') + return + + # Удаление дубликатов рецептов по одинаковым description + duplicate_descriptions = Recipe.objects.values( + 'description' + ).annotate( + description_count=Count('description') + ).filter( + description_count__gt=1 + ) + for data in duplicate_descriptions: + description = data['description'] + _list = list(Recipe.objects.filter(description=description).values_list('pk', flat=True)[1:]) + Recipe.objects.filter(id__in=_list).delete() data_types = { diff --git a/apps/recipe/views/common.py b/apps/recipe/views/common.py index f268107e..31e74f20 100644 --- a/apps/recipe/views/common.py +++ b/apps/recipe/views/common.py @@ -1,5 +1,7 @@ """Recipe app common views.""" +from django.utils import translation from rest_framework import generics, permissions + from recipe import models from recipe.serializers import common as serializers @@ -10,9 +12,14 @@ class RecipeViewMixin(generics.GenericAPIView): pagination_class = None permission_classes = (permissions.AllowAny,) - def get_queryset(self): + def get_queryset(self, *args, **kwargs): user = self.request.user qs = models.Recipe.objects.published().annotate_in_favorites(user) + + locale = kwargs.get('locale') + if locale: + qs = qs.by_locale(locale) + return qs @@ -21,6 +28,11 @@ class RecipeListView(RecipeViewMixin, generics.ListAPIView): serializer_class = serializers.RecipeListSerializer + def get_queryset(self, *args, **kwargs): + locale = translation.get_language() + kwargs.update({'locale': locale}) + return super().get_queryset(*args, **kwargs) + class RecipeDetailView(RecipeViewMixin, generics.RetrieveAPIView): """Resource for detailed recipe information.""" diff --git a/apps/review/transfer_data.py b/apps/review/transfer_data.py index 20c5712d..ac3749f6 100644 --- a/apps/review/transfer_data.py +++ b/apps/review/transfer_data.py @@ -127,8 +127,10 @@ def transfer_product_reviews(): data_types = { + "languages": [ + transfer_languages, + ], "overlook": [ - # transfer_languages, transfer_reviews, transfer_text_review, make_en_text_review, diff --git a/apps/transfer/management/commands/transfer.py b/apps/transfer/management/commands/transfer.py index b4a42349..4e849b13 100644 --- a/apps/transfer/management/commands/transfer.py +++ b/apps/transfer/management/commands/transfer.py @@ -49,6 +49,7 @@ class Command(BaseCommand): 'guide_elements_bulk', 'guide_element_advertorials', 'guide_complete', + 'languages', # №4 - перенос языков ] def handle(self, *args, **options): From dbbb526902ace414e009881b2d03ef82732a237a Mon Sep 17 00:00:00 2001 From: Anatoly Date: Tue, 17 Dec 2019 13:02:46 +0300 Subject: [PATCH 77/81] Refactored mechanism to find similar objects. Added endpoints to product app. --- apps/establishment/models.py | 43 ++++++++++++-------- apps/establishment/urls/common.py | 8 ++-- apps/establishment/views/web.py | 29 ++++++++------ apps/product/models.py | 64 ++++++++++++++++++++++++++++-- apps/product/serializers/common.py | 2 +- apps/product/urls/common.py | 9 +++++ apps/product/views/common.py | 18 +++++++++ apps/utils/pagination.py | 3 +- 8 files changed, 138 insertions(+), 38 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 5ff6f921..8162aab1 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -212,6 +212,10 @@ class EstablishmentQuerySet(models.QuerySet): output_field=models.FloatField(default=0) )) + def has_location(self): + """Return objects with geo location.""" + return self.filter(address__coordinates__isnull=False) + def similar_base(self, establishment): """ Return filtered QuerySet by base filters. @@ -267,25 +271,30 @@ class EstablishmentQuerySet(models.QuerySet): else: return self.none() - def similar_artisans(self, slug): + def same_subtype(self, establishment): + """Annotate flag same subtype.""" + return self.annotate(same_subtype=Case( + models.When( + establishment_subtypes__in=establishment.establishment_subtypes.all(), + then=True + ), + default=False, + output_field=models.BooleanField(default=False) + )) + + def similar_artisans_producers(self, slug): """ - Return QuerySet with objects that similar to Artisan. - :param slug: str artisan slug + Return QuerySet with objects that similar to Artisan/Producer(s). + :param slug: str artisan/producer 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') + establishment_qs = self.filter(slug=slug) + if establishment_qs.exists(): + establishment = establishment_qs.first() + return self.similar_base(establishment) \ + .same_subtype(establishment) \ + .order_by(F('same_subtype').desc(), + F('distance').asc()) \ + .distinct('same_subtype', 'distance', 'id') else: return self.none() diff --git a/apps/establishment/urls/common.py b/apps/establishment/urls/common.py index 046667df..54944a0a 100644 --- a/apps/establishment/urls/common.py +++ b/apps/establishment/urls/common.py @@ -17,12 +17,14 @@ urlpatterns = [ path('slug//favorites/', views.EstablishmentFavoritesCreateDestroyView.as_view(), name='create-destroy-favorites'), - # similar establishments + # similar establishments by type/subtype path('slug//similar/', views.RestaurantSimilarListView.as_view(), name='similar-restaurants'), path('slug//similar/wineries/', views.WinerySimilarListView.as_view(), name='similar-wineries'), - path('slug//similar/artisans/', views.ArtisanSimilarListView.as_view(), + # temporary uses single mechanism, bec. description in process + path('slug//similar/artisans/', views.ArtisanProducerSimilarListView.as_view(), name='similar-artisans'), - + path('slug//similar/producers/', views.ArtisanProducerSimilarListView.as_view(), + name='similar-producers'), ] diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index 4253b6a6..7eba8607 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -8,7 +8,7 @@ from comment import models as comment_models from comment.serializers import CommentRUDSerializer from establishment import filters, models, serializers from main import methods -from utils.pagination import EstablishmentPortionPagination +from utils.pagination import PortionPagination from utils.views import FavoritesCreateDestroyMixinView, CarouselCreateDestroyMixinView @@ -41,6 +41,12 @@ class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView): .with_certain_tag_category_related('shop_category', 'artisan_category') +class EstablishmentSimilarView(EstablishmentListView): + """Resource for getting a list of similar establishments.""" + serializer_class = serializers.EstablishmentSimilarSerializer + pagination_class = PortionPagination + + class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView): """Resource for getting a establishment.""" @@ -61,7 +67,7 @@ class EstablishmentMobileRetrieveView(EstablishmentRetrieveView): class EstablishmentRecentReviewListView(EstablishmentListView): """List view for last reviewed establishments.""" - pagination_class = EstablishmentPortionPagination + pagination_class = PortionPagination def get_queryset(self): """Overridden method 'get_queryset'.""" @@ -77,37 +83,34 @@ class EstablishmentRecentReviewListView(EstablishmentListView): return qs.last_reviewed(point=point) -class EstablishmentSimilarList(EstablishmentListView): - """Resource for getting a list of similar establishments.""" - serializer_class = serializers.EstablishmentSimilarSerializer - pagination_class = EstablishmentPortionPagination - - -class RestaurantSimilarListView(EstablishmentSimilarList): +class RestaurantSimilarListView(EstablishmentSimilarView): """Resource for getting a list of similar restaurants.""" def get_queryset(self): """Overridden get_queryset method""" return EstablishmentMixinView.get_queryset(self) \ + .has_location() \ .similar_restaurants(slug=self.kwargs.get('slug')) -class WinerySimilarListView(EstablishmentSimilarList): +class WinerySimilarListView(EstablishmentSimilarView): """Resource for getting a list of similar wineries.""" def get_queryset(self): """Overridden get_queryset method""" return EstablishmentMixinView.get_queryset(self) \ + .has_location() \ .similar_wineries(slug=self.kwargs.get('slug')) -class ArtisanSimilarListView(EstablishmentSimilarList): - """Resource for getting a list of similar artisans.""" +class ArtisanProducerSimilarListView(EstablishmentSimilarView): + """Resource for getting a list of similar artisan/producer(s).""" def get_queryset(self): """Overridden get_queryset method""" return EstablishmentMixinView.get_queryset(self) \ - .similar_artisans(slug=self.kwargs.get('slug')) + .has_location() \ + .similar_artisans_producers(slug=self.kwargs.get('slug')) class EstablishmentTypeListView(generics.ListAPIView): diff --git a/apps/product/models.py b/apps/product/models.py index 6923d6dd..7aeacdf2 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -1,13 +1,17 @@ """Product app models.""" +from django.conf import settings from django.contrib.contenttypes import fields as generic from django.contrib.gis.db import models as gis_models +from django.contrib.gis.db.models.functions import Distance +from django.contrib.gis.geos import Point from django.core.exceptions import ValidationError from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models -from django.db.models import Case, When +from django.db.models import Case, When, F from django.utils.translation import gettext_lazy as _ from location.models import WineOriginAddressMixin +from review.models import Review from utils.models import (BaseAttributes, ProjectBaseMixin, HasTagsMixin, TranslatedFieldsMixin, TJSONField, FavoritesMixin, GalleryModelMixin, IntermediateGalleryModelMixin) @@ -136,6 +140,60 @@ class ProductQuerySet(models.QuerySet): ) ) + def annotate_distance(self, point: Point = None): + """ + Return QuerySet with annotated field - distance + Description: + + """ + return self.annotate(distance=Distance('establishment__address__coordinates', + point, + srid=settings.GEO_DEFAULT_SRID)) + + def has_location(self): + """Return objects with geo location.""" + return self.filter(establishment__address__coordinates__isnull=False) + + def same_subtype(self, product): + """Annotate flag same subtype.""" + return self.annotate(same_subtype=Case( + models.When( + subtypes__in=product.subtypes.all(), + then=True + ), + default=False, + output_field=models.BooleanField(default=False) + )) + + def similar_base(self, product): + """Return QuerySet filtered by base filters for Product model.""" + filters = { + 'reviews__status': Review.READY, + 'product_type': product.product_type, + } + if product.subtypes.exists(): + filters.update( + {'subtypes__in': product.subtypes.all()}) + return self.exclude(id=product.id) \ + .filter(**filters) \ + .annotate_distance(point=product.establishment.location) + + def similar(self, slug): + """ + Return QuerySet with objects that similar to Product. + :param slug: str product slug + """ + product_qs = self.filter(slug=slug) + if product_qs.exists(): + product = product_qs.first() + return self.similar_base(product) \ + .same_subtype(product) \ + .order_by(F('same_subtype').desc(), + F('distance').asc()) \ + .distinct('same_subtype', 'distance', 'id') + else: + return self.none() + class Product(GalleryModelMixin, TranslatedFieldsMixin, BaseAttributes, HasTagsMixin, FavoritesMixin): @@ -219,8 +277,8 @@ class Product(GalleryModelMixin, TranslatedFieldsMixin, BaseAttributes, awards = generic.GenericRelation(to='main.Award', related_query_name='product') serial_number = models.CharField(max_length=255, - default=None, null=True, - verbose_name=_('Serial number')) + default=None, null=True, + verbose_name=_('Serial number')) objects = ProductManager.from_queryset(ProductQuerySet)() diff --git a/apps/product/serializers/common.py b/apps/product/serializers/common.py index e0617e63..86344a36 100644 --- a/apps/product/serializers/common.py +++ b/apps/product/serializers/common.py @@ -218,4 +218,4 @@ class ProductCommentCreateSerializer(CommentSerializer): 'user': self.context.get('request').user, 'content_object': validated_data.pop('product') }) - return super().create(validated_data) \ No newline at end of file + return super().create(validated_data) diff --git a/apps/product/urls/common.py b/apps/product/urls/common.py index bd6c331d..4d64b93e 100644 --- a/apps/product/urls/common.py +++ b/apps/product/urls/common.py @@ -16,4 +16,13 @@ urlpatterns = [ name='create-comment'), path('slug//comments//', views.ProductCommentRUDView.as_view(), name='rud-comment'), + + # similar products by type/subtype + # temporary uses single mechanism, bec. description in process + path('slug//similar/wines/', views.SimilarListView.as_view(), + name='similar-wine'), + path('slug//similar/liquors/', views.SimilarListView.as_view(), + name='similar-liquor'), + path('slug//similar/food/', views.SimilarListView.as_view(), + name='similar-food'), ] diff --git a/apps/product/views/common.py b/apps/product/views/common.py index 650c1dfe..dbb24e53 100644 --- a/apps/product/views/common.py +++ b/apps/product/views/common.py @@ -6,6 +6,7 @@ from comment.models import Comment from product import filters, serializers from comment.serializers import CommentRUDSerializer from utils.views import FavoritesCreateDestroyMixinView +from utils.pagination import PortionPagination class ProductBaseView(generics.GenericAPIView): @@ -31,6 +32,12 @@ class ProductListView(ProductBaseView, generics.ListAPIView): return qs +class ProductSimilarView(ProductListView): + """Resource for getting a list of similar product.""" + serializer_class = serializers.ProductBaseSerializer + pagination_class = PortionPagination + + class ProductDetailView(ProductBaseView, generics.RetrieveAPIView): """Detail view fro model Product.""" lookup_field = 'slug' @@ -81,3 +88,14 @@ class ProductCommentRUDView(generics.RetrieveUpdateDestroyAPIView): self.check_object_permissions(self.request, comment_obj) return comment_obj + + +class SimilarListView(ProductSimilarView): + """Return similar products.""" + + def get_queryset(self): + """Overridden get_queryset method.""" + return super().get_queryset() \ + .has_location() \ + .similar(slug=self.kwargs.get('slug')) + diff --git a/apps/utils/pagination.py b/apps/utils/pagination.py index 199d55b6..77e67d75 100644 --- a/apps/utils/pagination.py +++ b/apps/utils/pagination.py @@ -6,6 +6,7 @@ from django.conf import settings from rest_framework.pagination import CursorPagination, PageNumberPagination from django_elasticsearch_dsl_drf.pagination import PageNumberPagination as ESPagination + class ProjectPageNumberPagination(PageNumberPagination): """Customized pagination class.""" @@ -82,7 +83,7 @@ class ESDocumentPagination(ESPagination): return page.facets._d_ -class EstablishmentPortionPagination(ProjectMobilePagination): +class PortionPagination(ProjectMobilePagination): """ Pagination for app establishments with limit page size equal to 12 """ From 73f0dd13a578cba0bb693b1021212eaf3f77cee0 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 17 Dec 2019 14:00:44 +0300 Subject: [PATCH 78/81] duplicates fix --- apps/news/serializers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index ad29f1ac..bdb4f48b 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -364,9 +364,11 @@ class NewsCloneCreateSerializer(NewsBackOfficeBaseSerializer, """Serializer for creating news clone.""" template_display = serializers.CharField(source='get_template_display', read_only=True) + duplicates = NewsBackOfficeDuplicationInfoSerializer(many=True, allow_null=True, read_only=True) class Meta(NewsBackOfficeBaseSerializer.Meta, NewsDetailSerializer.Meta): fields = NewsBackOfficeBaseSerializer.Meta.fields + NewsDetailSerializer.Meta.fields + ( 'template_display', + 'duplicates', ) read_only_fields = fields From f36713ab0237d63d7395e77c1f3db615e024852a Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 17 Dec 2019 14:14:21 +0300 Subject: [PATCH 79/81] description to locale is required now --- apps/news/serializers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index bdb4f48b..66692051 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -189,6 +189,7 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer): extra_kwargs = { 'backoffice_title': {'allow_null': False}, 'duplication_date': {'read_only': True}, + 'locale_to_description_is_active': {'allow_null': False, 'blank': False} } def create(self, validated_data): From 89f8fcf73e519e547dadf6704e9fec88c0e33fec Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 17 Dec 2019 14:20:21 +0300 Subject: [PATCH 80/81] fix --- apps/news/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 66692051..e6a4bec2 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -189,7 +189,7 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer): extra_kwargs = { 'backoffice_title': {'allow_null': False}, 'duplication_date': {'read_only': True}, - 'locale_to_description_is_active': {'allow_null': False, 'blank': False} + 'locale_to_description_is_active': {'allow_null': False, 'allow_blank': False} } def create(self, validated_data): From 7608fcb52703c015b665e79122ff8753b9bfb04b Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 17 Dec 2019 14:22:08 +0300 Subject: [PATCH 81/81] fix #2 --- apps/news/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index e6a4bec2..2265a1d9 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -189,7 +189,7 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer): extra_kwargs = { 'backoffice_title': {'allow_null': False}, 'duplication_date': {'read_only': True}, - 'locale_to_description_is_active': {'allow_null': False, 'allow_blank': False} + 'locale_to_description_is_active': {'allow_null': False} } def create(self, validated_data):