From 883b214d742ac68119eaae1c9942058a27599b18 Mon Sep 17 00:00:00 2001 From: Semyon Yekhmenin Date: Wed, 27 Nov 2019 12:49:33 +0000 Subject: [PATCH 01/85] Added is_international property to news --- apps/news/models.py | 4 ++++ apps/news/serializers.py | 1 + 2 files changed, 5 insertions(+) diff --git a/apps/news/models.py b/apps/news/models.py index 66dabc90..2aa571c0 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -221,6 +221,10 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi def is_publish(self): return self.state in self.PUBLISHED_STATES + @property + def is_international(self): + return self.INTERNATIONAL_TAG_VALUE in map(lambda tag: tag.value, self.tags.all()) + @property def web_url(self): return reverse('web:news:rud', kwargs={'slug': self.slug}) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index c80ce9a5..86673645 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -201,6 +201,7 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer, 'site_id', 'template', 'template_display', + 'is_international', ) From 9b35da1eca2dc5ba2ab2cab02121e81f3b000c26 Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 27 Nov 2019 17:03:27 +0300 Subject: [PATCH 02/85] add artisan subtype --- .../commands/add_artisan_subtype.py | 48 ++++++++ apps/establishment/models.py | 1 - docker-compose.local.yml | 106 ++++++++++++++++++ env | 9 ++ 4 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 apps/establishment/management/commands/add_artisan_subtype.py create mode 100644 docker-compose.local.yml create mode 100644 env diff --git a/apps/establishment/management/commands/add_artisan_subtype.py b/apps/establishment/management/commands/add_artisan_subtype.py new file mode 100644 index 00000000..8616f2ab --- /dev/null +++ b/apps/establishment/management/commands/add_artisan_subtype.py @@ -0,0 +1,48 @@ +from django.core.management.base import BaseCommand +from tqdm import tqdm + +from establishment.models import Establishment, EstablishmentSubType, EstablishmentType +from transfer.models import Metadata + + +class Command(BaseCommand): + help = 'Add subtype for establishment artisan' + + def handle(self, *args, **options): + artisans = Establishment.objects.artisans().filter( + old_id__isnull=False, + ).prefetch_related('tags') + + old_tags = Metadata.objects.filter( + establishment__in=list(artisans.values_list('old_id', flat=True)), + key='shop_category', + ) + + tags = [] + for tag in tqdm(old_tags): + tags.append(tag.value) + subtypes = set(tags) + + es_type, _ = EstablishmentType.objects.get_or_create( + index_name='artisan', + defaults={ + 'index_name': 'artisan', + 'name': {'en-GB': 'artisan'}, + } + ) + for artisan in tqdm(artisans): + artisan_tags = artisan.tags.all() + for t in artisan_tags: + if t.value in subtypes: + tag = 'coffee_shop' if t.value == 'coffe_shop' else t.value + subtype, _ = EstablishmentSubType.objects.get_or_create( + index_name=tag, + defaults={ + 'index_name': tag, + 'name': {'en-GB': tag}, + } + ) + artisan.tags.add(subtype) + artisan.save() + + self.stdout.write(self.style.WARNING(f'Artisans subtype updated.')) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index cb490aa4..c5533d52 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -125,7 +125,6 @@ class EstablishmentQuerySet(models.QuerySet): 'menu_set__plate_set__currency', 'currency'). \ prefetch_actual_employees() - def with_type_related(self): return self.prefetch_related('establishment_subtypes') diff --git a/docker-compose.local.yml b/docker-compose.local.yml new file mode 100644 index 00000000..1a2d9236 --- /dev/null +++ b/docker-compose.local.yml @@ -0,0 +1,106 @@ +version: '3.5' +services: + + # Legacy MySQL DB + mysql_db: + image: mysql:5.7 + ports: + - "3306:3306" + environment: + MYSQL_DATABASE: dev + MYSQL_USER: dev + MYSQL_PASSWORD: octosecret123 + MYSQL_ROOT_PASSWORD: rootPassword + volumes: + - gm-mysql_db:/var/lib/mysql + + + # PostgreSQL database + db: + build: + context: ./_dockerfiles/db + dockerfile: Dockerfile + hostname: db + env_file: + - env + ports: + - "5436:5432" + volumes: + - gm-db:/var/lib/postgresql/data/ + - ./local_files/dump_alex.sql:/dump_alex.sql +# - ./local_files/docker-entrypoint.sh:/docker-entrypoint-initdb.d/docker-entrypoint.sh + + + elasticsearch: + image: elasticsearch:7.3.1 + volumes: + - gm-esdata:/usr/share/elasticsearch/data + hostname: elasticsearch + ports: + - 9200:9200 + - 9300:9300 + environment: + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + - discovery.type=single-node + - xpack.security.enabled=false + + + # Redis + redis: + image: redis:alpine + + + # Celery + worker: + build: . + command: ./run_celery.sh + env_file: + - env + volumes: + - .:/code + links: + - db + - redis + + + worker_beat: + build: . + command: ./run_celery_beat.sh + env_file: + - env + volumes: + - .:/code + links: + - db + - redis + + + # App: G&M + gm_app: + build: . + command: python manage.py runserver 0.0.0.0:8000 + env_file: + - env + depends_on: + - mysql_db + - db + - redis + - worker + - worker_beat + - elasticsearch + volumes: + - .:/code + - gm-media:/media-data + ports: + - "8000:8000" + + +volumes: + gm-mysql_db: + name: gm-mysql_db + gm-db: + name: gm-db + gm-media: + name: gm-media + gm-esdata: + diff --git a/env b/env new file mode 100644 index 00000000..67e9f44f --- /dev/null +++ b/env @@ -0,0 +1,9 @@ +SETTINGS_CONFIGURATION=local +DB_NAME=gm +DB_USERNAME=gm +DB_HOSTNAME=db +DB_PORT=5432 +DB_PASSWORD=gm +POSTGRES_USER=gm +POSTGRES_PASSWORD=gm +POSTGRES_DB=gm \ No newline at end of file From 78d414533686da4a31b1410dbf926ea5c90d90dc Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 28 Nov 2019 09:13:38 +0300 Subject: [PATCH 03/85] artisan subtype --- apps/establishment/management/commands/add_artisan_subtype.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/establishment/management/commands/add_artisan_subtype.py b/apps/establishment/management/commands/add_artisan_subtype.py index 8616f2ab..ac93d81f 100644 --- a/apps/establishment/management/commands/add_artisan_subtype.py +++ b/apps/establishment/management/commands/add_artisan_subtype.py @@ -40,9 +40,10 @@ class Command(BaseCommand): defaults={ 'index_name': tag, 'name': {'en-GB': tag}, + 'establishment_type': es_type, } ) - artisan.tags.add(subtype) + artisan.establishment_subtypes.add(subtype) artisan.save() self.stdout.write(self.style.WARNING(f'Artisans subtype updated.')) From 749c4a637bf5f914da0de9bd80a9dce45ef73a82 Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 28 Nov 2019 09:19:53 +0300 Subject: [PATCH 04/85] artisan subtype fix --- .../commands/add_artisan_subtype.py | 2 +- docker-compose.local.yml | 106 ------------------ env | 9 -- 3 files changed, 1 insertion(+), 116 deletions(-) delete mode 100644 docker-compose.local.yml delete mode 100644 env diff --git a/apps/establishment/management/commands/add_artisan_subtype.py b/apps/establishment/management/commands/add_artisan_subtype.py index ac93d81f..f7283f4f 100644 --- a/apps/establishment/management/commands/add_artisan_subtype.py +++ b/apps/establishment/management/commands/add_artisan_subtype.py @@ -39,7 +39,7 @@ class Command(BaseCommand): index_name=tag, defaults={ 'index_name': tag, - 'name': {'en-GB': tag}, + 'name': {'en-GB': ' '.join(tag.split('_')).capitalize()}, 'establishment_type': es_type, } ) diff --git a/docker-compose.local.yml b/docker-compose.local.yml deleted file mode 100644 index 1a2d9236..00000000 --- a/docker-compose.local.yml +++ /dev/null @@ -1,106 +0,0 @@ -version: '3.5' -services: - - # Legacy MySQL DB - mysql_db: - image: mysql:5.7 - ports: - - "3306:3306" - environment: - MYSQL_DATABASE: dev - MYSQL_USER: dev - MYSQL_PASSWORD: octosecret123 - MYSQL_ROOT_PASSWORD: rootPassword - volumes: - - gm-mysql_db:/var/lib/mysql - - - # PostgreSQL database - db: - build: - context: ./_dockerfiles/db - dockerfile: Dockerfile - hostname: db - env_file: - - env - ports: - - "5436:5432" - volumes: - - gm-db:/var/lib/postgresql/data/ - - ./local_files/dump_alex.sql:/dump_alex.sql -# - ./local_files/docker-entrypoint.sh:/docker-entrypoint-initdb.d/docker-entrypoint.sh - - - elasticsearch: - image: elasticsearch:7.3.1 - volumes: - - gm-esdata:/usr/share/elasticsearch/data - hostname: elasticsearch - ports: - - 9200:9200 - - 9300:9300 - environment: - - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - - discovery.type=single-node - - xpack.security.enabled=false - - - # Redis - redis: - image: redis:alpine - - - # Celery - worker: - build: . - command: ./run_celery.sh - env_file: - - env - volumes: - - .:/code - links: - - db - - redis - - - worker_beat: - build: . - command: ./run_celery_beat.sh - env_file: - - env - volumes: - - .:/code - links: - - db - - redis - - - # App: G&M - gm_app: - build: . - command: python manage.py runserver 0.0.0.0:8000 - env_file: - - env - depends_on: - - mysql_db - - db - - redis - - worker - - worker_beat - - elasticsearch - volumes: - - .:/code - - gm-media:/media-data - ports: - - "8000:8000" - - -volumes: - gm-mysql_db: - name: gm-mysql_db - gm-db: - name: gm-db - gm-media: - name: gm-media - gm-esdata: - diff --git a/env b/env deleted file mode 100644 index 67e9f44f..00000000 --- a/env +++ /dev/null @@ -1,9 +0,0 @@ -SETTINGS_CONFIGURATION=local -DB_NAME=gm -DB_USERNAME=gm -DB_HOSTNAME=db -DB_PORT=5432 -DB_PASSWORD=gm -POSTGRES_USER=gm -POSTGRES_PASSWORD=gm -POSTGRES_DB=gm \ No newline at end of file From 19190eb1e0b26c663e3ad3a7603018982738c9a6 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 28 Nov 2019 14:00:33 +0300 Subject: [PATCH 05/85] Fix hardcoded tags (cherry picked from commit 4e5291d) --- apps/tag/filters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/tag/filters.py b/apps/tag/filters.py index a7d9da9d..5e2b31a7 100644 --- a/apps/tag/filters.py +++ b/apps/tag/filters.py @@ -86,7 +86,7 @@ class TagsFilterSet(TagsBaseFilterSet): if self.NEWS in value: queryset = queryset.for_news().filter(value__in=settings.NEWS_CHOSEN_TAGS).distinct('value') if self.ESTABLISHMENT in value: - queryset = queryset.for_establishments().filter(value__in=settings.ESTABLISHMENT_CHOSEN_TAGS).distinct( + queryset = queryset.for_establishments().filter(category__value_type='list').filter(value__in=settings.ESTABLISHMENT_CHOSEN_TAGS).distinct( 'value') return queryset From 4bc3af29a048e1963dca74e281b3935cf9a3f3b5 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 28 Nov 2019 14:24:39 +0300 Subject: [PATCH 06/85] Fix booking --- apps/booking/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/booking/views.py b/apps/booking/views.py index 73f6f55e..c2a143a6 100644 --- a/apps/booking/views.py +++ b/apps/booking/views.py @@ -84,8 +84,8 @@ class CheckWhetherBookingAvailable(generics.GenericAPIView): service_response = self._preprocess_guestonline_response(service.response) \ if establishment.guestonline_id is not None \ - else service.response - response.update({'details': service_response} if service and service.response else {}) + else service.response if service else None + response.update({'details': service_response}) return Response(data=response, status=200) From f225336121d6087684fe14f143f422e6be2980d4 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 28 Nov 2019 14:45:52 +0300 Subject: [PATCH 07/85] Remove facets from mobile --- apps/search_indexes/urls.py | 4 +++- apps/search_indexes/views.py | 27 ++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/apps/search_indexes/urls.py b/apps/search_indexes/urls.py index 70e21369..902ccfeb 100644 --- a/apps/search_indexes/urls.py +++ b/apps/search_indexes/urls.py @@ -6,9 +6,11 @@ from search_indexes import views router = routers.SimpleRouter() # router.register(r'news', views.NewsDocumentViewSet, basename='news') # temporarily disabled router.register(r'establishments', views.EstablishmentDocumentViewSet, basename='establishment') -router.register(r'mobile/establishments', views.EstablishmentDocumentViewSet, basename='establishment-mobile') +router.register(r'mobile/establishments', views.MobileEstablishmentDocumentViewSet, basename='establishment-mobile') router.register(r'news', views.NewsDocumentViewSet, basename='news') +router.register(r'mobile/news', views.MobileNewsDocumentViewSet, basename='news-mobile') router.register(r'products', views.ProductDocumentViewSet, basename='product') +router.register(r'mobile/products', views.MobileProductDocumentViewSet, basename='product-mobile') urlpatterns = router.urls diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index 37ed46fb..e352f9be 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -78,6 +78,14 @@ class NewsDocumentViewSet(BaseDocumentViewSet): } +class MobileNewsDocumentViewSet(NewsDocumentViewSet): + + filter_backends = [ + filters.CustomSearchFilterBackend, + FilteringFilterBackend, + ] + + class EstablishmentDocumentViewSet(BaseDocumentViewSet): """Establishment document ViewSet.""" @@ -278,6 +286,15 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): } +class MobileEstablishmentDocumentViewSet(EstablishmentDocumentViewSet): + + filter_backends = [ + FilteringFilterBackend, + filters.CustomSearchFilterBackend, + GeoSpatialFilteringFilterBackend, + ] + + class ProductDocumentViewSet(BaseDocumentViewSet): """Product document ViewSet.""" @@ -380,4 +397,12 @@ class ProductDocumentViewSet(BaseDocumentViewSet): constants.LOOKUP_QUERY_EXCLUDE, ], }, - } \ No newline at end of file + } + + +class MobileProductDocumentViewSet(ProductDocumentViewSet): + + filter_backends = [ + FilteringFilterBackend, + filters.CustomSearchFilterBackend, + ] From 03b7ae678ef112e09f2e82a8ed68f3437dfaf7dd Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 28 Nov 2019 14:59:41 +0300 Subject: [PATCH 08/85] All facets are global now --- apps/search_indexes/views.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index e352f9be..7f8520d7 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -34,6 +34,7 @@ class NewsDocumentViewSet(BaseDocumentViewSet): 'field': 'tags.id', 'enabled': True, 'facet': TermsFacet, + 'global': True, 'options': { 'size': utils.FACET_MAX_RESPONSE, }, @@ -111,31 +112,37 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): 'field': 'works_at_weekday', 'facet': TermsFacet, 'enabled': True, + 'global': True, }, 'toque_number': { 'field': 'toque_number', 'enabled': True, 'facet': TermsFacet, + 'global': True, }, 'works_noon': { 'field': 'works_noon', 'facet': TermsFacet, 'enabled': True, + 'global': True, }, 'works_evening': { 'field': 'works_evening', 'facet': TermsFacet, 'enabled': True, + 'global': True, }, 'works_now': { 'field': 'works_now', 'facet': TermsFacet, 'enabled': True, + 'global': True, }, 'tag': { 'field': 'visible_tags.id', 'facet': TermsFacet, 'enabled': True, + 'global': True, 'options': { 'size': utils.FACET_MAX_RESPONSE, }, @@ -144,6 +151,7 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): 'field': 'products.wine_colors.id', 'facet': TermsFacet, 'enabled': True, + 'global': True, 'options': { 'size': utils.FACET_MAX_RESPONSE, }, @@ -152,6 +160,7 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): 'field': 'products.wine_region.id', 'facet': TermsFacet, 'enabled': True, + 'global': True, 'options': { 'size': utils.FACET_MAX_RESPONSE, }, @@ -325,6 +334,7 @@ class ProductDocumentViewSet(BaseDocumentViewSet): 'tag': { 'field': 'wine_colors.id', 'enabled': True, + 'global': True, 'facet': TermsFacet, 'options': { 'size': utils.FACET_MAX_RESPONSE, @@ -333,6 +343,7 @@ class ProductDocumentViewSet(BaseDocumentViewSet): 'wine_region_id': { 'field': 'wine_region.id', 'enabled': True, + 'global': True, 'facet': TermsFacet, 'options': { 'size': utils.FACET_MAX_RESPONSE, From a8ff626179fd89e60a9dade65528e1307efaf8ab Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 28 Nov 2019 15:10:40 +0300 Subject: [PATCH 09/85] ReviewBackSerializer --- apps/review/serializers/back.py | 20 +++++++++++++++++++- apps/review/serializers/common.py | 2 ++ apps/review/views/back.py | 5 +++-- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/apps/review/serializers/back.py b/apps/review/serializers/back.py index c72cb205..674e1edc 100644 --- a/apps/review/serializers/back.py +++ b/apps/review/serializers/back.py @@ -1,3 +1,21 @@ """Review app back serializers.""" -from review import models from rest_framework import serializers + +from review import models + + +class ReviewBackSerializer(serializers.ModelSerializer): + class Meta: + model = models.Review + fields = ( + 'id', + 'reviewer', + 'text', + 'status', + # 'child', + 'published_at', + 'vintage', + # 'country', + 'content_type', + 'object_id', + ) diff --git a/apps/review/serializers/common.py b/apps/review/serializers/common.py index da7b624b..b1025d34 100644 --- a/apps/review/serializers/common.py +++ b/apps/review/serializers/common.py @@ -33,6 +33,7 @@ class ReviewShortSerializer(ReviewBaseSerializer): class InquiriesBaseSerializer(serializers.ModelSerializer): """Serializer for model Inquiries.""" + class Meta: model = Inquiries fields = ( @@ -56,6 +57,7 @@ class InquiriesBaseSerializer(serializers.ModelSerializer): class GridItemsBaseSerializer(serializers.ModelSerializer): """Serializer for model GridItems.""" + class Meta: model = GridItems fields = ( diff --git a/apps/review/views/back.py b/apps/review/views/back.py index 27f9af0d..511c91f9 100644 --- a/apps/review/views/back.py +++ b/apps/review/views/back.py @@ -4,11 +4,12 @@ from review import filters from review import models from review import serializers from utils.permissions import IsReviewerManager, IsRestaurantReviewer +from review.serializers.back import ReviewBackSerializer class ReviewLstView(generics.ListCreateAPIView): """Comment list create view.""" - serializer_class = serializers.ReviewBaseSerializer + serializer_class = ReviewBackSerializer queryset = models.Review.objects.all() permission_classes = [permissions.IsAuthenticatedOrReadOnly, ] filterset_class = filters.ReviewFilter @@ -16,7 +17,7 @@ class ReviewLstView(generics.ListCreateAPIView): class ReviewRUDView(generics.RetrieveUpdateDestroyAPIView): """Comment RUD view.""" - serializer_class = serializers.ReviewBaseSerializer + serializer_class = ReviewBackSerializer queryset = models.Review.objects.all() permission_classes = [permissions.IsAdminUser | IsReviewerManager | IsRestaurantReviewer] lookup_field = 'id' From 5a3093be5193b79ad120e1e6eeb249491f7119ec Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 28 Nov 2019 15:19:13 +0300 Subject: [PATCH 10/85] content type get api --- apps/main/serializers.py | 9 +++++++++ apps/main/urls/back.py | 1 + apps/main/views/back.py | 17 +++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/apps/main/serializers.py b/apps/main/serializers.py index 572aff31..256333b4 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -1,4 +1,5 @@ """Main app serializers.""" +from django.contrib.contenttypes.models import ContentType from rest_framework import serializers from location.serializers import CountrySerializer @@ -216,3 +217,11 @@ class PageTypeBaseSerializer(serializers.ModelSerializer): 'id', 'name', ] + + +class ContentTypeBackSerializer(serializers.ModelSerializer): + """Serializer fro model ContentType.""" + + class Meta: + model = ContentType + fields = '__all__' diff --git a/apps/main/urls/back.py b/apps/main/urls/back.py index d92bddf8..8424d236 100644 --- a/apps/main/urls/back.py +++ b/apps/main/urls/back.py @@ -8,4 +8,5 @@ app_name = 'main' urlpatterns = [ path('awards/', views.AwardLstView.as_view(), name='awards-list-create'), path('awards//', views.AwardRUDView.as_view(), name='awards-rud'), + path('content_type/', views.ContentTypeView.as_view(), name='content_type-list'), ] diff --git a/apps/main/views/back.py b/apps/main/views/back.py index bbbfad53..5d82d88a 100644 --- a/apps/main/views/back.py +++ b/apps/main/views/back.py @@ -1,3 +1,5 @@ +from django.contrib.contenttypes.models import ContentType +from django_filters.rest_framework import DjangoFilterBackend from rest_framework import generics, permissions from main import serializers @@ -19,3 +21,18 @@ class AwardRUDView(generics.RetrieveUpdateDestroyAPIView): serializer_class = serializers.BackAwardSerializer permission_classes = (permissions.IsAdminUser,) lookup_field = 'id' + + +class ContentTypeView(generics.ListAPIView): + """ContentType list view""" + queryset = ContentType.objects.all() + serializer_class = serializers.ContentTypeBackSerializer + permission_classes = (permissions.IsAdminUser,) + filter_backends = (DjangoFilterBackend, ) + ordering_fields = '__all__' + lookup_field = 'id' + filterset_fields = ( + 'id', + 'model', + 'app_label', + ) From c81dbf2d8eb6fab6e3f0e01d08eca946182016ff Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 28 Nov 2019 15:29:42 +0300 Subject: [PATCH 11/85] main back test --- apps/main/tests/tests_back.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/main/tests/tests_back.py b/apps/main/tests/tests_back.py index e09c7b4b..781eafab 100644 --- a/apps/main/tests/tests_back.py +++ b/apps/main/tests/tests_back.py @@ -9,7 +9,7 @@ from location.models import Country from main.models import Award, AwardType -class AwardTestCase(APITestCase): +class BaseTestCase(APITestCase): def setUp(self): self.user = User.objects.create_user( @@ -25,6 +25,12 @@ class AwardTestCase(APITestCase): {'access_token': tokens.get('access_token'), 'refresh_token': tokens.get('refresh_token')}) + +class AwardTestCase(BaseTestCase): + + def setUp(self): + super().setUp() + self.country_ru = Country.objects.create( name={'en-GB': 'Russian'}, code='RU', @@ -71,3 +77,13 @@ class AwardTestCase(APITestCase): response = self.client.delete(f'/api/back/main/awards/{self.award.id}/') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + + +class ContentTypeTestCase(BaseTestCase): + + def setUp(self): + super().setUp() + + def test_content_type_list(self): + response = self.client.get('/api/back/main/content_type/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) From 2cca32e4cf4380256e22b19806a670a9e3d6a97c Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 28 Nov 2019 15:37:26 +0300 Subject: [PATCH 12/85] mark in review back serializer --- apps/review/serializers/back.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/review/serializers/back.py b/apps/review/serializers/back.py index 674e1edc..ef8f5c8d 100644 --- a/apps/review/serializers/back.py +++ b/apps/review/serializers/back.py @@ -12,6 +12,7 @@ class ReviewBackSerializer(serializers.ModelSerializer): 'reviewer', 'text', 'status', + 'mark', # 'child', 'published_at', 'vintage', From 9c59052e0b0302227ec8f5b8c34bbbc52895cf12 Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 28 Nov 2019 15:55:24 +0300 Subject: [PATCH 13/85] add review priority command start --- apps/review/management/__init__.py | 0 apps/review/management/commands/__init__.py | 0 .../commands/add_review_priority.py | 24 +++++++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 apps/review/management/__init__.py create mode 100644 apps/review/management/commands/__init__.py create mode 100644 apps/review/management/commands/add_review_priority.py diff --git a/apps/review/management/__init__.py b/apps/review/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/review/management/commands/__init__.py b/apps/review/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/review/management/commands/add_review_priority.py b/apps/review/management/commands/add_review_priority.py new file mode 100644 index 00000000..0583efbd --- /dev/null +++ b/apps/review/management/commands/add_review_priority.py @@ -0,0 +1,24 @@ +from django.core.management.base import BaseCommand +from tqdm import tqdm + +from review.models import Review +from transfer.models import Reviews + + +class Command(BaseCommand): + help = '''Add review priority from old db to new db.''' + + def handle(self, *args, **kwargs): + reviews = Review.objects.all().values_list('old_id', flat=True) + queryset = Reviews.objects.exclude(product_id__isnull=False).filter( + id__in=list(reviews), + ).values_list('id', 'priority') + + for old_id, priority in tqdm(queryset, desc='Add priority to reviews'): + review = Review.objects.filter(old_id=old_id).first() + if review: + print(priority) + + self.stdout.write(self.style.WARNING(f'Priority added to review objects.')) + + From 6df9ffb0249a3a0bd1442c29c97d99e6528b6d34 Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 28 Nov 2019 16:56:15 +0300 Subject: [PATCH 14/85] add review priority fin --- .../management/commands/add_review_priority.py | 3 ++- apps/review/migrations/0019_review_priority.py | 18 ++++++++++++++++++ apps/review/models.py | 2 +- apps/review/serializers/back.py | 1 + apps/review/serializers/common.py | 1 + 5 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 apps/review/migrations/0019_review_priority.py diff --git a/apps/review/management/commands/add_review_priority.py b/apps/review/management/commands/add_review_priority.py index 0583efbd..4c43bc52 100644 --- a/apps/review/management/commands/add_review_priority.py +++ b/apps/review/management/commands/add_review_priority.py @@ -17,7 +17,8 @@ class Command(BaseCommand): for old_id, priority in tqdm(queryset, desc='Add priority to reviews'): review = Review.objects.filter(old_id=old_id).first() if review: - print(priority) + review.priority = priority + review.save() self.stdout.write(self.style.WARNING(f'Priority added to review objects.')) diff --git a/apps/review/migrations/0019_review_priority.py b/apps/review/migrations/0019_review_priority.py new file mode 100644 index 00000000..980aa7a2 --- /dev/null +++ b/apps/review/migrations/0019_review_priority.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.7 on 2019-11-28 13:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('review', '0018_auto_20191117_1117'), + ] + + operations = [ + migrations.AddField( + model_name='review', + name='priority', + field=models.PositiveSmallIntegerField(blank=True, default=None, null=True, verbose_name='Priority'), + ), + ] diff --git a/apps/review/models.py b/apps/review/models.py index 8734f4f6..bb344fc5 100644 --- a/apps/review/models.py +++ b/apps/review/models.py @@ -39,7 +39,6 @@ class Review(BaseAttributes, TranslatedFieldsMixin): (TO_REVIEW, _('To review')), (READY, _('Ready')), ) - reviewer = models.ForeignKey( 'account.User', related_name='reviews', @@ -83,6 +82,7 @@ class Review(BaseAttributes, TranslatedFieldsMixin): ) vintage = models.IntegerField(_('Year of review'), validators=[MinValueValidator(1900), MaxValueValidator(2100)]) mark = models.FloatField(verbose_name=_('mark'), blank=True, null=True, default=None) + priority = models.PositiveSmallIntegerField(_('Priority'), blank=True, null=True, default=None) old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None) objects = ReviewQuerySet.as_manager() diff --git a/apps/review/serializers/back.py b/apps/review/serializers/back.py index ef8f5c8d..6c851796 100644 --- a/apps/review/serializers/back.py +++ b/apps/review/serializers/back.py @@ -13,6 +13,7 @@ class ReviewBackSerializer(serializers.ModelSerializer): 'text', 'status', 'mark', + 'priority', # 'child', 'published_at', 'vintage', diff --git a/apps/review/serializers/common.py b/apps/review/serializers/common.py index b1025d34..e714fff7 100644 --- a/apps/review/serializers/common.py +++ b/apps/review/serializers/common.py @@ -10,6 +10,7 @@ class ReviewBaseSerializer(serializers.ModelSerializer): 'id', 'reviewer', 'text', + 'priority', 'status', 'child', 'published_at', From 41611c5b9b28038ab35ae24eaed6b97d58a4421d Mon Sep 17 00:00:00 2001 From: Anatoly Date: Thu, 28 Nov 2019 18:37:12 +0300 Subject: [PATCH 15/85] added sites to back office endpoints --- apps/main/urls/back.py | 4 +++- apps/main/urls/common.py | 4 ++-- apps/main/urls/web.py | 5 +++-- apps/main/views/__init__.py | 4 ++++ 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/main/urls/back.py b/apps/main/urls/back.py index 8424d236..2cb72aef 100644 --- a/apps/main/urls/back.py +++ b/apps/main/urls/back.py @@ -1,7 +1,7 @@ """Back main URLs""" from django.urls import path -from main.views import back as views +from main import views app_name = 'main' @@ -9,4 +9,6 @@ urlpatterns = [ path('awards/', views.AwardLstView.as_view(), name='awards-list-create'), path('awards//', views.AwardRUDView.as_view(), name='awards-rud'), path('content_type/', views.ContentTypeView.as_view(), name='content_type-list'), + path('sites/', views.SiteListView.as_view(), name='site-list'), + path('site-settings//', views.SiteSettingsView.as_view(), name='site-settings'), ] diff --git a/apps/main/urls/common.py b/apps/main/urls/common.py index 964442f9..6b8f26ce 100644 --- a/apps/main/urls/common.py +++ b/apps/main/urls/common.py @@ -1,6 +1,6 @@ """Main app urls.""" from django.urls import path -from main.views.common import * +from main.views import * app = 'main' @@ -8,5 +8,5 @@ common_urlpatterns = [ path('awards/', AwardView.as_view(), name='awards_list'), path('awards//', AwardRetrieveView.as_view(), name='awards_retrieve'), path('carousel/', CarouselListView.as_view(), name='carousel-list'), - path('determine-location/', DetermineLocation.as_view(), name='determine-location') + path('determine-location/', DetermineLocation.as_view(), name='determine-location'), ] diff --git a/apps/main/urls/web.py b/apps/main/urls/web.py index 2126b0c0..50ac9d1f 100644 --- a/apps/main/urls/web.py +++ b/apps/main/urls/web.py @@ -1,11 +1,12 @@ -from main.urls.common import common_urlpatterns from django.urls import path +from main.urls.common import common_urlpatterns from main.views.web import DetermineSiteView, SiteListView, SiteSettingsView urlpatterns = [ path('determine-site/', DetermineSiteView.as_view(), name='determine-site'), path('sites/', SiteListView.as_view(), name='site-list'), - path('site-settings//', SiteSettingsView.as_view(), name='site-settings'), ] + path('site-settings//', SiteSettingsView.as_view(), name='site-settings'), +] urlpatterns.extend(common_urlpatterns) diff --git a/apps/main/views/__init__.py b/apps/main/views/__init__.py index e69de29b..d1a35297 100644 --- a/apps/main/views/__init__.py +++ b/apps/main/views/__init__.py @@ -0,0 +1,4 @@ +from .common import * +from .back import * +from .mobile import * +from .web import * From 5660c20d5076ece1f982d642cd2f483e2e470b32 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Thu, 28 Nov 2019 18:52:57 +0300 Subject: [PATCH 16/85] added id field to site BO serializers --- apps/main/serializers.py | 30 +++++++++++++++++++++++++++--- apps/main/urls/back.py | 5 +++-- apps/main/views/__init__.py | 2 +- apps/main/views/back.py | 11 +++++++++++ 4 files changed, 42 insertions(+), 6 deletions(-) diff --git a/apps/main/serializers.py b/apps/main/serializers.py index 256333b4..410eb6bb 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -72,7 +72,7 @@ class SiteSettingsSerializer(serializers.ModelSerializer): """Meta class.""" model = models.SiteSettings - fields = ( + fields = [ 'country_code', 'time_format', 'subdomain', @@ -86,7 +86,17 @@ class SiteSettingsSerializer(serializers.ModelSerializer): 'published_features', 'currency', 'country_name', - ) + ] + + +class SiteSettingsBackOfficeSerializer(SiteSettingsSerializer): + """Site settings serializer for back office.""" + + class Meta(SiteSettingsSerializer.Meta): + """Meta class.""" + fields = SiteSettingsSerializer.Meta.fields + [ + 'id', + ] class SiteSerializer(serializers.ModelSerializer): @@ -95,7 +105,11 @@ class SiteSerializer(serializers.ModelSerializer): class Meta: """Meta class.""" model = models.SiteSettings - fields = ('subdomain', 'site_url', 'country') + fields = [ + 'subdomain', + 'site_url', + 'country' + ] class SiteShortSerializer(serializers.ModelSerializer): @@ -108,6 +122,16 @@ class SiteShortSerializer(serializers.ModelSerializer): ] +class SiteBackOfficeSerializer(SiteSerializer): + """Serializer for back office.""" + + class Meta(SiteSerializer.Meta): + """Meta class.""" + fields = SiteSerializer.Meta.fields + [ + 'id', + ] + + # class SiteFeatureSerializer(serializers.ModelSerializer): # """Site feature serializer.""" # diff --git a/apps/main/urls/back.py b/apps/main/urls/back.py index 2cb72aef..40011aa2 100644 --- a/apps/main/urls/back.py +++ b/apps/main/urls/back.py @@ -9,6 +9,7 @@ urlpatterns = [ path('awards/', views.AwardLstView.as_view(), name='awards-list-create'), path('awards//', views.AwardRUDView.as_view(), name='awards-rud'), path('content_type/', views.ContentTypeView.as_view(), name='content_type-list'), - path('sites/', views.SiteListView.as_view(), name='site-list'), - path('site-settings//', views.SiteSettingsView.as_view(), name='site-settings'), + path('sites/', views.SiteListBackOfficeView.as_view(), name='site-list'), + path('site-settings//', views.SiteSettingsBackOfficeView.as_view(), + name='site-settings'), ] diff --git a/apps/main/views/__init__.py b/apps/main/views/__init__.py index d1a35297..2c9dae42 100644 --- a/apps/main/views/__init__.py +++ b/apps/main/views/__init__.py @@ -1,4 +1,4 @@ from .common import * -from .back import * from .mobile import * from .web import * +from .back import * diff --git a/apps/main/views/back.py b/apps/main/views/back.py index 5d82d88a..de47825b 100644 --- a/apps/main/views/back.py +++ b/apps/main/views/back.py @@ -5,6 +5,7 @@ from rest_framework import generics, permissions from main import serializers from main.filters import AwardFilter from main.models import Award +from main.views import SiteSettingsView, SiteListView class AwardLstView(generics.ListCreateAPIView): @@ -36,3 +37,13 @@ class ContentTypeView(generics.ListAPIView): 'model', 'app_label', ) + + +class SiteSettingsBackOfficeView(SiteSettingsView): + """Site settings View.""" + serializer_class = serializers.SiteSettingsBackOfficeSerializer + + +class SiteListBackOfficeView(SiteListView): + """Site settings View.""" + serializer_class = serializers.SiteBackOfficeSerializer From 9b02b855d60fd3424c3967cb87e6152e13663689 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 28 Nov 2019 15:47:11 +0300 Subject: [PATCH 17/85] Revert "All facets are global now" This reverts commit 03b7ae6 --- apps/search_indexes/views.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index 7f8520d7..e352f9be 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -34,7 +34,6 @@ class NewsDocumentViewSet(BaseDocumentViewSet): 'field': 'tags.id', 'enabled': True, 'facet': TermsFacet, - 'global': True, 'options': { 'size': utils.FACET_MAX_RESPONSE, }, @@ -112,37 +111,31 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): 'field': 'works_at_weekday', 'facet': TermsFacet, 'enabled': True, - 'global': True, }, 'toque_number': { 'field': 'toque_number', 'enabled': True, 'facet': TermsFacet, - 'global': True, }, 'works_noon': { 'field': 'works_noon', 'facet': TermsFacet, 'enabled': True, - 'global': True, }, 'works_evening': { 'field': 'works_evening', 'facet': TermsFacet, 'enabled': True, - 'global': True, }, 'works_now': { 'field': 'works_now', 'facet': TermsFacet, 'enabled': True, - 'global': True, }, 'tag': { 'field': 'visible_tags.id', 'facet': TermsFacet, 'enabled': True, - 'global': True, 'options': { 'size': utils.FACET_MAX_RESPONSE, }, @@ -151,7 +144,6 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): 'field': 'products.wine_colors.id', 'facet': TermsFacet, 'enabled': True, - 'global': True, 'options': { 'size': utils.FACET_MAX_RESPONSE, }, @@ -160,7 +152,6 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): 'field': 'products.wine_region.id', 'facet': TermsFacet, 'enabled': True, - 'global': True, 'options': { 'size': utils.FACET_MAX_RESPONSE, }, @@ -334,7 +325,6 @@ class ProductDocumentViewSet(BaseDocumentViewSet): 'tag': { 'field': 'wine_colors.id', 'enabled': True, - 'global': True, 'facet': TermsFacet, 'options': { 'size': utils.FACET_MAX_RESPONSE, @@ -343,7 +333,6 @@ class ProductDocumentViewSet(BaseDocumentViewSet): 'wine_region_id': { 'field': 'wine_region.id', 'enabled': True, - 'global': True, 'facet': TermsFacet, 'options': { 'size': utils.FACET_MAX_RESPONSE, From 68626e9fd5d203ba678ca52cac81b167ec61ab36 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 28 Nov 2019 21:24:39 +0300 Subject: [PATCH 18/85] Another agg ES response strategy --- apps/search_indexes/filters.py | 43 +++++++++++++++++++++++++++++++++- apps/search_indexes/views.py | 28 ++++++++++++++++++---- apps/utils/pagination.py | 18 +++++++++++++- 3 files changed, 82 insertions(+), 7 deletions(-) diff --git a/apps/search_indexes/filters.py b/apps/search_indexes/filters.py index ab47ef84..5966d540 100644 --- a/apps/search_indexes/filters.py +++ b/apps/search_indexes/filters.py @@ -1,7 +1,48 @@ """Search indexes filters.""" from elasticsearch_dsl.query import Q -from django_elasticsearch_dsl_drf.filter_backends import SearchFilterBackend +from django_elasticsearch_dsl_drf.filter_backends import SearchFilterBackend, FacetedSearchFilterBackend from search_indexes.utils import OBJECT_FIELD_PROPERTIES +from six import iteritems + + +class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): + + def __init__(self): + self.facets_computed = {} + + def aggregate(self, request, queryset, view): + """Aggregate. + + :param request: + :param queryset: + :param view: + :return: + """ + def makefilter(cur_facet): + def myfilter(x): + return cur_facet['facet']._params['field'] != next(iter(x._params)) + return myfilter + __facets = self.construct_facets(request, view) + for __field, __facet in iteritems(__facets): + agg = __facet['facet'].get_aggregation() + agg_filter = Q('match_all') + if __facet['global']: + queryset.aggs.bucket( + '_filter_' + __field, + 'global' + ).bucket(__field, agg) + else: + qs = queryset._clone() + filterer = makefilter(__facet) + qs.query._proxied._params['must'] = list(filter(filterer, qs.query._proxied._params['must'])) + facet_name = '_filter_' + __field + qs.aggs.bucket( + facet_name, + 'filter', + filter=agg_filter + ).bucket(__field, agg) + self.facets_computed.update({facet_name: qs.execute().aggregations[facet_name]}) + return queryset class CustomSearchFilterBackend(SearchFilterBackend): diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index e352f9be..37ac5928 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -14,7 +14,25 @@ from search_indexes.documents.product import ProductDocument from utils.pagination import ESDocumentPagination -class NewsDocumentViewSet(BaseDocumentViewSet): +class FacetedResponseMixin: + def filter_queryset(self, queryset): + """ + Given a queryset, filter it with whichever filter backend is in use. + + You are unlikely to want to override this method, although you may need + to call it either from a list view, or from a custom `get_object` + method if you want to apply the configured filtering backend to the + default queryset. + """ + for backend in list(self.filter_backends): + bc = backend() + queryset = bc.filter_queryset(self.request, queryset, self) + if hasattr(bc, 'facets_computed'): + setattr(self, 'facets_computed', bc.facets_computed) + return queryset + + +class NewsDocumentViewSet(BaseDocumentViewSet, FacetedResponseMixin): """News document ViewSet.""" document = NewsDocument @@ -26,7 +44,7 @@ class NewsDocumentViewSet(BaseDocumentViewSet): filter_backends = [ filters.CustomSearchFilterBackend, FilteringFilterBackend, - FacetedSearchFilterBackend, + filters.CustomFacetedSearchFilterBackend, ] faceted_search_fields = { @@ -86,7 +104,7 @@ class MobileNewsDocumentViewSet(NewsDocumentViewSet): ] -class EstablishmentDocumentViewSet(BaseDocumentViewSet): +class EstablishmentDocumentViewSet(BaseDocumentViewSet, FacetedResponseMixin): """Establishment document ViewSet.""" document = EstablishmentDocument @@ -103,7 +121,7 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): FilteringFilterBackend, filters.CustomSearchFilterBackend, GeoSpatialFilteringFilterBackend, - FacetedSearchFilterBackend, + filters.CustomFacetedSearchFilterBackend, ] faceted_search_fields = { @@ -306,7 +324,7 @@ class ProductDocumentViewSet(BaseDocumentViewSet): filter_backends = [ FilteringFilterBackend, filters.CustomSearchFilterBackend, - FacetedSearchFilterBackend, + filters.CustomFacetedSearchFilterBackend, ] search_fields = { diff --git a/apps/utils/pagination.py b/apps/utils/pagination.py index ed5ce89e..199d55b6 100644 --- a/apps/utils/pagination.py +++ b/apps/utils/pagination.py @@ -6,7 +6,6 @@ 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.""" @@ -65,6 +64,23 @@ class ESDocumentPagination(ESPagination): return None return self.page.previous_page_number() + def get_facets(self, page=None): + """Get facets. + + :param page: + :return: + """ + if page is None: + page = self.page + + if hasattr(self, 'facets_computed'): + ret = {} + for filter_field, bucket_data in self.facets_computed.items(): + ret.update({filter_field: bucket_data.__dict__['_d_']}) + return ret + elif hasattr(page, 'facets') and hasattr(page.facets, '_d_'): + return page.facets._d_ + class EstablishmentPortionPagination(ProjectMobilePagination): """ From 9cf82f03fe582103e24f50af6b81f75673906f4b Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 28 Nov 2019 21:31:12 +0300 Subject: [PATCH 19/85] Another agg ES response strategy #2 --- apps/search_indexes/views.py | 47 ++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index 37ac5928..4d97a70e 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -4,7 +4,6 @@ from django_elasticsearch_dsl_drf import constants from django_elasticsearch_dsl_drf.filter_backends import ( FilteringFilterBackend, GeoSpatialFilteringFilterBackend, - FacetedSearchFilterBackend, ) from elasticsearch_dsl import TermsFacet from django_elasticsearch_dsl_drf.viewsets import BaseDocumentViewSet @@ -14,25 +13,7 @@ from search_indexes.documents.product import ProductDocument from utils.pagination import ESDocumentPagination -class FacetedResponseMixin: - def filter_queryset(self, queryset): - """ - Given a queryset, filter it with whichever filter backend is in use. - - You are unlikely to want to override this method, although you may need - to call it either from a list view, or from a custom `get_object` - method if you want to apply the configured filtering backend to the - default queryset. - """ - for backend in list(self.filter_backends): - bc = backend() - queryset = bc.filter_queryset(self.request, queryset, self) - if hasattr(bc, 'facets_computed'): - setattr(self, 'facets_computed', bc.facets_computed) - return queryset - - -class NewsDocumentViewSet(BaseDocumentViewSet, FacetedResponseMixin): +class NewsDocumentViewSet(BaseDocumentViewSet): """News document ViewSet.""" document = NewsDocument @@ -95,6 +76,14 @@ class NewsDocumentViewSet(BaseDocumentViewSet, FacetedResponseMixin): }, } + def filter_queryset(self, queryset): + for backend in list(self.filter_backends): + bc = backend() + queryset = bc.filter_queryset(self.request, queryset, self) + if hasattr(bc, 'facets_computed'): + setattr(self.paginator, 'facets_computed', bc.facets_computed) + return queryset + class MobileNewsDocumentViewSet(NewsDocumentViewSet): @@ -104,7 +93,7 @@ class MobileNewsDocumentViewSet(NewsDocumentViewSet): ] -class EstablishmentDocumentViewSet(BaseDocumentViewSet, FacetedResponseMixin): +class EstablishmentDocumentViewSet(BaseDocumentViewSet): """Establishment document ViewSet.""" document = EstablishmentDocument @@ -303,6 +292,14 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet, FacetedResponseMixin): } } + def filter_queryset(self, queryset): + for backend in list(self.filter_backends): + bc = backend() + queryset = bc.filter_queryset(self.request, queryset, self) + if hasattr(bc, 'facets_computed'): + setattr(self.paginator, 'facets_computed', bc.facets_computed) + return queryset + class MobileEstablishmentDocumentViewSet(EstablishmentDocumentViewSet): @@ -417,6 +414,14 @@ class ProductDocumentViewSet(BaseDocumentViewSet): }, } + def filter_queryset(self, queryset): + for backend in list(self.filter_backends): + bc = backend() + queryset = bc.filter_queryset(self.request, queryset, self) + if hasattr(bc, 'facets_computed'): + setattr(self.paginator, 'facets_computed', bc.facets_computed) + return queryset + class MobileProductDocumentViewSet(ProductDocumentViewSet): From 02cacc0ffeca87408bcbcc68ce7462e82352718f Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 28 Nov 2019 22:38:26 +0300 Subject: [PATCH 20/85] Fix issue with immutable instances --- apps/search_indexes/filters.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/search_indexes/filters.py b/apps/search_indexes/filters.py index 5966d540..a2a1bd08 100644 --- a/apps/search_indexes/filters.py +++ b/apps/search_indexes/filters.py @@ -32,7 +32,8 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): 'global' ).bucket(__field, agg) else: - qs = queryset._clone() + qs = queryset.__copy__() + qs.query = queryset.query._clone() filterer = makefilter(__facet) qs.query._proxied._params['must'] = list(filter(filterer, qs.query._proxied._params['must'])) facet_name = '_filter_' + __field From e924b359c89f69fc27a0ad408274b28aa9675abb Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 28 Nov 2019 22:55:52 +0300 Subject: [PATCH 21/85] Fix issue w/ tags --- 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 4d97a70e..e2404fbb 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -140,7 +140,7 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): 'enabled': True, }, 'tag': { - 'field': 'visible_tags.id', + 'field': 'tags.id', 'facet': TermsFacet, 'enabled': True, 'options': { From 846176131eab22976b99bbf47031c7a993f66bd2 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 28 Nov 2019 23:33:09 +0300 Subject: [PATCH 22/85] Fix issue w/ tags #2 --- apps/search_indexes/filters.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/search_indexes/filters.py b/apps/search_indexes/filters.py index a2a1bd08..229bbff7 100644 --- a/apps/search_indexes/filters.py +++ b/apps/search_indexes/filters.py @@ -35,7 +35,12 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): qs = queryset.__copy__() qs.query = queryset.query._clone() filterer = makefilter(__facet) - qs.query._proxied._params['must'] = list(filter(filterer, qs.query._proxied._params['must'])) + for param_type in ['must', 'must_not', 'should']: + qs.query._proxied._params[param_type] = list( + filter( + filterer, qs.query._proxied._params[param_type] + ) + ) facet_name = '_filter_' + __field qs.aggs.bucket( facet_name, From 678377c234193b5f5dca82606d5a49930fbd0a35 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 28 Nov 2019 23:43:18 +0300 Subject: [PATCH 23/85] Fix issue w/ tags #3 --- apps/search_indexes/filters.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/search_indexes/filters.py b/apps/search_indexes/filters.py index 229bbff7..c1f8a578 100644 --- a/apps/search_indexes/filters.py +++ b/apps/search_indexes/filters.py @@ -41,6 +41,9 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): filterer, qs.query._proxied._params[param_type] ) ) + if not len(qs.query._proxied._params['should']) \ + and qs.query._proxied._params.get('minimum_should_match'): + qs.query._proxied._params.pop('minimum_should_match') facet_name = '_filter_' + __field qs.aggs.bucket( facet_name, From 4f6cbb768425359b785b9b433ed8796e1725f23c Mon Sep 17 00:00:00 2001 From: alex Date: Fri, 29 Nov 2019 09:55:06 +0300 Subject: [PATCH 24/85] ReviewBackSerializer --- .../commands/add_review_priority.py | 2 -- apps/review/serializers/back.py | 33 +++++++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/apps/review/management/commands/add_review_priority.py b/apps/review/management/commands/add_review_priority.py index 4c43bc52..97525cf9 100644 --- a/apps/review/management/commands/add_review_priority.py +++ b/apps/review/management/commands/add_review_priority.py @@ -21,5 +21,3 @@ class Command(BaseCommand): review.save() self.stdout.write(self.style.WARNING(f'Priority added to review objects.')) - - diff --git a/apps/review/serializers/back.py b/apps/review/serializers/back.py index 6c851796..05a5d702 100644 --- a/apps/review/serializers/back.py +++ b/apps/review/serializers/back.py @@ -1,15 +1,43 @@ """Review app back serializers.""" +from django.contrib.contenttypes.models import ContentType from rest_framework import serializers -from review import models +from account.models import User +from review.models import Review + + +class _ReviewerSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ( + 'id', + 'username', + 'first_name', + 'last_name', + 'email', + ) + + +class _ContentTypeSerializer(serializers.ModelSerializer): + class Meta: + model = ContentType + fields = ( + 'id', + 'app_label', + 'model', + ) class ReviewBackSerializer(serializers.ModelSerializer): + reviewer_data = _ReviewerSerializer(read_only=True, source='reviewer') + content_type_data = _ContentTypeSerializer(read_only=True, source='content_type') + class Meta: - model = models.Review + model = Review fields = ( 'id', 'reviewer', + 'reviewer_data', 'text', 'status', 'mark', @@ -19,5 +47,6 @@ class ReviewBackSerializer(serializers.ModelSerializer): 'vintage', # 'country', 'content_type', + 'content_type_data', 'object_id', ) From c769049160182e63637e43913fc12a736cc061ab Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 29 Nov 2019 13:01:29 +0300 Subject: [PATCH 25/85] Email layout. Redirect to main from logo --- project/templates/account/change_email.html | 2 +- project/templates/account/password_change_email.html | 2 +- project/templates/account/password_reset_email.html | 2 +- project/templates/authorization/confirm_email.html | 2 +- project/templates/news/news_email.html | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/project/templates/account/change_email.html b/project/templates/account/change_email.html index efe92766..9f412a8f 100644 --- a/project/templates/account/change_email.html +++ b/project/templates/account/change_email.html @@ -19,7 +19,7 @@
diff --git a/project/templates/account/password_change_email.html b/project/templates/account/password_change_email.html index 77cad83f..d82eb967 100644 --- a/project/templates/account/password_change_email.html +++ b/project/templates/account/password_change_email.html @@ -19,7 +19,7 @@
diff --git a/project/templates/account/password_reset_email.html b/project/templates/account/password_reset_email.html index 4d61147d..c290c3c6 100644 --- a/project/templates/account/password_reset_email.html +++ b/project/templates/account/password_reset_email.html @@ -19,7 +19,7 @@
diff --git a/project/templates/authorization/confirm_email.html b/project/templates/authorization/confirm_email.html index c05c85b0..8b1332d0 100644 --- a/project/templates/authorization/confirm_email.html +++ b/project/templates/authorization/confirm_email.html @@ -19,7 +19,7 @@
diff --git a/project/templates/news/news_email.html b/project/templates/news/news_email.html index c2ae227c..27b8086c 100644 --- a/project/templates/news/news_email.html +++ b/project/templates/news/news_email.html @@ -18,7 +18,7 @@
From 1a57d0edd830e7c16d9301641111bd61ff6a1e65 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 29 Nov 2019 13:46:20 +0300 Subject: [PATCH 26/85] Paginator custom facets code optimization --- apps/search_indexes/filters.py | 3 ++- apps/search_indexes/views.py | 28 ++++------------------------ 2 files changed, 6 insertions(+), 25 deletions(-) diff --git a/apps/search_indexes/filters.py b/apps/search_indexes/filters.py index c1f8a578..c85b1ad0 100644 --- a/apps/search_indexes/filters.py +++ b/apps/search_indexes/filters.py @@ -23,6 +23,7 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): return cur_facet['facet']._params['field'] != next(iter(x._params)) return myfilter __facets = self.construct_facets(request, view) + setattr(view.paginator, 'facets_computed', {}) for __field, __facet in iteritems(__facets): agg = __facet['facet'].get_aggregation() agg_filter = Q('match_all') @@ -50,7 +51,7 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): 'filter', filter=agg_filter ).bucket(__field, agg) - self.facets_computed.update({facet_name: qs.execute().aggregations[facet_name]}) + view.paginator.facets_computed.update({facet_name: qs.execute().aggregations[facet_name]}) return queryset diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index e2404fbb..74cd4649 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -4,6 +4,7 @@ from django_elasticsearch_dsl_drf import constants from django_elasticsearch_dsl_drf.filter_backends import ( FilteringFilterBackend, GeoSpatialFilteringFilterBackend, + GeoSpatialOrderingFilterBackend, ) from elasticsearch_dsl import TermsFacet from django_elasticsearch_dsl_drf.viewsets import BaseDocumentViewSet @@ -26,6 +27,7 @@ class NewsDocumentViewSet(BaseDocumentViewSet): filters.CustomSearchFilterBackend, FilteringFilterBackend, filters.CustomFacetedSearchFilterBackend, + GeoSpatialOrderingFilterBackend, ] faceted_search_fields = { @@ -76,14 +78,6 @@ class NewsDocumentViewSet(BaseDocumentViewSet): }, } - def filter_queryset(self, queryset): - for backend in list(self.filter_backends): - bc = backend() - queryset = bc.filter_queryset(self.request, queryset, self) - if hasattr(bc, 'facets_computed'): - setattr(self.paginator, 'facets_computed', bc.facets_computed) - return queryset - class MobileNewsDocumentViewSet(NewsDocumentViewSet): @@ -111,6 +105,7 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): filters.CustomSearchFilterBackend, GeoSpatialFilteringFilterBackend, filters.CustomFacetedSearchFilterBackend, + GeoSpatialOrderingFilterBackend, ] faceted_search_fields = { @@ -292,14 +287,6 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): } } - def filter_queryset(self, queryset): - for backend in list(self.filter_backends): - bc = backend() - queryset = bc.filter_queryset(self.request, queryset, self) - if hasattr(bc, 'facets_computed'): - setattr(self.paginator, 'facets_computed', bc.facets_computed) - return queryset - class MobileEstablishmentDocumentViewSet(EstablishmentDocumentViewSet): @@ -322,6 +309,7 @@ class ProductDocumentViewSet(BaseDocumentViewSet): FilteringFilterBackend, filters.CustomSearchFilterBackend, filters.CustomFacetedSearchFilterBackend, + GeoSpatialOrderingFilterBackend, ] search_fields = { @@ -414,14 +402,6 @@ class ProductDocumentViewSet(BaseDocumentViewSet): }, } - def filter_queryset(self, queryset): - for backend in list(self.filter_backends): - bc = backend() - queryset = bc.filter_queryset(self.request, queryset, self) - if hasattr(bc, 'facets_computed'): - setattr(self.paginator, 'facets_computed', bc.facets_computed) - return queryset - class MobileProductDocumentViewSet(ProductDocumentViewSet): From 376f815c97719d32b9c3e5decc825ed7287b057a Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 29 Nov 2019 14:37:10 +0300 Subject: [PATCH 27/85] Order within bounding box from center --- apps/search_indexes/filters.py | 23 ++++++++++++++++++++++- apps/search_indexes/views.py | 10 ++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/apps/search_indexes/filters.py b/apps/search_indexes/filters.py index c85b1ad0..47af1698 100644 --- a/apps/search_indexes/filters.py +++ b/apps/search_indexes/filters.py @@ -1,10 +1,31 @@ """Search indexes filters.""" from elasticsearch_dsl.query import Q -from django_elasticsearch_dsl_drf.filter_backends import SearchFilterBackend, FacetedSearchFilterBackend +from django_elasticsearch_dsl_drf.filter_backends import SearchFilterBackend, \ + FacetedSearchFilterBackend, GeoSpatialFilteringFilterBackend from search_indexes.utils import OBJECT_FIELD_PROPERTIES from six import iteritems +class CustomGeoSpatialFilteringFilterBackend(GeoSpatialFilteringFilterBackend): + """Automatically adds centering and sorting within bounding box.""" + + @staticmethod + def calculate_center(a, b): + return (a[0] + b[0]) / 2, (a[1] + b[1]) / 2 + + def filter_queryset(self, request, queryset, view): + ret = super().filter_queryset(request, queryset, view) + bb = request.query_params.get('location__geo_bounding_box') + if bb: + center = self.calculate_center(*map(lambda p: list(map(lambda x: float(x),p.split(','))), bb.split('__'))) + request.GET._mutable = True + request.query_params.update({ + 'ordering': f'location__{center[0]}__{center[1]}__km' + }) + request.GET._mutable = False + return ret + + class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): def __init__(self): diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index 74cd4649..7f262762 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -103,7 +103,7 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): filter_backends = [ FilteringFilterBackend, filters.CustomSearchFilterBackend, - GeoSpatialFilteringFilterBackend, + filters.CustomGeoSpatialFilteringFilterBackend, filters.CustomFacetedSearchFilterBackend, GeoSpatialOrderingFilterBackend, ] @@ -287,6 +287,12 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): } } + geo_spatial_ordering_fields = { + 'location': { + 'field': 'address.coordinates', + }, + } + class MobileEstablishmentDocumentViewSet(EstablishmentDocumentViewSet): @@ -309,7 +315,7 @@ class ProductDocumentViewSet(BaseDocumentViewSet): FilteringFilterBackend, filters.CustomSearchFilterBackend, filters.CustomFacetedSearchFilterBackend, - GeoSpatialOrderingFilterBackend, + # GeoSpatialOrderingFilterBackend, ] search_fields = { From aebdb0fb20fc312f7613902aee0103aac093c55a Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 29 Nov 2019 15:50:42 +0300 Subject: [PATCH 28/85] Add id field to api/web/main/site-settings/{domain} --- apps/main/views/web.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/main/views/web.py b/apps/main/views/web.py index 3a634457..86c550da 100644 --- a/apps/main/views/web.py +++ b/apps/main/views/web.py @@ -25,7 +25,7 @@ class SiteSettingsView(generics.RetrieveAPIView): lookup_field = 'subdomain' permission_classes = (permissions.AllowAny,) queryset = models.SiteSettings.objects.all() - serializer_class = serializers.SiteSettingsSerializer + serializer_class = serializers.SiteSettingsBackOfficeSerializer class SiteListView(generics.ListAPIView): From a102b68173e930aa5891f91dac559a6699317938 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 29 Nov 2019 16:22:02 +0300 Subject: [PATCH 29/85] News start field is nullable now --- .../news/migrations/0037_auto_20191129_1320.py | 18 ++++++++++++++++++ apps/news/models.py | 3 ++- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 apps/news/migrations/0037_auto_20191129_1320.py diff --git a/apps/news/migrations/0037_auto_20191129_1320.py b/apps/news/migrations/0037_auto_20191129_1320.py new file mode 100644 index 00000000..91c9a898 --- /dev/null +++ b/apps/news/migrations/0037_auto_20191129_1320.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.7 on 2019-11-29 13:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0036_news_site'), + ] + + operations = [ + migrations.AlterField( + model_name='news', + name='start', + field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='Start'), + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index 2aa571c0..30e4206b 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -174,7 +174,8 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi description = TJSONField(blank=True, null=True, default=None, verbose_name=_('description'), help_text='{"en-GB":"some text"}') - start = models.DateTimeField(verbose_name=_('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')) slug = models.SlugField(unique=True, max_length=255, From 4e3a990ac60a8035557e761e910eb9421a7f3cf6 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 29 Nov 2019 16:44:24 +0300 Subject: [PATCH 30/85] added command to fix existed collections --- .../management/commands/fix_collection.py | 32 +++++++++++++++++++ .../management/commands/import_collection.py | 7 ++-- 2 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 apps/collection/management/commands/fix_collection.py diff --git a/apps/collection/management/commands/fix_collection.py b/apps/collection/management/commands/fix_collection.py new file mode 100644 index 00000000..9162f8f7 --- /dev/null +++ b/apps/collection/management/commands/fix_collection.py @@ -0,0 +1,32 @@ +from django.conf import settings +from django.core.management.base import BaseCommand +from tqdm import tqdm + +from collection.models import Collection + + +class Command(BaseCommand): + help = """Fix existed collections.""" + + def handle(self, *args, **kwarg): + update_collections = [] + collections = Collection.objects.values_list('id', 'collection_type', 'description') + for id, collection_type, description in tqdm(collections): + collection = Collection.objects.get(id=id) + description = collection.description + collection_updated = False + + if isinstance(description, str): + if description.lower().find('pop') != -1: + collection.collection_type = Collection.POP + collection_updated = True + + if not isinstance(description, dict): + collection.description = {settings.FALLBACK_LOCALE: collection.description} + collection_updated = True + + if collection_updated: + update_collections.append(collection) + + Collection.objects.bulk_update(update_collections, ['collection_type', 'description', ]) + self.stdout.write(self.style.WARNING(f'Updated products: {len(update_collections)}')) diff --git a/apps/collection/management/commands/import_collection.py b/apps/collection/management/commands/import_collection.py index 67d1cacf..f8f1702e 100644 --- a/apps/collection/management/commands/import_collection.py +++ b/apps/collection/management/commands/import_collection.py @@ -3,6 +3,7 @@ from establishment.models import Establishment from location.models import Country, Language from transfer.models import Collections from collection.models import Collection +from django.conf import settings from news.models import News @@ -93,9 +94,11 @@ class Command(BaseCommand): country = Country.objects.filter(code=obj['country_code']).first() if country: objects.append( - Collection(name={"en-GB": obj['title']}, collection_type=Collection.ORDINARY, + Collection(name={settings.FALLBACK_LOCALE: obj['title']}, + collection_type=Collection.POP if obj['description'].lower().find('pop') != -1 + else Collection.ORDINARY, country=country, - description=obj['description'], + description={settings.FALLBACK_LOCALE: obj['description']}, slug=obj['slug'], old_id=obj['collection_id'], start=obj['start'], image_url='https://s3.eu-central-1.amazonaws.com/gm-test.com/media/'+obj['attachment_suffix_url'] From 530b7e2ab61b337cf11e59a25441c05a550358f7 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 29 Nov 2019 18:31:10 +0300 Subject: [PATCH 31/85] Fix search aggs --- apps/search_indexes/filters.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/search_indexes/filters.py b/apps/search_indexes/filters.py index 47af1698..ec45350c 100644 --- a/apps/search_indexes/filters.py +++ b/apps/search_indexes/filters.py @@ -58,12 +58,14 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): qs.query = queryset.query._clone() filterer = makefilter(__facet) for param_type in ['must', 'must_not', 'should']: - qs.query._proxied._params[param_type] = list( - filter( - filterer, qs.query._proxied._params[param_type] + if qs.query._proxied._params.get(param_type): + qs.query._proxied._params[param_type] = list( + filter( + filterer, qs.query._proxied._params[param_type] + ) ) - ) - if not len(qs.query._proxied._params['should']) \ + sh = qs.query._proxied._params.get('should') + if (not sh or not len(sh)) \ and qs.query._proxied._params.get('minimum_should_match'): qs.query._proxied._params.pop('minimum_should_match') facet_name = '_filter_' + __field From a9d45ab7bbeb16e6f7413e5e8588b7d1e10f324a Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 29 Nov 2019 18:43:41 +0300 Subject: [PATCH 32/85] Start news field s timefield --- apps/search_indexes/documents/news.py | 3 +-- apps/search_indexes/serializers.py | 1 + apps/search_indexes/views.py | 9 ++++++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/search_indexes/documents/news.py b/apps/search_indexes/documents/news.py index 1fa2f9d9..3c87e680 100644 --- a/apps/search_indexes/documents/news.py +++ b/apps/search_indexes/documents/news.py @@ -42,13 +42,12 @@ class NewsDocument(Document): }, multi=True) favorites_for_users = fields.ListField(field=fields.IntegerField()) - + start = fields.DateField(attr='start') class Django: model = models.News fields = ( 'id', - 'start', 'end', 'slug', 'state', diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 84782f0c..e5a249a7 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -206,6 +206,7 @@ class NewsDocumentSerializer(InFavoritesMixin, DocumentSerializer): 'preview_image_url', 'news_type', 'tags', + 'start', 'slug', ) diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index 7f262762..1f04a9a9 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -5,6 +5,7 @@ from django_elasticsearch_dsl_drf.filter_backends import ( FilteringFilterBackend, GeoSpatialFilteringFilterBackend, GeoSpatialOrderingFilterBackend, + OrderingFilterBackend, ) from elasticsearch_dsl import TermsFacet from django_elasticsearch_dsl_drf.viewsets import BaseDocumentViewSet @@ -27,9 +28,15 @@ class NewsDocumentViewSet(BaseDocumentViewSet): filters.CustomSearchFilterBackend, FilteringFilterBackend, filters.CustomFacetedSearchFilterBackend, - GeoSpatialOrderingFilterBackend, + OrderingFilterBackend ] + ordering_fields = { + 'start': { + 'field': 'start', + }, + } + faceted_search_fields = { 'tag': { 'field': 'tags.id', From 20b3e7f4616ad57d8600ff7d349904d53a480621 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 2 Dec 2019 12:07:24 +0300 Subject: [PATCH 33/85] status display field --- apps/review/serializers/back.py | 2 ++ apps/review/views/back.py | 18 ++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/apps/review/serializers/back.py b/apps/review/serializers/back.py index 05a5d702..75df94e2 100644 --- a/apps/review/serializers/back.py +++ b/apps/review/serializers/back.py @@ -31,6 +31,7 @@ class _ContentTypeSerializer(serializers.ModelSerializer): class ReviewBackSerializer(serializers.ModelSerializer): reviewer_data = _ReviewerSerializer(read_only=True, source='reviewer') content_type_data = _ContentTypeSerializer(read_only=True, source='content_type') + status_display = serializers.CharField(read_only=True, source='get_status_display') class Meta: model = Review @@ -40,6 +41,7 @@ class ReviewBackSerializer(serializers.ModelSerializer): 'reviewer_data', 'text', 'status', + 'status_display', 'mark', 'priority', # 'child', diff --git a/apps/review/views/back.py b/apps/review/views/back.py index 511c91f9..caf12b62 100644 --- a/apps/review/views/back.py +++ b/apps/review/views/back.py @@ -8,7 +8,14 @@ from review.serializers.back import ReviewBackSerializer class ReviewLstView(generics.ListCreateAPIView): - """Comment list create view.""" + """Review list create view. + + status values: + + TO_INVESTIGATE = 0 + TO_REVIEW = 1 + READY = 2 + """ serializer_class = ReviewBackSerializer queryset = models.Review.objects.all() permission_classes = [permissions.IsAuthenticatedOrReadOnly, ] @@ -16,7 +23,14 @@ class ReviewLstView(generics.ListCreateAPIView): class ReviewRUDView(generics.RetrieveUpdateDestroyAPIView): - """Comment RUD view.""" + """Review RUD view. + + status values: + + TO_INVESTIGATE = 0 + TO_REVIEW = 1 + READY = 2 + """ serializer_class = ReviewBackSerializer queryset = models.Review.objects.all() permission_classes = [permissions.IsAdminUser | IsReviewerManager | IsRestaurantReviewer] From fccffa93e36e3c47fec834fe32748520c31d0420 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 2 Dec 2019 12:45:58 +0300 Subject: [PATCH 34/85] Order news by -start --- apps/news/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/news/views.py b/apps/news/views.py index 6573b5d5..a4a5c33a 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -21,7 +21,7 @@ class NewsMixinView: qs = models.News.objects.published() \ .with_base_related() \ .annotate_in_favorites(self.request.user) \ - .order_by('-is_highlighted', '-created') + .order_by('-is_highlighted', '-start') country_code = self.request.country_code if country_code: From 5b11b6b8fcf11e325543472b33fecb0ed00b7e47 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 2 Dec 2019 13:52:53 +0300 Subject: [PATCH 35/85] Add created to products search results --- apps/search_indexes/documents/product.py | 1 + apps/search_indexes/serializers.py | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/search_indexes/documents/product.py b/apps/search_indexes/documents/product.py index 61c4eeeb..ad99e178 100644 --- a/apps/search_indexes/documents/product.py +++ b/apps/search_indexes/documents/product.py @@ -149,6 +149,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='create') # publishing date (?) class Django: model = models.Product diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index e5a249a7..7f1999a2 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -290,4 +290,5 @@ class ProductDocumentSerializer(InFavoritesMixin, DocumentSerializer): 'grape_variety', 'establishment_detail', 'average_price', + 'created', ) From c492ec530b8c352fb589c16b2fd46ca6f9faeaf2 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 2 Dec 2019 13:59:03 +0300 Subject: [PATCH 36/85] Fix typo --- apps/search_indexes/documents/product.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/search_indexes/documents/product.py b/apps/search_indexes/documents/product.py index ad99e178..853f72a2 100644 --- a/apps/search_indexes/documents/product.py +++ b/apps/search_indexes/documents/product.py @@ -149,7 +149,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='create') # publishing date (?) + created = fields.DateField(attr='created') # publishing date (?) class Django: model = models.Product From b868efb137a910774e68e6187a91fc817ab5b9b4 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 2 Dec 2019 14:28:01 +0300 Subject: [PATCH 37/85] order bu created --- apps/search_indexes/views.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index 1f04a9a9..a5b952d7 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -322,9 +322,16 @@ class ProductDocumentViewSet(BaseDocumentViewSet): FilteringFilterBackend, filters.CustomSearchFilterBackend, filters.CustomFacetedSearchFilterBackend, + OrderingFilterBackend, # GeoSpatialOrderingFilterBackend, ] + ordering_fields = { + 'created': { + 'field': 'created', + }, + } + search_fields = { 'name': {'fuzziness': 'auto:2,5', 'boost': 8}, From 5ee7c314c0c961777e1dcbfe959e30cd79239ad1 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 2 Dec 2019 14:31:24 +0300 Subject: [PATCH 38/85] Fix product document serialization --- apps/search_indexes/serializers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 7f1999a2..b45bd2e3 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -135,6 +135,9 @@ class ProductEstablishmentDocumentSerializer(serializers.Serializer): 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.""" From cbe6a25d79bb463c4c5d8a3c3eddd3a6aaeeacbb Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 2 Dec 2019 14:42:33 +0300 Subject: [PATCH 39/85] Fix booking --- apps/booking/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/booking/views.py b/apps/booking/views.py index c2a143a6..4dd76c65 100644 --- a/apps/booking/views.py +++ b/apps/booking/views.py @@ -34,7 +34,7 @@ class CheckWhetherBookingAvailable(generics.GenericAPIView): periods = response['periods'] periods_by_name = {period['period']: period for period in periods if 'period' in period} if not periods_by_name: - raise ValueError('Empty guestonline response') + return None period_template = iter(periods_by_name.values()).__next__().copy() period_template.pop('total_left_seats') From b3d01c8887eeda55527a407c435ef438d71c4feb Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 2 Dec 2019 15:06:43 +0300 Subject: [PATCH 40/85] Fix booking #2 --- apps/booking/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/booking/views.py b/apps/booking/views.py index 4dd76c65..06ef9273 100644 --- a/apps/booking/views.py +++ b/apps/booking/views.py @@ -86,6 +86,8 @@ class CheckWhetherBookingAvailable(generics.GenericAPIView): if establishment.guestonline_id is not None \ else service.response if service else None response.update({'details': service_response}) + if service_response is None: + response['available'] = False return Response(data=response, status=200) From 6d9686b64a96f9ffe52fa40ef55afe576ea063a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Mon, 2 Dec 2019 15:12:17 +0300 Subject: [PATCH 41/85] Fix error --- apps/account/models.py | 4 +++- apps/account/serializers/back.py | 19 ++++++++++--------- apps/account/views/back.py | 2 +- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/apps/account/models.py b/apps/account/models.py index c212ffda..956baa56 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -287,7 +287,9 @@ class User(AbstractUser): class UserRole(ProjectBaseMixin): """UserRole model.""" - user = models.ForeignKey(User, verbose_name=_('User'), on_delete=models.CASCADE) + 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) diff --git a/apps/account/serializers/back.py b/apps/account/serializers/back.py index 57c3fb42..b2316734 100644 --- a/apps/account/serializers/back.py +++ b/apps/account/serializers/back.py @@ -13,15 +13,6 @@ class RoleSerializer(serializers.ModelSerializer): ] -class UserRoleSerializer(serializers.ModelSerializer): - class Meta: - model = models.UserRole - fields = [ - 'user', - 'role' - ] - - class BackUserSerializer(serializers.ModelSerializer): class Meta: model = User @@ -49,3 +40,13 @@ class BackDetailUserSerializer(BackUserSerializer): user.set_password(validated_data['password']) user.save() return user + + +class UserRoleSerializer(serializers.ModelSerializer): + class Meta: + model = models.UserRole + fields = [ + 'role', + 'user', + 'establishment' + ] diff --git a/apps/account/views/back.py b/apps/account/views/back.py index b3d77d1e..80775b3a 100644 --- a/apps/account/views/back.py +++ b/apps/account/views/back.py @@ -13,7 +13,7 @@ class RoleLstView(generics.ListCreateAPIView): class UserRoleLstView(generics.ListCreateAPIView): serializer_class = serializers.UserRoleSerializer - queryset = models.Role.objects.all() + queryset = models.UserRole.objects.all() class UserLstView(generics.ListCreateAPIView): From a72deaec37916ed9ea462fe74075ea9a645885c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Mon, 2 Dec 2019 17:24:51 +0300 Subject: [PATCH 42/85] Site site settings --- apps/main/serializers.py | 9 ++++++++- apps/main/urls/back.py | 2 +- apps/main/views/back.py | 5 +++++ apps/main/views/web.py | 4 ++-- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/apps/main/serializers.py b/apps/main/serializers.py index 410eb6bb..c0bf14ad 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -108,7 +108,14 @@ class SiteSerializer(serializers.ModelSerializer): fields = [ 'subdomain', 'site_url', - 'country' + 'country', + 'default_site', + 'pinterest_page_url', + 'twitter_page_url', + 'facebook_page_url', + 'instagram_page_url', + 'contact_email', + 'currency' ] diff --git a/apps/main/urls/back.py b/apps/main/urls/back.py index 40011aa2..25347434 100644 --- a/apps/main/urls/back.py +++ b/apps/main/urls/back.py @@ -9,7 +9,7 @@ urlpatterns = [ path('awards/', views.AwardLstView.as_view(), name='awards-list-create'), path('awards//', views.AwardRUDView.as_view(), name='awards-rud'), path('content_type/', views.ContentTypeView.as_view(), name='content_type-list'), - path('sites/', views.SiteListBackOfficeView.as_view(), name='site-list'), + path('sites/', views.SiteListBackOfficeView.as_view(), name='site-list-create'), path('site-settings//', views.SiteSettingsBackOfficeView.as_view(), name='site-settings'), ] diff --git a/apps/main/views/back.py b/apps/main/views/back.py index de47825b..95ac3b24 100644 --- a/apps/main/views/back.py +++ b/apps/main/views/back.py @@ -44,6 +44,11 @@ class SiteSettingsBackOfficeView(SiteSettingsView): serializer_class = serializers.SiteSettingsBackOfficeSerializer +# class SiteSettingsBackRUDView(generics.RetrieveUpdateDestroyAPIView): +# """Site settings RUD View.""" +# serializer_class = serializers.SiteSettingsBackOfficeSerializer + + class SiteListBackOfficeView(SiteListView): """Site settings View.""" serializer_class = serializers.SiteBackOfficeSerializer diff --git a/apps/main/views/web.py b/apps/main/views/web.py index 86c550da..12e23649 100644 --- a/apps/main/views/web.py +++ b/apps/main/views/web.py @@ -19,7 +19,7 @@ class DetermineSiteView(generics.GenericAPIView): return Response(data={'url': url}) -class SiteSettingsView(generics.RetrieveAPIView): +class SiteSettingsView(generics.RetrieveUpdateDestroyAPIView): """Site settings View.""" lookup_field = 'subdomain' @@ -28,7 +28,7 @@ class SiteSettingsView(generics.RetrieveAPIView): serializer_class = serializers.SiteSettingsBackOfficeSerializer -class SiteListView(generics.ListAPIView): +class SiteListView(generics.ListCreateAPIView): """Site settings View.""" pagination_class = None From e02db4958afd5f2b344d67cdd7b5e76833a06844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Mon, 2 Dec 2019 17:40:01 +0300 Subject: [PATCH 43/85] Add feature serializer --- apps/main/serializers.py | 16 ++++++++++++++++ apps/main/urls/back.py | 2 ++ apps/main/views/back.py | 15 ++++++++++----- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/apps/main/serializers.py b/apps/main/serializers.py index c0bf14ad..561a50c0 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -139,6 +139,22 @@ class SiteBackOfficeSerializer(SiteSerializer): ] +class FeatureSerializer(serializers.ModelSerializer): + """Site feature serializer.""" + + class Meta: + """Meta class.""" + + model = models.Feature + fields = ( + 'id', + 'slug', + 'priority', + 'route', + 'site_settings', + ) + + # class SiteFeatureSerializer(serializers.ModelSerializer): # """Site feature serializer.""" # diff --git a/apps/main/urls/back.py b/apps/main/urls/back.py index 25347434..99e6a50f 100644 --- a/apps/main/urls/back.py +++ b/apps/main/urls/back.py @@ -12,4 +12,6 @@ urlpatterns = [ path('sites/', views.SiteListBackOfficeView.as_view(), name='site-list-create'), path('site-settings//', views.SiteSettingsBackOfficeView.as_view(), name='site-settings'), + path('feature/', views.FeatureBackView.as_view(), name='feature-list-create'), + path('feature//', views.FeatureRUDBackView.as_view(), name='feature-rud') ] diff --git a/apps/main/views/back.py b/apps/main/views/back.py index 95ac3b24..76c99e3d 100644 --- a/apps/main/views/back.py +++ b/apps/main/views/back.py @@ -39,16 +39,21 @@ class ContentTypeView(generics.ListAPIView): ) +class FeatureBackView(generics.ListCreateAPIView): + """Feature list or create View.""" + serializer_class = serializers.FeatureSerializer + + +class FeatureRUDBackView(generics.RetrieveUpdateDestroyAPIView): + """Feature RUD View.""" + serializer_class = serializers.FeatureSerializer + + class SiteSettingsBackOfficeView(SiteSettingsView): """Site settings View.""" serializer_class = serializers.SiteSettingsBackOfficeSerializer -# class SiteSettingsBackRUDView(generics.RetrieveUpdateDestroyAPIView): -# """Site settings RUD View.""" -# serializer_class = serializers.SiteSettingsBackOfficeSerializer - - class SiteListBackOfficeView(SiteListView): """Site settings View.""" serializer_class = serializers.SiteBackOfficeSerializer From 69dbaf40dc20e16b88f9314437dd0dd52cef8b45 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Mon, 2 Dec 2019 18:05:57 +0300 Subject: [PATCH 44/85] update Dockerfile --- .dockerignore | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 9 ++-- 2 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..e2ee1d61 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,113 @@ +# Created by .ignore support plugin (hsz.mobi) +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +_*/ + +.git/ +.idea/ +_files/ + diff --git a/Dockerfile b/Dockerfile index 3564a933..c2b4c4dc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,9 @@ FROM python:3.7 ENV PYTHONUNBUFFERED 1 RUN apt-get update; apt-get --assume-yes install binutils libproj-dev gdal-bin gettext -RUN mkdir /code +RUN mkdir /code /requirements +ADD ./requirements /requirements +RUN pip install --no-cache-dir -r /requirements/base.txt && \ + pip install --no-cache-dir -r /requirements/development.txt WORKDIR /code -ADD . /code/ -RUN pip install --no-cache-dir -r /code/requirements/base.txt && \ - pip install --no-cache-dir -r /code/requirements/development.txt \ No newline at end of file +ADD . /code/ \ No newline at end of file From 3b05a552aaf477e4ba1b8cc38bfe48e8b20422c6 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 2 Dec 2019 18:36:44 +0300 Subject: [PATCH 45/85] Tz field for establishments (cherry picked from commit a537f04) --- apps/establishment/models.py | 5 +++++ apps/establishment/serializers/common.py | 3 ++- apps/search_indexes/documents/establishment.py | 1 + apps/search_indexes/serializers.py | 1 + 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index c5533d52..c5da4094 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -541,6 +541,11 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin, time_at_est_tz = now_at_est_tz.time() return schedule_for_today.ending_time > time_at_est_tz > schedule_for_today.opening_time + @property + def timezone_as_str(self): + """ Returns tz in str format""" + return self.tz.localize(datetime.now()).strftime('%z') + @property def tags_indexing(self): return [{'id': tag.metadata.id, diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index cb102ff1..7364d02c 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -286,7 +286,7 @@ class EstablishmentBaseSerializer(ProjectModelSerializer): preview_image = serializers.URLField(source='preview_image_url', allow_null=True, read_only=True) - + tz = serializers.CharField(read_only=True, source='timezone_as_str') new_image = ImageBaseSerializer(source='crop_main_image', allow_null=True, read_only=True) class Meta: @@ -311,6 +311,7 @@ class EstablishmentBaseSerializer(ProjectModelSerializer): 'image', 'preview_image', 'new_image', + 'tz', ] diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index e53b93de..b1983b61 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -124,6 +124,7 @@ class EstablishmentDocument(Document): }, ) favorites_for_users = fields.ListField(field=fields.IntegerField()) + tz = fields.KeywordField(attr='timezone_as_str') class Django: diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index b45bd2e3..654d3354 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -251,6 +251,7 @@ class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer): 'works_noon', 'works_evening', 'works_at_weekday', + 'tz', # 'works_now', # 'collections', # 'establishment_type', From ab2304a6faa527c76af22f709b6bfeb5954965fe Mon Sep 17 00:00:00 2001 From: dormantman Date: Mon, 2 Dec 2019 20:50:47 +0300 Subject: [PATCH 46/85] Fixed a bug related to inaccurate calc of the visual center of the map --- apps/search_indexes/filters.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/apps/search_indexes/filters.py b/apps/search_indexes/filters.py index ec45350c..ee03c7a5 100644 --- a/apps/search_indexes/filters.py +++ b/apps/search_indexes/filters.py @@ -10,14 +10,27 @@ class CustomGeoSpatialFilteringFilterBackend(GeoSpatialFilteringFilterBackend): """Automatically adds centering and sorting within bounding box.""" @staticmethod - def calculate_center(a, b): - return (a[0] + b[0]) / 2, (a[1] + b[1]) / 2 + def calculate_center(first, second): + if second[1] < 0 <= first[1]: + reverse_first, reverse_second = 180 - abs(first[1]), 180 - abs(second[1]) + diff = (reverse_first + reverse_second) / 2 + + if reverse_first < reverse_second: + result_part = -180 + (180 + second[1] - diff) + + else: + result_part = 180 - (180 - first[1] - diff) + + else: + result_part = (first[1] + second[1]) / 2 + + return round((first[0] + second[0]) / 2, 2), round(result_part, 2) def filter_queryset(self, request, queryset, view): ret = super().filter_queryset(request, queryset, view) bb = request.query_params.get('location__geo_bounding_box') if bb: - center = self.calculate_center(*map(lambda p: list(map(lambda x: float(x),p.split(','))), bb.split('__'))) + center = self.calculate_center(*map(lambda point: list(map(float, point.split(','))), bb.split('__'))) request.GET._mutable = True request.query_params.update({ 'ordering': f'location__{center[0]}__{center[1]}__km' From 4d54c84b515f4fc3e17d952e07991d03b5c129a1 Mon Sep 17 00:00:00 2001 From: dormantman Date: Mon, 2 Dec 2019 20:52:32 +0300 Subject: [PATCH 47/85] Removed rounding of coordinates --- apps/search_indexes/filters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/search_indexes/filters.py b/apps/search_indexes/filters.py index ee03c7a5..7a3993c0 100644 --- a/apps/search_indexes/filters.py +++ b/apps/search_indexes/filters.py @@ -24,7 +24,7 @@ class CustomGeoSpatialFilteringFilterBackend(GeoSpatialFilteringFilterBackend): else: result_part = (first[1] + second[1]) / 2 - return round((first[0] + second[0]) / 2, 2), round(result_part, 2) + return (first[0] + second[0]) / 2, result_part def filter_queryset(self, request, queryset, view): ret = super().filter_queryset(request, queryset, view) From 140ba040e7a376adc59b21176cc75bcdf3dab1c6 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 2 Dec 2019 21:26:55 +0300 Subject: [PATCH 48/85] Extra fields for establishments --- apps/establishment/models.py | 10 +++++++++- apps/establishment/serializers/common.py | 4 ++++ apps/establishment/views/web.py | 4 +++- .../search_indexes/documents/establishment.py | 20 ++++++++++++++++++- apps/search_indexes/serializers.py | 4 ++++ 5 files changed, 39 insertions(+), 3 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index c5da4094..74d40921 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -14,7 +14,7 @@ from django.contrib.postgres.fields import ArrayField from django.core.exceptions import ValidationError from django.core.validators import MinValueValidator, MaxValueValidator from django.db import models -from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q +from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q, Prefetch from django.utils import timezone from django.utils.translation import gettext_lazy as _ from phonenumber_field.modelfields import PhoneNumberField @@ -23,6 +23,7 @@ from timezone_field import TimeZoneField from collection.models import Collection from location.models import Address from main.models import Award, Currency +from tag.models import Tag from review.models import Review from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin, TranslatedFieldsMixin, BaseAttributes, GalleryModelMixin, @@ -321,6 +322,13 @@ class EstablishmentQuerySet(models.QuerySet): """Exclude countries.""" return self.exclude(address__city__country__in=countries) + def with_certain_tag_category_related(self, index_name, attr_name): + """Includes extra tags.""" + return self.prefetch_related( + Prefetch('tags', queryset=Tag.objects.filter(category__index_name=index_name), + to_attr=attr_name) + ) + class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin, HasTagsMixin, FavoritesMixin): diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 7364d02c..6a88c5d4 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -320,12 +320,16 @@ class EstablishmentListRetrieveSerializer(EstablishmentBaseSerializer): address = AddressDetailSerializer() schedule = ScheduleRUDSerializer(many=True, allow_null=True) + restaurant_category = TagBaseSerializer(read_only=True, many=True, allow_null=True) + restaurant_cuisine = TagBaseSerializer(read_only=True, many=True, allow_null=True) class Meta(EstablishmentBaseSerializer.Meta): """Meta class.""" fields = EstablishmentBaseSerializer.Meta.fields + [ 'schedule', + 'restaurant_category', + 'restaurant_cuisine', ] diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index bd826e4e..5c73feb6 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -35,7 +35,9 @@ class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView): def get_queryset(self): return super().get_queryset().with_schedule() \ - .with_extended_address_related().with_currency_related() + .with_extended_address_related().with_currency_related() \ + .with_certain_tag_category_related('category', 'restaurant_category') \ + .with_certain_tag_category_related('cuisine', 'restaurant_cuisine') \ class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView): diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index b1983b61..5e14888c 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -49,6 +49,22 @@ class EstablishmentDocument(Document): 'value': fields.KeywordField(), }, multi=True) + restaurant_category = fields.ObjectField( + properties={ + 'id': fields.IntegerField(attr='id'), + 'label': fields.ObjectField(attr='label_indexing', + properties=OBJECT_FIELD_PROPERTIES), + 'value': fields.KeywordField(), + }, + multi=True) + restaurant_cuisine = fields.ObjectField( + properties={ + 'id': fields.IntegerField(attr='id'), + 'label': fields.ObjectField(attr='label_indexing', + properties=OBJECT_FIELD_PROPERTIES), + 'value': fields.KeywordField(), + }, + multi=True) visible_tags = fields.ObjectField( properties={ 'id': fields.IntegerField(attr='id'), @@ -142,4 +158,6 @@ class EstablishmentDocument(Document): ) def get_queryset(self): - return super().get_queryset().with_es_related() + return super().get_queryset().with_es_related() \ + .with_certain_tag_category_related('category', 'restaurant_category') \ + .with_certain_tag_category_related('cuisine', 'restaurant_cuisine') diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 654d3354..62b5560f 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -229,6 +229,8 @@ class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer): establishment_subtypes = EstablishmentTypeSerializer(many=True) address = AddressDocumentSerializer(allow_null=True) tags = TagsDocumentSerializer(many=True, source='visible_tags') + restaurant_category = TagsDocumentSerializer(many=True) + restaurant_cuisine = TagsDocumentSerializer(many=True) schedule = ScheduleDocumentSerializer(many=True, allow_null=True) class Meta: @@ -247,6 +249,8 @@ class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer): 'preview_image', 'address', 'tags', + 'restaurant_category', + 'restaurant_cuisine', 'schedule', 'works_noon', 'works_evening', From e981b48fa0f7822e8a682a7ee7471414a750af5d Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 2 Dec 2019 21:32:03 +0300 Subject: [PATCH 49/85] Fix extra tags indexing --- apps/establishment/models.py | 8 ++++++++ apps/search_indexes/documents/establishment.py | 8 +++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 74d40921..e270832b 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -602,6 +602,14 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin, if qs.exists(): return qs.first().image + @property + def restaurant_category_indexing(self): + return self.tags.filter(category__index_name='category') + + @property + def restaurant_cuisine_indexing(self): + return self.tags.filter(category__index_name='cuisine') + class EstablishmentNoteQuerySet(models.QuerySet): """QuerySet for model EstablishmentNote.""" diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index 5e14888c..9cfc27d3 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -56,7 +56,7 @@ class EstablishmentDocument(Document): properties=OBJECT_FIELD_PROPERTIES), 'value': fields.KeywordField(), }, - multi=True) + multi=True, attr='restaurant_category_indexing') restaurant_cuisine = fields.ObjectField( properties={ 'id': fields.IntegerField(attr='id'), @@ -64,7 +64,7 @@ class EstablishmentDocument(Document): properties=OBJECT_FIELD_PROPERTIES), 'value': fields.KeywordField(), }, - multi=True) + multi=True, attr='restaurant_cuisine_indexing') visible_tags = fields.ObjectField( properties={ 'id': fields.IntegerField(attr='id'), @@ -158,6 +158,4 @@ class EstablishmentDocument(Document): ) def get_queryset(self): - return super().get_queryset().with_es_related() \ - .with_certain_tag_category_related('category', 'restaurant_category') \ - .with_certain_tag_category_related('cuisine', 'restaurant_cuisine') + return super().get_queryset().with_es_related() From e40156908bc691e015bb64f2c8ea941dde3ac9ad Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 2 Dec 2019 21:38:21 +0300 Subject: [PATCH 50/85] Artisan category tags for establishments --- apps/establishment/models.py | 4 ++++ apps/establishment/serializers/common.py | 2 ++ apps/establishment/views/web.py | 1 + apps/search_indexes/documents/establishment.py | 8 ++++++++ apps/search_indexes/serializers.py | 2 ++ 5 files changed, 17 insertions(+) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index e270832b..140188d7 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -610,6 +610,10 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin, def restaurant_cuisine_indexing(self): return self.tags.filter(category__index_name='cuisine') + @property + def artisan_category_indexing(self): + return self.tags.filter(category__index_name='shop_category') + class EstablishmentNoteQuerySet(models.QuerySet): """QuerySet for model EstablishmentNote.""" diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 6a88c5d4..37432a21 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -322,6 +322,7 @@ class EstablishmentListRetrieveSerializer(EstablishmentBaseSerializer): schedule = ScheduleRUDSerializer(many=True, allow_null=True) restaurant_category = TagBaseSerializer(read_only=True, many=True, allow_null=True) restaurant_cuisine = TagBaseSerializer(read_only=True, many=True, allow_null=True) + artisan_category = TagBaseSerializer(read_only=True, many=True, allow_null=True) class Meta(EstablishmentBaseSerializer.Meta): """Meta class.""" @@ -330,6 +331,7 @@ class EstablishmentListRetrieveSerializer(EstablishmentBaseSerializer): 'schedule', 'restaurant_category', 'restaurant_cuisine', + 'artisan_category', ] diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index 5c73feb6..cfd4880e 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -38,6 +38,7 @@ class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView): .with_extended_address_related().with_currency_related() \ .with_certain_tag_category_related('category', 'restaurant_category') \ .with_certain_tag_category_related('cuisine', 'restaurant_cuisine') \ + .with_ceratin_tag_category_related('shop_category', 'artisan_category') class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView): diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index 9cfc27d3..9e1d0a82 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -65,6 +65,14 @@ class EstablishmentDocument(Document): 'value': fields.KeywordField(), }, multi=True, attr='restaurant_cuisine_indexing') + artisan_category = fields.ObjectField( + properties={ + 'id': fields.IntegerField(attr='id'), + 'label': fields.ObjectField(attr='label_indexing', + properties=OBJECT_FIELD_PROPERTIES), + 'value': fields.KeywordField(), + }, + multi=True, attr='artisan_category_indexing') visible_tags = fields.ObjectField( properties={ 'id': fields.IntegerField(attr='id'), diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 62b5560f..1a7c7132 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -231,6 +231,7 @@ class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer): tags = TagsDocumentSerializer(many=True, source='visible_tags') restaurant_category = TagsDocumentSerializer(many=True) restaurant_cuisine = TagsDocumentSerializer(many=True) + artisan_category = TagsDocumentSerializer(many=True) schedule = ScheduleDocumentSerializer(many=True, allow_null=True) class Meta: @@ -251,6 +252,7 @@ class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer): 'tags', 'restaurant_category', 'restaurant_cuisine', + 'artisan_category', 'schedule', 'works_noon', 'works_evening', From ed28ea176731db2f12cb8941433c681db33d5e6c Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 2 Dec 2019 22:10:38 +0300 Subject: [PATCH 51/85] Fix missing tags --- apps/search_indexes/serializers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 1a7c7132..67a2bff0 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -229,9 +229,9 @@ class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer): establishment_subtypes = EstablishmentTypeSerializer(many=True) address = AddressDocumentSerializer(allow_null=True) tags = TagsDocumentSerializer(many=True, source='visible_tags') - restaurant_category = TagsDocumentSerializer(many=True) - restaurant_cuisine = TagsDocumentSerializer(many=True) - artisan_category = TagsDocumentSerializer(many=True) + restaurant_category = TagsDocumentSerializer(many=True, allow_null=True) + restaurant_cuisine = TagsDocumentSerializer(many=True, allow_null=True) + artisan_category = TagsDocumentSerializer(many=True, allow_null=True) schedule = ScheduleDocumentSerializer(many=True, allow_null=True) class Meta: From b6d0815ed06bf1207d5490ea219d872ccc856e83 Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Tue, 3 Dec 2019 09:59:11 +0300 Subject: [PATCH 52/85] add news author migration --- apps/news/management/commands/add_author.py | 29 +++++++++++++++++++++ apps/news/transfer_data.py | 1 + apps/transfer/serializers/news.py | 9 +++++++ docker-compose.mysql.yml | 2 +- project/settings/local.py | 3 +++ 5 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 apps/news/management/commands/add_author.py diff --git a/apps/news/management/commands/add_author.py b/apps/news/management/commands/add_author.py new file mode 100644 index 00000000..0313d7b6 --- /dev/null +++ b/apps/news/management/commands/add_author.py @@ -0,0 +1,29 @@ +from django.core.management.base import BaseCommand +from django.db.models import F +from tqdm import tqdm + +from account.models import User +from news.models import News +from transfer.models import PageTexts + + +class Command(BaseCommand): + help = 'Add author of News' + + def handle(self, *args, **kwargs): + count = 0 + news_list = News.objects.filter(created_by__isnull=True) + + for news in tqdm(news_list, desc="Find author for exist news"): + old_news = PageTexts.objects.filter(id=news.old_id).annotate( + account_id=F('page__account_id'), + ).first() + if old_news: + user = User.objects.filter(old_id=old_news.account_id).first() + if user: + news.created_by = user + news.modified_by = user + news.save() + count += 1 + + self.stdout.write(self.style.WARNING(f'Update {count} objects.')) diff --git a/apps/news/transfer_data.py b/apps/news/transfer_data.py index bc0d3711..33aeedfd 100644 --- a/apps/news/transfer_data.py +++ b/apps/news/transfer_data.py @@ -38,6 +38,7 @@ def transfer_news(): image=F('page__attachment_suffix_url'), template=F('page__template'), tags=GroupConcat('page__tags__id'), + account_id=F('page__account_id'), ) serialized_data = NewsSerializer(data=list(queryset.values()), many=True) diff --git a/apps/transfer/serializers/news.py b/apps/transfer/serializers/news.py index 4dc7d913..4ef03184 100644 --- a/apps/transfer/serializers/news.py +++ b/apps/transfer/serializers/news.py @@ -7,10 +7,12 @@ from tag.models import Tag from transfer.models import PageMetadata from utils.legacy_parser import parse_legacy_news_content from utils.slug_generator import generate_unique_slug +from account.models import User class NewsSerializer(serializers.Serializer): id = serializers.IntegerField() + account_id = serializers.IntegerField(allow_null=True) tag_cat_id = serializers.IntegerField() news_type_id = serializers.IntegerField() news_title = serializers.CharField() @@ -39,6 +41,8 @@ class NewsSerializer(serializers.Serializer): 'state': self.get_state(validated_data), 'template': self.get_template(validated_data), 'country': self.get_country(validated_data), + 'created_by': self.get_account(validated_data), + 'modified_by': self.get_account(validated_data), } obj = News.objects.create(**payload) @@ -126,3 +130,8 @@ class NewsSerializer(serializers.Serializer): else: content = {data['locale']: data['title']} return content + + @staticmethod + def get_account(data): + """Get account""" + return User.objects.filter(old_id=data['account_id']).first() diff --git a/docker-compose.mysql.yml b/docker-compose.mysql.yml index bd81ecb2..a8900226 100644 --- a/docker-compose.mysql.yml +++ b/docker-compose.mysql.yml @@ -5,7 +5,7 @@ services: mysql_db: image: mysql:5.7 ports: - - "3306:3306" + - "3316:3306" environment: MYSQL_DATABASE: dev MYSQL_USER: dev diff --git a/project/settings/local.py b/project/settings/local.py index c8974c40..6a592a46 100644 --- a/project/settings/local.py +++ b/project/settings/local.py @@ -99,3 +99,6 @@ TESTING = sys.argv[1:2] == ['test'] if TESTING: ELASTICSEARCH_INDEX_NAMES = {} ELASTICSEARCH_DSL_AUTOSYNC = False + +# INSTALLED APPS +INSTALLED_APPS.append('transfer.apps.TransferConfig') \ No newline at end of file From fdc63f2677f34c2f5c4543fdd8f1ae6de2c2f5b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Tue, 3 Dec 2019 10:20:13 +0300 Subject: [PATCH 53/85] Add API methods for site-feature --- apps/main/serializers.py | 55 +++++++++++++--------------------------- apps/main/urls/back.py | 8 +++++- apps/main/views/back.py | 10 ++++++++ 3 files changed, 35 insertions(+), 38 deletions(-) diff --git a/apps/main/serializers.py b/apps/main/serializers.py index 561a50c0..b93382d5 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -21,27 +21,6 @@ class FeatureSerializer(serializers.ModelSerializer): ) -class SiteFeatureSerializer(serializers.ModelSerializer): - id = serializers.IntegerField(source='feature.id') - slug = serializers.CharField(source='feature.slug') - priority = serializers.IntegerField(source='feature.priority') - route = serializers.CharField(source='feature.route.name') - source = serializers.IntegerField(source='feature.source') - nested = RecursiveFieldSerializer(many=True, allow_null=True) - - class Meta: - """Meta class.""" - model = models.SiteFeature - fields = ('main', - 'id', - 'slug', - 'priority', - 'route', - 'source', - 'nested', - ) - - class CurrencySerializer(ProjectModelSerializer): """Currency serializer.""" @@ -56,6 +35,23 @@ class CurrencySerializer(ProjectModelSerializer): ] +class SiteFeatureSerializer(serializers.ModelSerializer): + """Site feature serializer.""" + + class Meta: + """Meta class.""" + + model = models.SiteFeature + fields = ( + 'id', + 'site_settings', + 'feature', + 'published', + 'main', + 'nested' + ) + + class SiteSettingsSerializer(serializers.ModelSerializer): """Site settings serializer.""" @@ -140,7 +136,7 @@ class SiteBackOfficeSerializer(SiteSerializer): class FeatureSerializer(serializers.ModelSerializer): - """Site feature serializer.""" + """Feature serializer.""" class Meta: """Meta class.""" @@ -155,21 +151,6 @@ class FeatureSerializer(serializers.ModelSerializer): ) -# class SiteFeatureSerializer(serializers.ModelSerializer): -# """Site feature serializer.""" -# -# class Meta: -# """Meta class.""" -# -# model = models.SiteFeature -# fields = ( -# 'id', -# 'published', -# 'site_settings', -# 'feature', -# ) - - class AwardBaseSerializer(serializers.ModelSerializer): """Award base serializer.""" diff --git a/apps/main/urls/back.py b/apps/main/urls/back.py index 99e6a50f..609e61f7 100644 --- a/apps/main/urls/back.py +++ b/apps/main/urls/back.py @@ -13,5 +13,11 @@ urlpatterns = [ path('site-settings//', views.SiteSettingsBackOfficeView.as_view(), name='site-settings'), path('feature/', views.FeatureBackView.as_view(), name='feature-list-create'), - path('feature//', views.FeatureRUDBackView.as_view(), name='feature-rud') + path('feature//', views.FeatureRUDBackView.as_view(), name='feature-rud'), + path('site-feature/', views.SiteFeatureBackView.as_view(), + name='site-feature-list-create'), + path('site-feature//', views.SiteFeatureRUDBackView.as_view(), + name='site-feature-rud'), ] + + diff --git a/apps/main/views/back.py b/apps/main/views/back.py index 76c99e3d..adc0196a 100644 --- a/apps/main/views/back.py +++ b/apps/main/views/back.py @@ -44,11 +44,21 @@ class FeatureBackView(generics.ListCreateAPIView): serializer_class = serializers.FeatureSerializer +class SiteFeatureBackView(generics.ListCreateAPIView): + """Feature list or create View.""" + serializer_class = serializers.SiteFeatureSerializer + + class FeatureRUDBackView(generics.RetrieveUpdateDestroyAPIView): """Feature RUD View.""" serializer_class = serializers.FeatureSerializer +class SiteFeatureRUDBackView(generics.RetrieveUpdateDestroyAPIView): + """Feature RUD View.""" + serializer_class = serializers.SiteFeatureSerializer + + class SiteSettingsBackOfficeView(SiteSettingsView): """Site settings View.""" serializer_class = serializers.SiteSettingsBackOfficeSerializer From 4b9561fe752e55c851316923e8b84f11b8841295 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 3 Dec 2019 11:50:36 +0300 Subject: [PATCH 54/85] Fix typo --- apps/establishment/views/web.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index cfd4880e..15421ee7 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -38,7 +38,7 @@ class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView): .with_extended_address_related().with_currency_related() \ .with_certain_tag_category_related('category', 'restaurant_category') \ .with_certain_tag_category_related('cuisine', 'restaurant_cuisine') \ - .with_ceratin_tag_category_related('shop_category', 'artisan_category') + .with_certain_tag_category_related('shop_category', 'artisan_category') class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView): From 201e02b4b794a37050769b01008372d9c186c23a Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 3 Dec 2019 13:29:37 +0300 Subject: [PATCH 55/85] remove empty favs --- apps/tag/filters.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/tag/filters.py b/apps/tag/filters.py index 5e2b31a7..46470ca1 100644 --- a/apps/tag/filters.py +++ b/apps/tag/filters.py @@ -73,7 +73,10 @@ class TagsFilterSet(TagsBaseFilterSet): def by_establishment_type(self, queryset, name, value): if value == EstablishmentType.ARTISAN: - return models.Tag.objects.by_category_index_name('shop_category')[0:8] + qs = models.Tag.objects.by_category_index_name('shop_category') + if self.request.country_code and self.request.country_code not in settings.INTERNATIONAL_COUNTRY_CODES: + qs = qs.filter(establishments__address__city__country__code=self.request.country_code).distinct('id') + return qs.exclude(establishments__isnull=True)[0:8] return queryset.by_establishment_type(value) # TMP TODO remove it later From e7de51373d99a8b3d9a31f13545488fb84c0e287 Mon Sep 17 00:00:00 2001 From: dormantman Date: Tue, 3 Dec 2019 14:39:25 +0300 Subject: [PATCH 56/85] Correction of finding the visual center of points --- apps/search_indexes/filters.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/search_indexes/filters.py b/apps/search_indexes/filters.py index 7a3993c0..4537124d 100644 --- a/apps/search_indexes/filters.py +++ b/apps/search_indexes/filters.py @@ -11,7 +11,7 @@ class CustomGeoSpatialFilteringFilterBackend(GeoSpatialFilteringFilterBackend): @staticmethod def calculate_center(first, second): - if second[1] < 0 <= first[1]: + if second[1] < 0 < first[1]: reverse_first, reverse_second = 180 - abs(first[1]), 180 - abs(second[1]) diff = (reverse_first + reverse_second) / 2 @@ -21,6 +21,16 @@ class CustomGeoSpatialFilteringFilterBackend(GeoSpatialFilteringFilterBackend): else: result_part = 180 - (180 - first[1] - diff) + elif second[1] < 0 > first[1]: + diff = abs(abs(second[1]) - abs(first[1])) + + if diff > 90: + reverse_first, reverse_second = 180 - abs(first[1]), 180 - abs(second[1]) + result_part = (reverse_first + reverse_second) / 2 + + else: + result_part = (first[1] + second[1]) / 2 + else: result_part = (first[1] + second[1]) / 2 From 637903e6135304021d28c443e4dc8e1bfbe1a43a Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Tue, 3 Dec 2019 15:15:02 +0300 Subject: [PATCH 57/85] fix scheduler --- apps/account/serializers/common.py | 4 +++ .../management/commands/fix_scheduler.py | 36 +++++++++++++++++++ apps/transfer/serializers/establishment.py | 2 +- 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 apps/establishment/management/commands/fix_scheduler.py diff --git a/apps/account/serializers/common.py b/apps/account/serializers/common.py index d2933747..d2ce0974 100644 --- a/apps/account/serializers/common.py +++ b/apps/account/serializers/common.py @@ -92,7 +92,11 @@ class UserBaseSerializer(serializers.ModelSerializer): model = models.User fields = ( + 'id', 'fullname', + 'first_name', + 'last_name', + 'email', 'cropped_image_url', 'image_url', ) diff --git a/apps/establishment/management/commands/fix_scheduler.py b/apps/establishment/management/commands/fix_scheduler.py new file mode 100644 index 00000000..78422935 --- /dev/null +++ b/apps/establishment/management/commands/fix_scheduler.py @@ -0,0 +1,36 @@ +from django.core.management.base import BaseCommand +from tqdm import tqdm + +from establishment.models import Establishment +from transfer.models import Establishments +from transfer.serializers.establishment import EstablishmentSerializer +from timetable.models import Timetable +from django.db import transaction + + +class Command(BaseCommand): + help = 'Fix scheduler' + + @transaction.atomic + def handle(self, *args, **kwargs): + count = 0 + establishments = Establishment.objects.all() + old_est_list = Establishments.objects.prefetch_related( + 'schedules_set', + ) + # remove old records of Timetable + Timetable.objects.all().delete() + + for est in tqdm(establishments, desc="Fix scheduler"): + old_est = old_est_list.filter(id=est.old_id).first() + + if old_est and old_est.schedules_set.exists(): + old_schedule = old_est.schedules_set.first() + timetable = old_schedule.timetable + if timetable: + new_schedules = EstablishmentSerializer.get_schedules(timetable) + est.schedule.add(*new_schedules) + est.save() + count += 1 + + self.stdout.write(self.style.WARNING(f'Update {count} objects.')) diff --git a/apps/transfer/serializers/establishment.py b/apps/transfer/serializers/establishment.py index 0e5f55d4..a287d61b 100644 --- a/apps/transfer/serializers/establishment.py +++ b/apps/transfer/serializers/establishment.py @@ -125,7 +125,7 @@ class EstablishmentSerializer(serializers.ModelSerializer): weekdays = { 'su': Timetable.SUNDAY, 'mo': Timetable.MONDAY, - 'tu': Timetable.THURSDAY, + 'tu': Timetable.TUESDAY, 'we': Timetable.WEDNESDAY, 'th': Timetable.THURSDAY, 'fr': Timetable.FRIDAY, From f5d5ce922fe398d6ef68ea96614c43ee9fba66b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Tue, 3 Dec 2019 15:23:41 +0300 Subject: [PATCH 58/85] Old role migrate --- apps/account/migrations/0021_oldrole.py | 24 +++++++++++++++++++ .../migrations/0022_auto_20191203_1149.py | 18 ++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 apps/account/migrations/0021_oldrole.py create mode 100644 apps/account/migrations/0022_auto_20191203_1149.py diff --git a/apps/account/migrations/0021_oldrole.py b/apps/account/migrations/0021_oldrole.py new file mode 100644 index 00000000..9a8fd4e9 --- /dev/null +++ b/apps/account/migrations/0021_oldrole.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2.7 on 2019-12-03 10:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0020_role_site'), + ] + + operations = [ + migrations.CreateModel( + name='OldRole', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('new_role', models.CharField(max_length=512, verbose_name='New role')), + ('old_role', models.CharField(max_length=512, verbose_name='Old role')), + ], + options={ + 'unique_together': {('new_role', 'old_role')}, + }, + ), + ] diff --git a/apps/account/migrations/0022_auto_20191203_1149.py b/apps/account/migrations/0022_auto_20191203_1149.py new file mode 100644 index 00000000..f6b554ba --- /dev/null +++ b/apps/account/migrations/0022_auto_20191203_1149.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.7 on 2019-12-03 11:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0021_oldrole'), + ] + + operations = [ + migrations.AlterField( + model_name='oldrole', + name='old_role', + field=models.CharField(max_length=512, null=True, verbose_name='Old role'), + ), + ] From 5ecb5d214b51df9c8058edc1995099096db2e0de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Tue, 3 Dec 2019 15:24:02 +0300 Subject: [PATCH 59/85] Old role migrate1 --- .../management/commands/add_affilations.py | 45 +++++++++++++++++++ apps/account/models.py | 8 ++++ 2 files changed, 53 insertions(+) create mode 100644 apps/account/management/commands/add_affilations.py diff --git a/apps/account/management/commands/add_affilations.py b/apps/account/management/commands/add_affilations.py new file mode 100644 index 00000000..4485d6f1 --- /dev/null +++ b/apps/account/management/commands/add_affilations.py @@ -0,0 +1,45 @@ +from account.models import OldRole +from django.core.management.base import BaseCommand +from django.db import connections +from establishment.management.commands.add_position import namedtuplefetchall +from tqdm import tqdm + + +class Command(BaseCommand): + help = '''Add site affilations from old db to new db. + Run after migrate {}!!!''' + + def map_role_sql(self): + with connections['legacy'].cursor() as cursor: + cursor.execute(''' + select distinct + case when role = 'news_editor' then 'CONTENT_PAGE_MANAGER' + when role in ('reviewer', 'reviwer', 'reviewer_manager') then 'REVIEWER_MANGER' + when role = 'admin' then 'SUPERUSER' + when role ='community_manager' then 'COUNTRY_ADMIN' + when role = 'site_admin' then 'COUNTRY_ADMIN' + when role = 'wine_reviewer' then 'WINERY_REVIEWER' + when role in ('salesman', 'sales_man') then 'SALES_MAN' + when role = 'seller' then 'SELLER' + else role + end as new_role, + case when role = 'GUEST' then null else role end as role + from + ( + SELECT + DISTINCT + COALESCE(role, 'GUEST') as role + FROM site_affiliations AS sa + ) t + ''') + return namedtuplefetchall(cursor) + + def handle(self, *args, **kwargs): + objects = [] + OldRole.objects.all().delete() + for s in tqdm(self.map_role_sql(), desc='Add permissions old'): + objects.append( + OldRole(new_role=s.new_role, old_role=s.role) + ) + OldRole.objects.bulk_create(objects) + self.stdout.write(self.style.WARNING(f'Migrated old roles.')) \ No newline at end of file diff --git a/apps/account/models.py b/apps/account/models.py index c212ffda..d422b772 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -291,3 +291,11 @@ class UserRole(ProjectBaseMixin): 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) + + +class OldRole(models.Model): + new_role = models.CharField(verbose_name=_('New role'), max_length=512) + old_role = models.CharField(verbose_name=_('Old role'), max_length=512, null=True) + + class Meta: + unique_together = ('new_role', 'old_role') \ No newline at end of file From 4940a4472db324a19faa01aa7a0be04ef575ee3e Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Tue, 3 Dec 2019 15:54:15 +0300 Subject: [PATCH 60/85] add additional fields to UserBaseSerializer --- apps/account/serializers/common.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/account/serializers/common.py b/apps/account/serializers/common.py index d2ce0974..09136934 100644 --- a/apps/account/serializers/common.py +++ b/apps/account/serializers/common.py @@ -93,6 +93,7 @@ class UserBaseSerializer(serializers.ModelSerializer): model = models.User fields = ( 'id', + 'username', 'fullname', 'first_name', 'last_name', From 4797225beb75f8d19b34fb838c2d371537bd5805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Tue, 3 Dec 2019 16:40:50 +0300 Subject: [PATCH 61/85] model --- apps/account/models.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/account/models.py b/apps/account/models.py index d422b772..0a3cd3a6 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -32,6 +32,9 @@ class Role(ProjectBaseMixin): ESTABLISHMENT_MANAGER = 5 REVIEWER_MANGER = 6 RESTAURANT_REVIEWER = 7 + SALES_MAN = 8 + WINERY_REVIEWER = 9 + SELLER = 10 ROLE_CHOICES = ( (STANDARD_USER, 'Standard user'), @@ -40,7 +43,10 @@ class Role(ProjectBaseMixin): (CONTENT_PAGE_MANAGER, 'Content page manager'), (ESTABLISHMENT_MANAGER, 'Establishment manager'), (REVIEWER_MANGER, 'Reviewer manager'), - (RESTAURANT_REVIEWER, 'Restaurant reviewer') + (RESTAURANT_REVIEWER, 'Restaurant reviewer'), + (SALES_MAN, 'Sales man'), + (WINERY_REVIEWER, 'Winery reviewer'), + (SELLER, 'Seller') ) role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES, null=False, blank=False) From fee8a3b1209e165e988975e9a21c5e04842d93a0 Mon Sep 17 00:00:00 2001 From: dormantman Date: Tue, 3 Dec 2019 17:16:02 +0300 Subject: [PATCH 62/85] Correction of finding the center of points with positive --- apps/search_indexes/filters.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/apps/search_indexes/filters.py b/apps/search_indexes/filters.py index 4537124d..ce77bc06 100644 --- a/apps/search_indexes/filters.py +++ b/apps/search_indexes/filters.py @@ -21,15 +21,9 @@ class CustomGeoSpatialFilteringFilterBackend(GeoSpatialFilteringFilterBackend): else: result_part = 180 - (180 - first[1] - diff) - elif second[1] < 0 > first[1]: - diff = abs(abs(second[1]) - abs(first[1])) - - if diff > 90: - reverse_first, reverse_second = 180 - abs(first[1]), 180 - abs(second[1]) - result_part = (reverse_first + reverse_second) / 2 - - else: - result_part = (first[1] + second[1]) / 2 + elif second[1] < 0 > first[1] or second[1] > 0 < first[1]: + reverse_first, reverse_second = 180 - abs(first[1]), 180 - abs(second[1]) + result_part = ((reverse_first + reverse_second) / 2) * (-1 + (second[1] < 0) * 2) else: result_part = (first[1] + second[1]) / 2 From cc7754a9d78cba712fad35afdd4581c7289e8fa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Tue, 3 Dec 2019 17:58:46 +0300 Subject: [PATCH 63/85] Old role migrate1 --- .../management/commands/add_affilations.py | 52 ++++++++++++++++--- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/apps/account/management/commands/add_affilations.py b/apps/account/management/commands/add_affilations.py index 4485d6f1..550847f2 100644 --- a/apps/account/management/commands/add_affilations.py +++ b/apps/account/management/commands/add_affilations.py @@ -1,4 +1,5 @@ -from account.models import OldRole +from account.models import OldRole, Role +from main.models import SiteSettings from django.core.management.base import BaseCommand from django.db import connections from establishment.management.commands.add_position import namedtuplefetchall @@ -34,12 +35,51 @@ class Command(BaseCommand): ''') return namedtuplefetchall(cursor) - def handle(self, *args, **kwargs): + def add_old_roles(self): objects = [] OldRole.objects.all().delete() for s in tqdm(self.map_role_sql(), desc='Add permissions old'): - objects.append( - OldRole(new_role=s.new_role, old_role=s.role) - ) + objects.append( + OldRole(new_role=s.new_role, old_role=s.role) + ) OldRole.objects.bulk_create(objects) - self.stdout.write(self.style.WARNING(f'Migrated old roles.')) \ No newline at end of file + self.stdout.write(self.style.WARNING(f'Migrated old roles.')) + + def site_role_sql(self): + with connections['legacy'].cursor() as cursor: + cursor.execute(''' + select site_id, + role + from + ( + SELECT + DISTINCT + site_id, + COALESCE(role, 'GUEST') as role + FROM site_affiliations AS sa + ) t + where t.role not in ('admin', 'GUEST') + ''') + return namedtuplefetchall(cursor) + + def add_site_role(self): + objects = [] + for s in tqdm(self.site_role_sql(), desc='Add site role'): + old_role = OldRole.objects.get(old_role=s.role) + role_choice = getattr(Role, old_role.new_role) + sites = SiteSettings.objects.filter(old_id=s.site_id) + for site in sites: + role = Role.objects.filter(site=site, role=role_choice) + if not role.exists(): + objects.append( + Role(site=site, role=role_choice) + ) + + Role.objects.bulk_create(objects) + self.stdout.write(self.style.WARNING(f'Added site roles.')) + + + + def handle(self, *args, **kwargs): + # self.add_old_roles() + self.add_site_role() \ No newline at end of file From 7cf34f0741738ac03c16980e001c46adc38f1668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Tue, 3 Dec 2019 18:30:33 +0300 Subject: [PATCH 64/85] fix --- apps/main/serializers.py | 69 ++++++++++++---------------------------- apps/main/views/back.py | 4 +-- 2 files changed, 23 insertions(+), 50 deletions(-) diff --git a/apps/main/serializers.py b/apps/main/serializers.py index b93382d5..a41a643f 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -17,10 +17,11 @@ class FeatureSerializer(serializers.ModelSerializer): fields = ( 'id', 'slug', - 'priority' + 'priority', + 'route', + 'site_settings', ) - class CurrencySerializer(ProjectModelSerializer): """Currency serializer.""" @@ -36,20 +37,24 @@ class CurrencySerializer(ProjectModelSerializer): class SiteFeatureSerializer(serializers.ModelSerializer): - """Site feature serializer.""" + id = serializers.IntegerField(source='feature.id') + slug = serializers.CharField(source='feature.slug') + priority = serializers.IntegerField(source='feature.priority') + route = serializers.CharField(source='feature.route.name') + source = serializers.IntegerField(source='feature.source') + nested = RecursiveFieldSerializer(many=True, allow_null=True) class Meta: """Meta class.""" - model = models.SiteFeature - fields = ( - 'id', - 'site_settings', - 'feature', - 'published', - 'main', - 'nested' - ) + fields = ('main', + 'id', + 'slug', + 'priority', + 'route', + 'source', + 'nested', + ) class SiteSettingsSerializer(serializers.ModelSerializer): @@ -95,23 +100,15 @@ class SiteSettingsBackOfficeSerializer(SiteSettingsSerializer): ] -class SiteSerializer(serializers.ModelSerializer): +class SiteSerializer(SiteSettingsSerializer): country = CountrySerializer() class Meta: """Meta class.""" model = models.SiteSettings - fields = [ - 'subdomain', - 'site_url', - 'country', - 'default_site', - 'pinterest_page_url', - 'twitter_page_url', - 'facebook_page_url', - 'instagram_page_url', - 'contact_email', - 'currency' + fields = SiteSettingsSerializer.Meta.fields + [ + 'id', + 'country' ] @@ -125,30 +122,6 @@ class SiteShortSerializer(serializers.ModelSerializer): ] -class SiteBackOfficeSerializer(SiteSerializer): - """Serializer for back office.""" - - class Meta(SiteSerializer.Meta): - """Meta class.""" - fields = SiteSerializer.Meta.fields + [ - 'id', - ] - - -class FeatureSerializer(serializers.ModelSerializer): - """Feature serializer.""" - - class Meta: - """Meta class.""" - - model = models.Feature - fields = ( - 'id', - 'slug', - 'priority', - 'route', - 'site_settings', - ) class AwardBaseSerializer(serializers.ModelSerializer): diff --git a/apps/main/views/back.py b/apps/main/views/back.py index adc0196a..1faf79a0 100644 --- a/apps/main/views/back.py +++ b/apps/main/views/back.py @@ -61,9 +61,9 @@ class SiteFeatureRUDBackView(generics.RetrieveUpdateDestroyAPIView): class SiteSettingsBackOfficeView(SiteSettingsView): """Site settings View.""" - serializer_class = serializers.SiteSettingsBackOfficeSerializer + serializer_class = serializers.SiteSerializer class SiteListBackOfficeView(SiteListView): """Site settings View.""" - serializer_class = serializers.SiteBackOfficeSerializer + serializer_class = serializers.SiteSerializer From 848008cae13c05b15b04cebd3540174529487584 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 3 Dec 2019 19:02:41 +0300 Subject: [PATCH 65/85] tags dynamic filters --- apps/search_indexes/filters.py | 122 ++++++++++++++++++++++++++------- 1 file changed, 99 insertions(+), 23 deletions(-) diff --git a/apps/search_indexes/filters.py b/apps/search_indexes/filters.py index ce77bc06..3101fdc2 100644 --- a/apps/search_indexes/filters.py +++ b/apps/search_indexes/filters.py @@ -4,6 +4,7 @@ from django_elasticsearch_dsl_drf.filter_backends import SearchFilterBackend, \ FacetedSearchFilterBackend, GeoSpatialFilteringFilterBackend from search_indexes.utils import OBJECT_FIELD_PROPERTIES from six import iteritems +from functools import reduce class CustomGeoSpatialFilteringFilterBackend(GeoSpatialFilteringFilterBackend): @@ -56,10 +57,30 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): :param view: :return: """ - def makefilter(cur_facet): - def myfilter(x): + def make_filter(cur_facet): + def _filter(x): return cur_facet['facet']._params['field'] != next(iter(x._params)) - return myfilter + return _filter + + def make_tags_filter(cur_facet, tags_to_remove_ids): + def _filter(x): + if hasattr(x, '_params') and (x._params.get('must') or x._params.get('should')): + ret = [] + for t in ['must', 'should']: + terms = x._params.get(t) + if terms: + for term in terms: + if cur_facet['facet']._params['field'] != next(iter(term._params)): + return True # different fields. preserve filter + else: + ret.append(next(iter(term._params.values())) not in tags_to_remove_ids) + return all(ret) + if cur_facet['facet']._params['field'] != next(iter(x._params)): + return True # different fields. preserve filter + else: + return next(iter(x._params.values())) not in tags_to_remove_ids + return _filter + __facets = self.construct_facets(request, view) setattr(view.paginator, 'facets_computed', {}) for __field, __facet in iteritems(__facets): @@ -71,29 +92,84 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): 'global' ).bucket(__field, agg) else: - qs = queryset.__copy__() - qs.query = queryset.query._clone() - filterer = makefilter(__facet) - for param_type in ['must', 'must_not', 'should']: - if qs.query._proxied._params.get(param_type): - qs.query._proxied._params[param_type] = list( - filter( - filterer, qs.query._proxied._params[param_type] + if __field != 'tag' or not request.query_params.getlist('tags_id__in'): + qs = queryset.__copy__() + qs.query = queryset.query._clone() + filterer = make_filter(__facet) + for param_type in ['must', 'must_not', 'should']: + if qs.query._proxied._params.get(param_type): + qs.query._proxied._params[param_type] = list( + filter( + filterer, qs.query._proxied._params[param_type] + ) ) - ) - sh = qs.query._proxied._params.get('should') - if (not sh or not len(sh)) \ - and qs.query._proxied._params.get('minimum_should_match'): - qs.query._proxied._params.pop('minimum_should_match') - facet_name = '_filter_' + __field - qs.aggs.bucket( - facet_name, - 'filter', - filter=agg_filter - ).bucket(__field, agg) - view.paginator.facets_computed.update({facet_name: qs.execute().aggregations[facet_name]}) + sh = qs.query._proxied._params.get('should') + if (not sh or not len(sh)) \ + and qs.query._proxied._params.get('minimum_should_match'): + qs.query._proxied._params.pop('minimum_should_match') + facet_name = '_filter_' + __field + qs.aggs.bucket( + facet_name, + 'filter', + filter=agg_filter + ).bucket(__field, agg) + view.paginator.facets_computed.update({facet_name: qs.execute().aggregations[facet_name]}) + else: + tag_facets = [] + facet_name = '_filter_' + __field + for category_tags_ids in request.query_params.getlist('tags_id__in'): + tags_to_remove = category_tags_ids.split('__') + qs = queryset.__copy__() + qs.query = queryset.query._clone() + filterer = make_tags_filter(__facet, tags_to_remove) + for param_type in ['must', 'should']: + if qs.query._proxied._params.get(param_type): + if qs.query._proxied._params.get(param_type): + qs.query._proxied._params[param_type] = list( + filter( + filterer, qs.query._proxied._params[param_type] + ) + ) + sh = qs.query._proxied._params.get('should') + if (not sh or not len(sh)) \ + and qs.query._proxied._params.get('minimum_should_match'): + qs.query._proxied._params.pop('minimum_should_match') + qs.aggs.bucket( + facet_name, + 'filter', + filter=agg_filter + ).bucket(__field, agg) + tag_facets.append(qs.execute().aggregations[facet_name]) + view.paginator.facets_computed.update({facet_name: self.merge_buckets(tag_facets)}) return queryset + @staticmethod + def merge_buckets(buckets: list): + """Reduces all buckets preserving class""" + result_bucket = buckets[0] + for bucket in buckets[1:]: + for tag in bucket.tag.buckets._l_: + if tag not in result_bucket.tag.buckets._l_: + result_bucket.tag.buckets._l_.append(tag) + def reducer(prev, cur): + try: + index = list(map(lambda x: x['key'], prev)).index(cur['key']) + if cur['doc_count'] < prev[index]['doc_count']: + prev[index]['doc_count'] = cur['doc_count'] + except ValueError: + prev.append(cur) + return prev + + result_bucket.tag.buckets._l_ = list(reduce( + reducer, result_bucket.tag.buckets._l_, [] + )) + result_bucket.doc_count = reduce( + lambda prev, cur: prev + cur['doc_count'], + result_bucket.tag.buckets._l_, + 0 + ) + return result_bucket + class CustomSearchFilterBackend(SearchFilterBackend): """Custom SearchFilterBackend.""" From 7596652a3f3d5944fec5528baa991e836f58fd95 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 3 Dec 2019 19:31:05 +0300 Subject: [PATCH 66/85] category tag tags filter by obj type --- apps/tag/serializers.py | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py index 2cc818a9..5b6f390c 100644 --- a/apps/tag/serializers.py +++ b/apps/tag/serializers.py @@ -1,9 +1,12 @@ """Tag serializers.""" from rest_framework import serializers +from rest_framework.fields import SerializerMethodField + from establishment.models import (Establishment, EstablishmentType, EstablishmentSubType) from news.models import News, NewsType from tag import models +from tag.models import Tag from utils.exceptions import (ObjectAlreadyAdded, BindingObjectNotFound, RemovedBindingObjectNotFound) from utils.serializers import TranslatedField @@ -12,6 +15,9 @@ from utils.serializers import TranslatedField class TagBaseSerializer(serializers.ModelSerializer): """Serializer for model Tag.""" + def get_extra_kwargs(self): + return super().get_extra_kwargs() + label_translated = TranslatedField() index_name = serializers.CharField(source='value', read_only=True, allow_null=True) @@ -37,6 +43,7 @@ class TagBackOfficeSerializer(TagBaseSerializer): 'category' ) + class TagCategoryProductSerializer(serializers.ModelSerializer): """SHORT Serializer for TagCategory""" @@ -57,7 +64,7 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer): """Serializer for model TagCategory.""" label_translated = TranslatedField() - tags = TagBaseSerializer(many=True, read_only=True) + tags = SerializerMethodField() class Meta: """Meta class.""" @@ -70,6 +77,22 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer): 'tags', ) + def get_tags(self, obj): + query_params = dict(self.context['request'].query_params) + if len(query_params) > 1: + return None + + tags = Tag.objects.all() + + if 'establishment_type' in query_params: + types = query_params['establishment_type'] + tags = tags.filter(establishments__establishment_type__index_name__in=types).distinct() + elif 'product_type' in query_params: + types = query_params['product_type'] + tags = tags.filter(products__product_type__index_name__in=types).distinct() + + return TagBaseSerializer(instance=tags, many=True, read_only=True).data + class TagCategoryShortSerializer(serializers.ModelSerializer): """Serializer for model TagCategory.""" @@ -174,15 +197,15 @@ class TagCategoryBindObjectSerializer(serializers.Serializer): attrs['tag_category'] = tag_category if obj_type == self.ESTABLISHMENT_TYPE: - establishment_type = EstablishmentType.objects.filter(pk=obj_id).\ + establishment_type = EstablishmentType.objects.filter(pk=obj_id). \ first() if not establishment_type: raise BindingObjectNotFound() - if request.method == 'POST' and tag_category.establishment_types.\ + if request.method == 'POST' and tag_category.establishment_types. \ filter(pk=establishment_type.pk).exists(): raise ObjectAlreadyAdded() - if request.method == 'DELETE' and not tag_category.\ - establishment_types.filter(pk=establishment_type.pk).\ + if request.method == 'DELETE' and not tag_category. \ + establishment_types.filter(pk=establishment_type.pk). \ exists(): raise RemovedBindingObjectNotFound() attrs['related_object'] = establishment_type @@ -190,10 +213,10 @@ class TagCategoryBindObjectSerializer(serializers.Serializer): news_type = NewsType.objects.filter(pk=obj_id).first() if not news_type: raise BindingObjectNotFound() - if request.method == 'POST' and tag_category.news_types.\ + if request.method == 'POST' and tag_category.news_types. \ filter(pk=news_type.pk).exists(): raise ObjectAlreadyAdded() - if request.method == 'DELETE' and not tag_category.news_types.\ + if request.method == 'DELETE' and not tag_category.news_types. \ filter(pk=news_type.pk).exists(): raise RemovedBindingObjectNotFound() attrs['related_object'] = news_type From c00075feec564c87e13cdb6fbe8da998c89c45dd Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 3 Dec 2019 19:39:20 +0300 Subject: [PATCH 67/85] empty booking response --- apps/booking/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/booking/views.py b/apps/booking/views.py index 06ef9273..b3f85bff 100644 --- a/apps/booking/views.py +++ b/apps/booking/views.py @@ -34,7 +34,7 @@ class CheckWhetherBookingAvailable(generics.GenericAPIView): periods = response['periods'] periods_by_name = {period['period']: period for period in periods if 'period' in period} if not periods_by_name: - return None + return response period_template = iter(periods_by_name.values()).__next__().copy() period_template.pop('total_left_seats') From 013cd864c29d621a1e25cddb821d384e5fa6a935 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 3 Dec 2019 20:48:35 +0300 Subject: [PATCH 68/85] tags dynamic filters #2 --- apps/search_indexes/filters.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/apps/search_indexes/filters.py b/apps/search_indexes/filters.py index 3101fdc2..a12c37f7 100644 --- a/apps/search_indexes/filters.py +++ b/apps/search_indexes/filters.py @@ -149,25 +149,23 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): result_bucket = buckets[0] for bucket in buckets[1:]: for tag in bucket.tag.buckets._l_: - if tag not in result_bucket.tag.buckets._l_: - result_bucket.tag.buckets._l_.append(tag) + result_bucket.tag.buckets.append(tag) + def reducer(prev, cur): - try: - index = list(map(lambda x: x['key'], prev)).index(cur['key']) - if cur['doc_count'] < prev[index]['doc_count']: - prev[index]['doc_count'] = cur['doc_count'] - except ValueError: + """Unique by key""" + if not len(list(filter(lambda x: x['key'] == cur['key'], prev))): prev.append(cur) return prev - result_bucket.tag.buckets._l_ = list(reduce( - reducer, result_bucket.tag.buckets._l_, [] - )) - result_bucket.doc_count = reduce( - lambda prev, cur: prev + cur['doc_count'], + buckets_count = len(buckets) + result_bucket.tag.buckets = list(filter(lambda t: t is not None, [ + tag if len(list(filter(lambda t: t['key'] == tag['key'], result_bucket.tag.buckets._l_))) == buckets_count else None + for tag in result_bucket.tag.buckets._l_])) # here we remove tags which don't present in any bucket + result_bucket.tag.buckets = list(reduce( + reducer, result_bucket.tag.buckets._l_, - 0 - ) + [] + )) return result_bucket From 9c84842c111fd5a65a970369f36ad34ed2d6344c Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 3 Dec 2019 22:35:38 +0300 Subject: [PATCH 69/85] tags dynamic filters #3 --- apps/search_indexes/filters.py | 37 ++++++++++++---------------------- 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/apps/search_indexes/filters.py b/apps/search_indexes/filters.py index a12c37f7..ec67fe7a 100644 --- a/apps/search_indexes/filters.py +++ b/apps/search_indexes/filters.py @@ -4,7 +4,7 @@ from django_elasticsearch_dsl_drf.filter_backends import SearchFilterBackend, \ FacetedSearchFilterBackend, GeoSpatialFilteringFilterBackend from search_indexes.utils import OBJECT_FIELD_PROPERTIES from six import iteritems -from functools import reduce +from tag.models import TagCategory class CustomGeoSpatialFilteringFilterBackend(GeoSpatialFilteringFilterBackend): @@ -92,7 +92,7 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): 'global' ).bucket(__field, agg) else: - if __field != 'tag' or not request.query_params.getlist('tags_id__in'): + if __field != 'tag': qs = queryset.__copy__() qs.query = queryset.query._clone() filterer = make_filter(__facet) @@ -116,9 +116,11 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): view.paginator.facets_computed.update({facet_name: qs.execute().aggregations[facet_name]}) else: tag_facets = [] + preserve_ids = [] facet_name = '_filter_' + __field - for category_tags_ids in request.query_params.getlist('tags_id__in'): - tags_to_remove = category_tags_ids.split('__') + for category in TagCategory.objects.prefetch_related('tags').filter(public=True, + value_type=TagCategory.LIST): + tags_to_remove = list(map(lambda t: t.id, category.tags.all())) qs = queryset.__copy__() qs.query = queryset.query._clone() filterer = make_tags_filter(__facet, tags_to_remove) @@ -140,32 +142,19 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): filter=agg_filter ).bucket(__field, agg) tag_facets.append(qs.execute().aggregations[facet_name]) - view.paginator.facets_computed.update({facet_name: self.merge_buckets(tag_facets)}) + preserve_ids.append(tags_to_remove) + view.paginator.facets_computed.update({facet_name: self.merge_buckets(tag_facets, preserve_ids)}) return queryset @staticmethod - def merge_buckets(buckets: list): + def merge_buckets(buckets: list, presrve_ids: list): """Reduces all buckets preserving class""" result_bucket = buckets[0] - for bucket in buckets[1:]: + result_bucket.tag.buckets = list(filter(lambda x: x in presrve_ids[0], result_bucket.tag.buckets._l_)) + for bucket, ids in list(zip(buckets, presrve_ids))[1:]: for tag in bucket.tag.buckets._l_: - result_bucket.tag.buckets.append(tag) - - def reducer(prev, cur): - """Unique by key""" - if not len(list(filter(lambda x: x['key'] == cur['key'], prev))): - prev.append(cur) - return prev - - buckets_count = len(buckets) - result_bucket.tag.buckets = list(filter(lambda t: t is not None, [ - tag if len(list(filter(lambda t: t['key'] == tag['key'], result_bucket.tag.buckets._l_))) == buckets_count else None - for tag in result_bucket.tag.buckets._l_])) # here we remove tags which don't present in any bucket - result_bucket.tag.buckets = list(reduce( - reducer, - result_bucket.tag.buckets._l_, - [] - )) + if tag['key'] in ids: + result_bucket.tag.buckets.append(tag) return result_bucket From eaf5c41e1db19fbfc60246f2eef04a55be0b8b3b Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 3 Dec 2019 22:53:31 +0300 Subject: [PATCH 70/85] tags dynamic filters #4 --- apps/search_indexes/filters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/search_indexes/filters.py b/apps/search_indexes/filters.py index ec67fe7a..8040e4b4 100644 --- a/apps/search_indexes/filters.py +++ b/apps/search_indexes/filters.py @@ -150,7 +150,7 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): def merge_buckets(buckets: list, presrve_ids: list): """Reduces all buckets preserving class""" result_bucket = buckets[0] - result_bucket.tag.buckets = list(filter(lambda x: x in presrve_ids[0], result_bucket.tag.buckets._l_)) + result_bucket.tag.buckets = list(filter(lambda x: x['key'] in presrve_ids[0], result_bucket.tag.buckets._l_)) for bucket, ids in list(zip(buckets, presrve_ids))[1:]: for tag in bucket.tag.buckets._l_: if tag['key'] in ids: From f9fb2aa17ea661f7a1aae97872d624b9f8e0bd5b Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 3 Dec 2019 23:49:45 +0300 Subject: [PATCH 71/85] tags dynamic filters #4 --- apps/search_indexes/filters.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/search_indexes/filters.py b/apps/search_indexes/filters.py index 8040e4b4..bc48b029 100644 --- a/apps/search_indexes/filters.py +++ b/apps/search_indexes/filters.py @@ -120,7 +120,7 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): facet_name = '_filter_' + __field for category in TagCategory.objects.prefetch_related('tags').filter(public=True, value_type=TagCategory.LIST): - tags_to_remove = list(map(lambda t: t.id, category.tags.all())) + tags_to_remove = list(map(lambda t: str(t.id), category.tags.all())) qs = queryset.__copy__() qs.query = queryset.query._clone() filterer = make_tags_filter(__facet, tags_to_remove) @@ -142,16 +142,16 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): filter=agg_filter ).bucket(__field, agg) tag_facets.append(qs.execute().aggregations[facet_name]) - preserve_ids.append(tags_to_remove) + preserve_ids.append(list(map(int, tags_to_remove))) view.paginator.facets_computed.update({facet_name: self.merge_buckets(tag_facets, preserve_ids)}) return queryset @staticmethod - def merge_buckets(buckets: list, presrve_ids: list): + def merge_buckets(buckets: list, preserve_ids: list): """Reduces all buckets preserving class""" result_bucket = buckets[0] - result_bucket.tag.buckets = list(filter(lambda x: x['key'] in presrve_ids[0], result_bucket.tag.buckets._l_)) - for bucket, ids in list(zip(buckets, presrve_ids))[1:]: + result_bucket.tag.buckets = list(filter(lambda x: x['key'] in preserve_ids[0], result_bucket.tag.buckets._l_)) + for bucket, ids in list(zip(buckets, preserve_ids))[1:]: for tag in bucket.tag.buckets._l_: if tag['key'] in ids: result_bucket.tag.buckets.append(tag) From 9f882ca65643aa0455b2892737f89588f51d2107 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 4 Dec 2019 00:33:18 +0300 Subject: [PATCH 72/85] tags dynamic filters #4 (boost search via ES) --- apps/search_indexes/documents/__init__.py | 2 ++ apps/search_indexes/documents/tag_category.py | 33 +++++++++++++++++++ apps/search_indexes/filters.py | 9 +++-- project/settings/development.py | 1 + project/settings/local.py | 1 + project/settings/production.py | 1 + project/settings/stage.py | 1 + 7 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 apps/search_indexes/documents/tag_category.py diff --git a/apps/search_indexes/documents/__init__.py b/apps/search_indexes/documents/__init__.py index c357f29e..60a9b580 100644 --- a/apps/search_indexes/documents/__init__.py +++ b/apps/search_indexes/documents/__init__.py @@ -1,6 +1,7 @@ from search_indexes.documents.establishment import EstablishmentDocument from search_indexes.documents.news import NewsDocument from search_indexes.documents.product import ProductDocument +from search_indexes.documents.tag_category import TagCategoryDocument from search_indexes.tasks import es_update # todo: make signal to update documents on related fields @@ -8,5 +9,6 @@ __all__ = [ 'EstablishmentDocument', 'NewsDocument', 'ProductDocument', + 'TagCategoryDocument', 'es_update', ] \ No newline at end of file diff --git a/apps/search_indexes/documents/tag_category.py b/apps/search_indexes/documents/tag_category.py new file mode 100644 index 00000000..ed69c8b7 --- /dev/null +++ b/apps/search_indexes/documents/tag_category.py @@ -0,0 +1,33 @@ +"""Product app documents.""" +from django.conf import settings +from django_elasticsearch_dsl import Document, Index, fields +from tag import models + +TagCategoryIndex = Index(settings.ELASTICSEARCH_INDEX_NAMES.get(__name__, 'tag_category')) +TagCategoryIndex.settings(number_of_shards=2, number_of_replicas=2) + + +@TagCategoryIndex.doc_type +class TagCategoryDocument(Document): + """TagCategory document.""" + + tags = fields.ListField(fields.ObjectField( + properties={ + 'id': fields.IntegerField(), + 'value': fields.KeywordField(), + }, + )) + + class Django: + model = models.TagCategory + fields = ( + 'id', + 'index_name', + 'public', + 'value_type' + ) + related_models = [models.Tag] + + + def get_queryset(self): + return super().get_queryset().with_base_related() diff --git a/apps/search_indexes/filters.py b/apps/search_indexes/filters.py index bc48b029..27df0699 100644 --- a/apps/search_indexes/filters.py +++ b/apps/search_indexes/filters.py @@ -4,6 +4,7 @@ from django_elasticsearch_dsl_drf.filter_backends import SearchFilterBackend, \ FacetedSearchFilterBackend, GeoSpatialFilteringFilterBackend from search_indexes.utils import OBJECT_FIELD_PROPERTIES from six import iteritems +from search_indexes.documents import TagCategoryDocument from tag.models import TagCategory @@ -118,9 +119,11 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): tag_facets = [] preserve_ids = [] facet_name = '_filter_' + __field - for category in TagCategory.objects.prefetch_related('tags').filter(public=True, - value_type=TagCategory.LIST): - tags_to_remove = list(map(lambda t: str(t.id), category.tags.all())) + all_tag_categories = TagCategoryDocument.search() \ + .filter('term', public=True) \ + .filter('term', value_type=TagCategory.LIST) + for category in all_tag_categories: + tags_to_remove = list(map(lambda t: str(t.id), category.tags)) qs = queryset.__copy__() qs.query = queryset.query._clone() filterer = make_tags_filter(__facet, tags_to_remove) diff --git a/project/settings/development.py b/project/settings/development.py index f850aad7..88b88789 100644 --- a/project/settings/development.py +++ b/project/settings/development.py @@ -42,6 +42,7 @@ ELASTICSEARCH_INDEX_NAMES = { 'search_indexes.documents.news': 'development_news', 'search_indexes.documents.establishment': 'development_establishment', 'search_indexes.documents.product': 'development_product', + 'search_indexes.documents.tag_category': 'development_tag_category', } # ELASTICSEARCH_DSL_AUTOSYNC = False diff --git a/project/settings/local.py b/project/settings/local.py index 6a592a46..644098bb 100644 --- a/project/settings/local.py +++ b/project/settings/local.py @@ -92,6 +92,7 @@ ELASTICSEARCH_INDEX_NAMES = { # 'search_indexes.documents.news': 'local_news', 'search_indexes.documents.establishment': 'local_establishment', 'search_indexes.documents.product': 'local_product', + 'search_indexes.documents.tag_category': 'local_tag_category', } ELASTICSEARCH_DSL_AUTOSYNC = False diff --git a/project/settings/production.py b/project/settings/production.py index 7ef2dc62..3020c6f0 100644 --- a/project/settings/production.py +++ b/project/settings/production.py @@ -36,6 +36,7 @@ ELASTICSEARCH_INDEX_NAMES = { 'search_indexes.documents.news': 'development_news', # temporarily disabled 'search_indexes.documents.establishment': 'development_establishment', 'search_indexes.documents.product': 'development_product', + 'search_indexes.documents.tag_category': 'development_tag_category', } sentry_sdk.init( diff --git a/project/settings/stage.py b/project/settings/stage.py index 95285034..dbc6844a 100644 --- a/project/settings/stage.py +++ b/project/settings/stage.py @@ -23,6 +23,7 @@ ELASTICSEARCH_DSL = { ELASTICSEARCH_INDEX_NAMES = { # 'search_indexes.documents.news': 'stage_news', #temporarily disabled 'search_indexes.documents.establishment': 'stage_establishment', + 'search_indexes.documents.tag_category': 'stage_tag_category', } COOKIE_DOMAIN = '.id-east.ru' From f5079940148d135ba361a0dbc084a3aa31588075 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 4 Dec 2019 00:49:46 +0300 Subject: [PATCH 73/85] tags dynamic filters #5 (add wine-colors) --- apps/search_indexes/filters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/search_indexes/filters.py b/apps/search_indexes/filters.py index 27df0699..644b47fd 100644 --- a/apps/search_indexes/filters.py +++ b/apps/search_indexes/filters.py @@ -121,7 +121,7 @@ class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): facet_name = '_filter_' + __field all_tag_categories = TagCategoryDocument.search() \ .filter('term', public=True) \ - .filter('term', value_type=TagCategory.LIST) + .filter(Q('term', value_type=TagCategory.LIST) | Q('match', index_name='wine-color')) for category in all_tag_categories: tags_to_remove = list(map(lambda t: str(t.id), category.tags)) qs = queryset.__copy__() From 7590a73c5de31145658c6c114a8bce8ab91ad2b7 Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 4 Dec 2019 09:23:21 +0300 Subject: [PATCH 74/85] tags fix mtm --- apps/tag/models.py | 2 +- apps/tag/serializers.py | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/apps/tag/models.py b/apps/tag/models.py index 12517815..b718d83c 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -47,7 +47,7 @@ class Tag(TranslatedFieldsMixin, models.Model): old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None) old_id_meta_product = models.PositiveIntegerField(_('old id metadata product'), - blank=True, null=True, default=None) + blank=True, null=True, default=None) objects = TagQuerySet.as_manager() diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py index 5b6f390c..eb73291f 100644 --- a/apps/tag/serializers.py +++ b/apps/tag/serializers.py @@ -2,11 +2,9 @@ from rest_framework import serializers from rest_framework.fields import SerializerMethodField -from establishment.models import (Establishment, EstablishmentType, - EstablishmentSubType) +from establishment.models import (Establishment, EstablishmentType) from news.models import News, NewsType from tag import models -from tag.models import Tag from utils.exceptions import (ObjectAlreadyAdded, BindingObjectNotFound, RemovedBindingObjectNotFound) from utils.serializers import TranslatedField @@ -79,18 +77,21 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer): def get_tags(self, obj): query_params = dict(self.context['request'].query_params) + if len(query_params) > 1: - return None - - tags = Tag.objects.all() + return [] + params = {} if 'establishment_type' in query_params: - types = query_params['establishment_type'] - tags = tags.filter(establishments__establishment_type__index_name__in=types).distinct() + params = { + 'establishments__isnull': False, + } elif 'product_type' in query_params: - types = query_params['product_type'] - tags = tags.filter(products__product_type__index_name__in=types).distinct() + params = { + 'products__isnull': False, + } + tags = obj.tags.filter(**params).distinct() return TagBaseSerializer(instance=tags, many=True, read_only=True).data From 96e08a0900cd492669e3e3ab3a629b6738649731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 4 Dec 2019 12:24:23 +0300 Subject: [PATCH 75/85] Migrate complete --- .../management/commands/add_affilations.py | 77 +++++++++++++++++-- .../migrations/0023_auto_20191204_0916.py | 22 ++++++ apps/account/models.py | 2 + 3 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 apps/account/migrations/0023_auto_20191204_0916.py diff --git a/apps/account/management/commands/add_affilations.py b/apps/account/management/commands/add_affilations.py index 550847f2..591e676d 100644 --- a/apps/account/management/commands/add_affilations.py +++ b/apps/account/management/commands/add_affilations.py @@ -1,14 +1,15 @@ -from account.models import OldRole, Role +from account.models import OldRole, Role, User, UserRole from main.models import SiteSettings from django.core.management.base import BaseCommand -from django.db import connections +from django.db import connections, transaction +from django.db.models import Prefetch from establishment.management.commands.add_position import namedtuplefetchall from tqdm import tqdm class Command(BaseCommand): help = '''Add site affilations from old db to new db. - Run after migrate {}!!!''' + Run after migrate account models!!!''' def map_role_sql(self): with connections['legacy'].cursor() as cursor: @@ -78,8 +79,74 @@ class Command(BaseCommand): Role.objects.bulk_create(objects) self.stdout.write(self.style.WARNING(f'Added site roles.')) + def update_site_role(self): + roles = Role.objects.filter(country__isnull=True).select_related('site')\ + .filter(site__id__isnull=False).select_for_update() + with transaction.atomic(): + for role in tqdm(roles, desc='Update role country'): + role.country = role.site.country + role.save() + self.stdout.write(self.style.WARNING(f'Updated site roles.')) + def user_role_sql(self): + with connections['legacy'].cursor() as cursor: + cursor.execute(''' + select t.* + from + ( + SELECT + site_id, + account_id, + COALESCE(role, 'GUEST') as role + FROM site_affiliations AS sa + ) t + join accounts a on a.id = t.account_id + where t.role not in ('admin', 'GUEST') + ''') + return namedtuplefetchall(cursor) + + def add_role_user(self): + for s in tqdm(self.user_role_sql(), desc='Add role to user'): + sites = SiteSettings.objects.filter(old_id=s.site_id) + old_role = OldRole.objects.get(old_role=s.role) + role_choice = getattr(Role, old_role.new_role) + roles = Role.objects.filter(site__in=[site for site in sites], role=role_choice) + users = User.objects.filter(old_id=s.account_id) + for user in users: + for role in roles: + user_role = UserRole.objects.get_or_create(user=user, + role=role) + self.stdout.write(self.style.WARNING(f'Added users roles.')) + + def superuser_role_sql(self): + with connections['legacy'].cursor() as cursor: + cursor.execute(''' + select t.* + from + ( + SELECT + site_id, + account_id, + COALESCE(role, 'GUEST') as role + FROM site_affiliations AS sa + ) t + join accounts a on a.id = t.account_id + where t.role in ('admin') + ''') + return namedtuplefetchall(cursor) + + def add_superuser(self): + for s in tqdm(self.superuser_role_sql(), desc='Add superuser'): + users = User.objects.filter(old_id=s.account_id).select_for_update() + with transaction.atomic(): + for user in users: + user.is_superuser = True + user.save() + self.stdout.write(self.style.WARNING(f'Added superuser.')) def handle(self, *args, **kwargs): - # self.add_old_roles() - self.add_site_role() \ No newline at end of file + self.add_old_roles() + self.add_site_role() + self.update_site_role() + self.add_role_user() + self.add_superuser() \ No newline at end of file diff --git a/apps/account/migrations/0023_auto_20191204_0916.py b/apps/account/migrations/0023_auto_20191204_0916.py new file mode 100644 index 00000000..68d313a0 --- /dev/null +++ b/apps/account/migrations/0023_auto_20191204_0916.py @@ -0,0 +1,22 @@ +# Generated by Django 2.2.7 on 2019-12-04 09:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0022_auto_20191203_1149'), + ] + + operations = [ + migrations.AlterField( + model_name='role', + name='role', + field=models.PositiveIntegerField(choices=[(1, 'Standard user'), (2, 'Comments moderator'), (3, 'Country admin'), (4, 'Content page manager'), (5, 'Establishment manager'), (6, 'Reviewer manager'), (7, 'Restaurant reviewer'), (8, 'Sales man'), (9, 'Winery reviewer'), (10, 'Seller')], verbose_name='Role'), + ), + migrations.AlterUniqueTogether( + name='userrole', + unique_together={('user', 'role')}, + ), + ] diff --git a/apps/account/models.py b/apps/account/models.py index 0a3cd3a6..3d3de56e 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -297,6 +297,8 @@ class UserRole(ProjectBaseMixin): 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) + class Meta: + unique_together = ['user', 'role'] class OldRole(models.Model): From 4d36fc0f0cf6b953324f9ff12181cd5c7f51d005 Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Wed, 4 Dec 2019 12:38:07 +0300 Subject: [PATCH 76/85] fix fabric --- fabfile.py | 2 +- make_data_migration.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fabfile.py b/fabfile.py index e8a52f85..f9896200 100644 --- a/fabfile.py +++ b/fabfile.py @@ -54,7 +54,7 @@ def collectstatic(): def deploy(branch=None): role = env.roles[0] - if env.roledefs[role]['branch'] != 'develop': + if env.roledefs[role]['branch'] == 'develop': fetch() install_requirements() migrate() diff --git a/make_data_migration.sh b/make_data_migration.sh index c92f74e7..bed1afb7 100755 --- a/make_data_migration.sh +++ b/make_data_migration.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash ./manage.py transfer -a -#./manage.py transfer -d +./manage.py transfer -d ./manage.py transfer -e ./manage.py transfer --fill_city_gallery ./manage.py transfer -l From c5992afec0e096b7f477d574652aeba74b29cfe0 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 4 Dec 2019 13:50:55 +0300 Subject: [PATCH 77/85] add opening_at to schedule search results --- apps/search_indexes/serializers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 67a2bff0..7bd3995a 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -168,6 +168,7 @@ class ScheduleDocumentSerializer(serializers.Serializer): weekday = serializers.IntegerField() weekday_display = serializers.CharField() closed_at = serializers.CharField() + opening_at = serializers.CharField() class InFavoritesMixin(DocumentSerializer): From 552e08e24249de183d24a966a5d4adeed96d28b9 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 4 Dec 2019 13:52:39 +0300 Subject: [PATCH 78/85] opening_at for establishment --- apps/search_indexes/documents/establishment.py | 1 + apps/timetable/models.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index 9e1d0a82..aca81f3f 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -116,6 +116,7 @@ class EstablishmentDocument(Document): 'weekday': fields.IntegerField(attr='weekday'), 'weekday_display': fields.KeywordField(attr='get_weekday_display'), 'closed_at': fields.KeywordField(attr='closed_at_str'), + 'opening_at': fields.KeywordField(attr='opening_at_str'), } )) address = fields.ObjectField( diff --git a/apps/timetable/models.py b/apps/timetable/models.py index cf7f8d94..90a6ae38 100644 --- a/apps/timetable/models.py +++ b/apps/timetable/models.py @@ -39,6 +39,10 @@ class Timetable(ProjectBaseMixin): def closed_at_str(self): return str(self.closed_at) if self.closed_at else None + @property + def opening_at_str(self): + return str(self.opening_at) if self.opening_at else None + @property def opening_time(self): return self.opening_at or self.lunch_start or self.dinner_start From b2fb341c101e28410a71624b7ecf014c78a23347 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 4 Dec 2019 17:13:38 +0300 Subject: [PATCH 79/85] fix wines faceted search --- 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 a5b952d7..5fa845f2 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -346,7 +346,7 @@ class ProductDocumentViewSet(BaseDocumentViewSet): faceted_search_fields = { 'tag': { - 'field': 'wine_colors.id', + 'field': 'tags.id', 'enabled': True, 'facet': TermsFacet, 'options': { From 1ae11f0b617894edb71d3ebdf904cafa0b152386 Mon Sep 17 00:00:00 2001 From: dormantman Date: Wed, 4 Dec 2019 17:27:49 +0300 Subject: [PATCH 80/85] System tags removed --- apps/establishment/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 140188d7..b708de63 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -447,7 +447,7 @@ 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']) \ + 'business_tag', 'business_tags_de', 'tag', ]) \ \ # todo: recalculate toque_number From d0378ad14e49700971b1bfc34fbba6546870b345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 4 Dec 2019 17:27:51 +0300 Subject: [PATCH 81/85] Fix options --- apps/establishment/views/back.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index d3afbf2e..6fa4d821 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -165,7 +165,7 @@ class EmployeeListCreateView(generics.ListCreateAPIView): pagination_class = None -class EstablishmentEmployeeListView(generics.ListAPIView): +class EstablishmentEmployeeListView(generics.ListCreateAPIView): """Establishment emplyoees list view.""" permission_classes = (permissions.AllowAny, ) serializer_class = serializers.EstablishmentEmployeeBackSerializer From dee353199dd38340ed3dead4a740817a23e3c16c Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 4 Dec 2019 17:30:54 +0300 Subject: [PATCH 82/85] add schedule to establishments list --- apps/establishment/serializers/common.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 37432a21..e6e489ee 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -405,6 +405,12 @@ class EstablishmentSimilarSerializer(EstablishmentBaseSerializer): """Serializer for Establishment model.""" address = AddressDetailSerializer(read_only=True) + schedule = ScheduleRUDSerializer(many=True, allow_null=True) + + class Meta(EstablishmentBaseSerializer.Meta): + fields = EstablishmentBaseSerializer.Meta.fields + [ + 'schedule', + ] class EstablishmentCommentCreateSerializer(comment_serializers.CommentSerializer): From 69b40f21b7b1683839c844675d66b11949531c3a Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 4 Dec 2019 18:26:03 +0300 Subject: [PATCH 83/85] last comment for mobile establishment detail --- apps/establishment/models.py | 14 ++++++++++++++ apps/establishment/serializers/common.py | 13 +++++++++++++ apps/establishment/urls/common.py | 1 - apps/establishment/urls/mobile.py | 3 ++- apps/establishment/urls/web.py | 6 +++++- apps/establishment/views/web.py | 7 +++++++ 6 files changed, 41 insertions(+), 3 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index b708de63..6c9080f0 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -251,6 +251,15 @@ class EstablishmentQuerySet(models.QuerySet): return self.filter(id__in=subquery_filter_by_distance) \ .order_by('-reviews__published_at') + def prefetch_comments(self): + """Prefetch last comment.""" + from comment.models import Comment + return self.prefetch_related( + models.Prefetch('comments', + queryset=Comment.objects.exclude(is_publish=False).order_by('-created'), + to_attr='comments_prefetched') + ) + def prefetch_actual_employees(self): """Prefetch actual employees.""" return self.prefetch_related( @@ -614,6 +623,11 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin, def artisan_category_indexing(self): return self.tags.filter(category__index_name='shop_category') + @property + def last_comment(self): + if hasattr(self, 'comments_prefetched'): + return self.comments_prefetched[0] + class EstablishmentNoteQuerySet(models.QuerySet): """QuerySet for model EstablishmentNote.""" diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index e6e489ee..042a0a96 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -401,6 +401,19 @@ class EstablishmentDetailSerializer(EstablishmentBaseSerializer): ] +class MobileEstablishmentDetailSerializer(EstablishmentDetailSerializer): + """Serializer for Establishment model for mobiles.""" + + last_comment = comment_serializers.CommentRUDSerializer(allow_null=True) + + class Meta(EstablishmentDetailSerializer.Meta): + """Meta class.""" + + fields = EstablishmentDetailSerializer.Meta.fields + [ + 'last_comment', + ] + + class EstablishmentSimilarSerializer(EstablishmentBaseSerializer): """Serializer for Establishment model.""" diff --git a/apps/establishment/urls/common.py b/apps/establishment/urls/common.py index e37c38f8..faa34bd9 100644 --- a/apps/establishment/urls/common.py +++ b/apps/establishment/urls/common.py @@ -9,7 +9,6 @@ urlpatterns = [ path('', views.EstablishmentListView.as_view(), name='list'), path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(), name='recent-reviews'), - path('slug//', views.EstablishmentRetrieveView.as_view(), name='detail'), path('slug//similar/', views.EstablishmentSimilarListView.as_view(), name='similar'), path('slug//comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'), path('slug//comments/create/', views.EstablishmentCommentCreateView.as_view(), diff --git a/apps/establishment/urls/mobile.py b/apps/establishment/urls/mobile.py index 2803be18..165323a9 100644 --- a/apps/establishment/urls/mobile.py +++ b/apps/establishment/urls/mobile.py @@ -5,7 +5,8 @@ from establishment import views from establishment.urls.common import urlpatterns as common_urlpatterns urlpatterns = [ - path('geo/', views.EstablishmentNearestRetrieveView.as_view(), name='nearest-establishments-list') + path('geo/', views.EstablishmentNearestRetrieveView.as_view(), name='nearest-establishments-list'), + path('slug//', views.EstablishmentMobileRetrieveView.as_view(), name='mobile-detail'), ] urlpatterns.extend(common_urlpatterns) diff --git a/apps/establishment/urls/web.py b/apps/establishment/urls/web.py index b4d1942d..4fa6595e 100644 --- a/apps/establishment/urls/web.py +++ b/apps/establishment/urls/web.py @@ -1,7 +1,11 @@ """Establishment app web urlconf.""" from establishment.urls.common import urlpatterns as common_urlpatterns +from django.urls import path +from establishment import views -urlpatterns = [] +urlpatterns = [ + path('slug//', views.EstablishmentRetrieveView.as_view(), name='web-detail'), +] urlpatterns.extend(common_urlpatterns) diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index 15421ee7..ba5cb23b 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -51,6 +51,13 @@ class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView return super().get_queryset().with_extended_related() +class EstablishmentMobileRetrieveView(EstablishmentRetrieveView): + serializer_class = serializers.MobileEstablishmentDetailSerializer + + def get_queryset(self): + return super().get_queryset().prefetch_comments() + + class EstablishmentRecentReviewListView(EstablishmentListView): """List view for last reviewed establishments.""" From e0ad819caa943a2731d5ef788d0ca220a2419823 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 4 Dec 2019 18:50:13 +0300 Subject: [PATCH 84/85] fix empty comments --- apps/establishment/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 6c9080f0..c598de55 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -625,7 +625,7 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin, @property def last_comment(self): - if hasattr(self, 'comments_prefetched'): + if hasattr(self, 'comments_prefetched') and len(self.comments_prefetched): return self.comments_prefetched[0] From f44a4bb29d0626edbeedcf10e193d103b64fc649 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 4 Dec 2019 19:37:05 +0300 Subject: [PATCH 85/85] Establishments favs type --- apps/establishment/serializers/common.py | 2 ++ apps/favorites/views.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 042a0a96..da58225b 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -419,10 +419,12 @@ class EstablishmentSimilarSerializer(EstablishmentBaseSerializer): address = AddressDetailSerializer(read_only=True) schedule = ScheduleRUDSerializer(many=True, allow_null=True) + establishment_type = EstablishmentTypeGeoSerializer() class Meta(EstablishmentBaseSerializer.Meta): fields = EstablishmentBaseSerializer.Meta.fields + [ 'schedule', + 'establishment_type', ] diff --git a/apps/favorites/views.py b/apps/favorites/views.py index 444048ff..53a05469 100644 --- a/apps/favorites/views.py +++ b/apps/favorites/views.py @@ -29,7 +29,7 @@ class FavoritesEstablishmentListView(generics.ListAPIView): def get_queryset(self): """Override get_queryset method""" return Establishment.objects.filter(favorites__user=self.request.user) \ - .order_by('-favorites') + .order_by('-favorites').with_base_related() class FavoritesProductListView(generics.ListAPIView):