From 28273749118ca0ab9e26b43fc92f8d0b39e3eff2 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Mon, 30 Dec 2019 13:22:57 +0300 Subject: [PATCH] intermediate commit --- apps/collection/models.py | 22 +++++++---- apps/collection/serializers/common.py | 16 +++++++- apps/collection/tasks.py | 3 +- apps/collection/views/back.py | 3 +- apps/establishment/models.py | 57 +++++++++++++++++---------- apps/product/models.py | 51 +++++++++++++++++------- apps/product/views/common.py | 2 +- apps/utils/export.py | 34 +++++++++++++--- 8 files changed, 133 insertions(+), 55 deletions(-) diff --git a/apps/collection/models.py b/apps/collection/models.py index fcffe393..5c9240ff 100644 --- a/apps/collection/models.py +++ b/apps/collection/models.py @@ -343,13 +343,15 @@ class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin): def update_count_related_objects(self, nodes: set = ('EstablishmentNode', 'WineNode')): """Update count of related guide element objects.""" - descendants = GuideElement.objects.get_root_node(self) \ - .get_descendants() - if descendants: - updated_count = descendants.filter( - guide_element_type__name__in=nodes).count() - self.count_related_objects = updated_count - self.save() + root_node = GuideElement.objects.get_root_node(self) + if root_node: + descendants = root_node.get_descendants() + + if descendants: + updated_count = descendants.filter( + guide_element_type__name__in=nodes).count() + self.count_related_objects = updated_count + self.save() def generate_elements(self): if self.guidefilter: @@ -390,6 +392,12 @@ class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin): self.state = state self.save() + def export_to_file(self, user_id: int, file_type: str): + if settings.USE_CELERY: + tasks.export_guide.delay(guide_id=self.id, user_id=user_id, file_type=file_type) + else: + tasks.export_guide(guide_id=self.id, user_id=user_id, file_type=file_type) + class AdvertorialQuerySet(models.QuerySet): """QuerySet for model Advertorial.""" diff --git a/apps/collection/serializers/common.py b/apps/collection/serializers/common.py index 8751b02b..1cee40fc 100644 --- a/apps/collection/serializers/common.py +++ b/apps/collection/serializers/common.py @@ -217,7 +217,7 @@ class AdvertorialBaseSerializer(serializers.ModelSerializer): class GuideElementExportSerializer(GuideElementBaseSerializer): """GuideElement export serializer.""" - # establishment = EstablishmentGuideElementSerializer(read_only=True,) + # ESTABLISHMENT name = serializers.CharField(source='establishment.name', default=None) public_mark = serializers.CharField(source='establishment.public_mark_display', default=None) toque_number = serializers.IntegerField(source='establishment.toque_number', default=None) @@ -239,6 +239,18 @@ class GuideElementExportSerializer(GuideElementBaseSerializer): default=None) advertorial_page = serializers.IntegerField(default=None) + # PRODUCT + product_name = serializers.CharField(source='product.name', + default=None) + product_review = serializers.DictField(source='product.establishment.last_published_review_data', + default=None) + product_type = serializers.CharField(source='product.product_type_label', + default=None) + product_subtypes = serializers.CharField(source='product.product_subtype_labels', + default=None) + product_address = serializers.CharField(source='product.establishment.address.full_address', + default=None) + class Meta: model = models.GuideElement fields = [ @@ -254,7 +266,7 @@ class GuideElementExportSerializer(GuideElementBaseSerializer): # 'section_name', # 'wine_color_section_name', # 'children', - # 'label_photo', + 'label_photo_url', 'name', 'public_mark', 'toque_number', diff --git a/apps/collection/tasks.py b/apps/collection/tasks.py index 706adb5e..c2a98608 100644 --- a/apps/collection/tasks.py +++ b/apps/collection/tasks.py @@ -86,7 +86,7 @@ def generate_product_guide_elements(guide_id: int, filter_set: dict): guide = Guide.objects.get(id=guide_id) guide.change_state(Guide.BUILDING) - queryset_values = Product.objects.filter(**filter_set).values()[0] + queryset_values = Product.objects.filter(**filter_set).values() try: for instance in queryset_values: wine_id = instance.get('id') @@ -133,7 +133,6 @@ def generate_product_guide_elements(guide_id: int, filter_set: dict): guide.change_state(Guide.WAITING) logger.error(f'METHOD_NAME: {generate_product_guide_elements.__name__}\n' f'DETAIL: Guide ID {guide_id} - {e}') - else: guide.update_count_related_objects() guide.change_state(Guide.BUILT) diff --git a/apps/collection/views/back.py b/apps/collection/views/back.py index 9588f66a..481f70da 100644 --- a/apps/collection/views/back.py +++ b/apps/collection/views/back.py @@ -205,7 +205,6 @@ class GuideElementExportDOCView(generics.ListAPIView): """Overridden get_queryset method.""" guide = get_object_or_404( models.Guide.objects.all(), pk=self.kwargs.get('pk')) - # todo: put in GuideElement model - tasks.export_guide(guide_id=guide.id, user_id=request.user.id, file_type='doc') + guide.export_to_file(user_id=request.user.id, file_type='doc') return Response({"success": _('The file will be sent to your email.')}, status=status.HTTP_200_OK) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index be932d84..b9e36b51 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -247,13 +247,13 @@ class EstablishmentQuerySet(models.QuerySet): 'establishment_type': establishment.establishment_type, 'address__city__country': establishment.address.city.country } + qs = self.exclude(id=establishment.id).filter(**filters) if establishment.establishment_subtypes.exists(): filters.update( {'establishment_subtypes__in': establishment.establishment_subtypes.all()}) - - return self.exclude(id=establishment.id) \ - .filter(**filters) \ - .annotate_distance(point=establishment.location) + if establishment.address and establishment.address.coordinates: + qs = qs.annotate_distance(point=establishment.location) + return qs def similar_base_subquery(self, establishment, filters: dict) -> Subquery: """ @@ -262,12 +262,16 @@ class EstablishmentQuerySet(models.QuerySet): 1 Filter by transmitted filters. 2 With ordering by distance. """ + qs = self.similar_base(establishment) \ + .filter(**filters) + if establishment.address and establishment.address.coordinates: + return Subquery( + qs.order_by('distance') + .distinct() + .values_list('id', flat=True)[:settings.LIMITING_QUERY_OBJECTS] + ) return Subquery( - self.similar_base(establishment) - .filter(**filters) - .order_by('distance') - .distinct() - .values_list('id', flat=True)[:settings.LIMITING_QUERY_OBJECTS] + qs.values_list('id', flat=True)[:settings.LIMITING_QUERY_OBJECTS] ) def similar_restaurants(self, restaurant): @@ -284,7 +288,6 @@ class EstablishmentQuerySet(models.QuerySet): 'establishment_gallery__is_main': True, } ) - # todo: fix this - replace ids_by_subquery.queryset on ids_by_subquery return self.filter(id__in=ids_by_subquery.queryset) \ .annotate_intermediate_public_mark() \ .annotate_mark_similarity(mark=restaurant.public_mark) \ @@ -307,12 +310,20 @@ class EstablishmentQuerySet(models.QuerySet): Return QuerySet with objects that similar to Artisan/Producer(s). :param establishment: Establishment instance """ + similarity_rules = { + 'ordering': [F('same_subtype').desc(), ], + 'distinctions': ['same_subtype', ] + } + if establishment.address and establishment.address.coordinates: + similarity_rules['ordering'].append(F('distance').asc()) + similarity_rules['distinctions'].append('distance') + return self.similar_base(establishment) \ .same_subtype(establishment) \ .has_published_reviews() \ - .order_by(F('same_subtype').desc(), - F('distance').asc()) \ - .distinct('same_subtype', 'distance', 'id') + .order_by(similarity_rules['ordering']) \ + .distinct(similarity_rules['distinctions'], + 'id') def by_wine_region(self, wine_region): """ @@ -333,14 +344,20 @@ class EstablishmentQuerySet(models.QuerySet): Return QuerySet with objects that similar to Winery. :param winery: Establishment instance """ + similarity_rules = { + 'ordering': [F('wine_origins__wine_region').asc(), + F('wine_origins__wine_sub_region').asc()], + 'distinctions': ['wine_origins__wine_region', + 'wine_origins__wine_sub_region'] + } + if winery.address and winery.address.coordinates: + similarity_rules['ordering'].append(F('distance').asc()) + similarity_rules['distinctions'].append('distance') + return self.similar_base(winery) \ - .order_by(F('wine_origins__wine_region').asc(), - F('wine_origins__wine_sub_region').asc(), - F('distance').asc()) \ - .distinct('wine_origins__wine_region', - 'wine_origins__wine_sub_region', - 'distance', - 'id') + .order_by(*similarity_rules['ordering']) \ + .distinct(*similarity_rules['distinctions'], + 'id') def last_reviewed(self, point: Point): """ diff --git a/apps/product/models.py b/apps/product/models.py index be99e5b8..2740be24 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -16,6 +16,7 @@ from utils.models import (BaseAttributes, ProjectBaseMixin, HasTagsMixin, TranslatedFieldsMixin, TJSONField, FavoritesMixin, GalleryMixin, IntermediateGalleryModelMixin, TypeDefaultImageMixin) +from utils.methods import transform_into_readable_str class ProductType(TypeDefaultImageMixin, TranslatedFieldsMixin, ProjectBaseMixin): @@ -188,28 +189,32 @@ class ProductQuerySet(models.QuerySet): 'reviews__status': Review.READY, 'product_type': product.product_type, } + qs = self.exclude(id=product.id) \ + .filter(**filters) if product.subtypes.exists(): filters.update( {'subtypes__in': product.subtypes.all()}) - return self.exclude(id=product.id) \ - .filter(**filters) \ - .annotate_distance(point=product.establishment.location) + if product.establishment.address and product.establishment.location: + qs = qs.annotate_distance(point=product.establishment.location) + return qs - def similar(self, slug): + def similar(self, product): """ Return QuerySet with objects that similar to Product. - :param slug: str product slug + :param product: instance of Product model. """ - product_qs = self.filter(slug=slug) - if product_qs.exists(): - product = product_qs.first() - return self.similar_base(product) \ - .same_subtype(product) \ - .order_by(F('same_subtype').desc(), - F('distance').asc()) \ - .distinct('same_subtype', 'distance', 'id') - else: - return self.none() + similarity_rules = { + 'ordering': [F('same_subtype').desc(), ], + 'distinction': ['same_subtype', ] + } + if product.establishment.address and product.establishment.location: + similarity_rules['ordering'].append(F('distance').asc()) + similarity_rules['distinction'].append('distance') + return self.similar_base(product) \ + .same_subtype(product) \ + .order_by(*similarity_rules['ordering']) \ + .distinct(*similarity_rules['distinction'], + 'id') class Product(GalleryMixin, TranslatedFieldsMixin, BaseAttributes, @@ -389,6 +394,22 @@ class Product(GalleryMixin, TranslatedFieldsMixin, BaseAttributes, if self.wine_origins.exists(): return self.wine_origins.first().wine_region + @property + def last_published_review_data(self): + if self.last_published_review: + return self.last_published_review.text + + @property + def establishment_subtype_labels(self): + if self.subtypes: + return [transform_into_readable_str(label) + for label in self.subtypes.all().values_list('index_name', flat=True)] + + @property + def wine_region_label(self): + if self.wine_region: + return self.wine_region.name + class OnlineProductManager(ProductManager): """Extended manger for OnlineProduct model.""" diff --git a/apps/product/views/common.py b/apps/product/views/common.py index 59a735c4..34990cc3 100644 --- a/apps/product/views/common.py +++ b/apps/product/views/common.py @@ -110,6 +110,6 @@ class SimilarListView(ProductSimilarView): base_product = self.get_base_object() if base_product: - return qs.has_location().similar(slug=self.kwargs.get('slug'))[:settings.QUERY_OUTPUT_OBJECTS] + return qs.has_location().similar(base_product)[:settings.QUERY_OUTPUT_OBJECTS] else: return qs.none() diff --git a/apps/utils/export.py b/apps/utils/export.py index 49afd07d..a72d85d6 100644 --- a/apps/utils/export.py +++ b/apps/utils/export.py @@ -388,12 +388,6 @@ class SendGuideExport(SendExportBase): document.document.save(self.file_path) self.success = True - def get_headers(self): - headers = list(self.data[0].keys()) - headers.pop(headers.index('node_name')) - self.success = True - return headers - def get_data(self): return self.data @@ -432,6 +426,34 @@ class SendGuideExport(SendExportBase): print(f'ok: {self.file_path}') self.send_email() + def get_headers(self): + """Get headers for model Establishment.""" + exclude_headers = ['node_name', ] + headers = list(self.data[0].keys()) + + if self.guide.RESTAURANT or self.guide.ARTISAN: + exclude_headers.append('product_name', ) + if self.guide.WINE: + exclude_headers.extend([ + 'name', + 'public_mark', + 'toque_number', + 'schedule', + 'address', + 'phones', + 'establishment_type', + 'establishment_subtypes', + 'review', + 'price_level', + 'metadata', + ]) + + for name in set(exclude_headers): + headers.pop(headers.index(name)) + else: + self.success = True + return headers + def make_csv_file(self): file_header = self.get_headers() if not self.success: