From b41c185a653c7f7f80da7cb3331609b55408a69c Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 27 Dec 2019 14:46:36 +0300 Subject: [PATCH] see changes --- apps/collection/models.py | 31 ++-- apps/collection/serializers/common.py | 114 ++++++------- apps/collection/tasks.py | 15 +- apps/collection/views/back.py | 13 +- apps/establishment/models.py | 65 ++++++++ apps/location/models.py | 7 + apps/tag/models.py | 2 +- apps/utils/export.py | 229 +++++++++++++++++++++++++- apps/utils/methods.py | 18 +- requirements/base.txt | 3 + 10 files changed, 406 insertions(+), 91 deletions(-) diff --git a/apps/collection/models.py b/apps/collection/models.py index 76d76d62..c871f634 100644 --- a/apps/collection/models.py +++ b/apps/collection/models.py @@ -15,7 +15,7 @@ from product.models import Product from review.models import Review from collection import tasks from translation.models import Language -from utils.methods import slug_into_section_name +from utils.methods import transform_into_readable_str from utils.models import ( ProjectBaseMixin, TJSONField, TranslatedFieldsMixin, URLImageMixin, IntermediateGalleryModelMixin @@ -357,25 +357,23 @@ class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin): if settings.USE_CELERY: tasks.generate_establishment_guide_elements.delay( guide_id=self.id, - queryset_values=self.guidefilter.filtered_queryset_values, - section_node_name='RestaurantSectionNode' if self.RESTAURANT else self.ARTISAN + filter_set=self.guidefilter.establishment_filter_set, ) else: tasks.generate_establishment_guide_elements( guide_id=self.id, - queryset_values=self.guidefilter.filtered_queryset_values, - section_node_name='RestaurantSectionNode' if self.RESTAURANT else self.ARTISAN + filter_set=self.guidefilter.establishment_filter_set, ) elif self.guide_type == self.WINE: if settings.USE_CELERY: tasks.generate_product_guide_elements.delay( guide_id=self.id, - queryset_values=self.guidefilter.filtered_queryset_values, + filter_set=self.guidefilter.product_filter_set, ) else: tasks.generate_product_guide_elements( guide_id=self.id, - queryset_values=self.guidefilter.filtered_queryset_values, + filter_set=self.guidefilter.product_filter_set, ) def regenerate_elements(self): @@ -388,6 +386,10 @@ class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin): # update count elements self.update_count_related_objects() + def change_state(self, state: int): + self.state = state + self.save() + class AdvertorialQuerySet(models.QuerySet): """QuerySet for model Advertorial.""" @@ -469,7 +471,7 @@ class GuideFilter(ProjectBaseMixin): """ value_list = [] - if hasattr(model, 'objects'): + if hasattr(model, 'objects') and json_field: for value in getattr(json_field, 'get')(search_field): qs = model.objects.filter(**{search_field: value}) if qs.exists(): @@ -668,17 +670,6 @@ class GuideFilter(ProjectBaseMixin): return filters - @property - def filtered_queryset_values(self): - if self.guide.guide_type in [self.guide.RESTAURANT, self.guide.ARTISAN]: - fields = Establishment._meta.get_all_field_names() - fields.pop('tz') - return Establishment.objects.filter(**self.establishment_filter_set) \ - .values_list()[0] - elif self.guide.guide_type == self.guide.WINE: - return Product.objects.filter(**self.product_filter_set) \ - .values()[0] - class GuideElementType(models.Model): """Model for type of guide elements.""" @@ -861,7 +852,7 @@ class GuideElementManager(models.Manager): parent_node_qs = GuideElement.objects.filter(id=yard_node_id) if not wine_color_name.endswith('SectionNode'): - wine_color_name = slug_into_section_name(wine_color_name) + wine_color_name = transform_into_readable_str(wine_color_name) wine_color_section, _ = GuideWineColorSection.objects.get_or_create( name=wine_color_name, diff --git a/apps/collection/serializers/common.py b/apps/collection/serializers/common.py index 32d97a3d..449ee293 100644 --- a/apps/collection/serializers/common.py +++ b/apps/collection/serializers/common.py @@ -3,6 +3,7 @@ from rest_framework import serializers from rest_framework_recursive.fields import RecursiveField from collection import models +from review.serializers import ReviewBaseSerializer from establishment.serializers import EstablishmentGuideElementSerializer from location import models as location_models from main.serializers import SiteShortSerializer @@ -53,24 +54,46 @@ class CollectionSerializer(CollectionBaseSerializer): ] -class GuideFilterBaseSerialzer(serializers.ModelSerializer): - """Serializer for model GuideFilter.""" +class GuideFilterBaseSerializer(serializers.ModelSerializer): + """GuideFilter serializer""" class Meta: """Meta class.""" model = models.GuideFilter fields = [ - 'establishment_types', - 'locales', - 'review_states', - 'country_names', - 'region_names', - 'sub_region_names', - 'review_vintages', + 'id', + 'establishment_type_json', + 'country_json', + 'region_json', + 'sub_region_json', + 'wine_region_json', + 'with_mark', + 'locale_json', 'max_mark', 'min_mark', - 'with_mark', + 'review_vintage_json', + 'review_state_json', + 'guide', ] + extra_kwargs = { + 'guide': {'write_only': True, 'required': False}, + 'max_mark': {'required': True}, + 'min_mark': {'required': True}, + } + + @property + def request_kwargs(self): + """Get url kwargs from request.""" + return self.context.get('request').parser_context.get('kwargs') + + def create(self, validated_data): + """Overridden create method.""" + guide = get_object_or_404(models.Guide.objects.all(), + pk=self.request_kwargs.get('pk')) + validated_data['guide'] = guide + guide_filter = super().create(validated_data) + guide.generate_elements() + return guide_filter class GuideBaseSerializer(serializers.ModelSerializer): @@ -80,8 +103,8 @@ class GuideBaseSerializer(serializers.ModelSerializer): guide_type_display = serializers.CharField(read_only=True) site_detail = SiteShortSerializer(read_only=True, source='site') - guide_filters = GuideFilterBaseSerialzer(read_only=True, - source='guidefilter') + guide_filters = GuideFilterBaseSerializer(read_only=True, + source='guidefilter') # counters restaurant_counter = serializers.IntegerField(read_only=True) shop_counter = serializers.IntegerField(read_only=True) @@ -121,46 +144,6 @@ class GuideBaseSerializer(serializers.ModelSerializer): } -class GuideFilterBaseSerializer(serializers.ModelSerializer): - """GuideFilter serializer""" - - class Meta: - """Meta class.""" - model = models.GuideFilter - fields = [ - 'id', - 'establishment_type_json', - 'country_json', - 'region_json', - 'sub_region_json', - 'wine_region_json', - 'with_mark', - 'locale_json', - 'max_mark', - 'min_mark', - 'review_vintage_json', - 'review_state_json', - 'guide', - ] - extra_kwargs = { - 'guide': {'write_only': True, 'required': False}, - } - - @property - def request_kwargs(self): - """Get url kwargs from request.""" - return self.context.get('request').parser_context.get('kwargs') - - def create(self, validated_data): - """Overridden create method.""" - guide = get_object_or_404(models.Guide.objects.all(), - pk=self.request_kwargs.get('pk')) - validated_data['guide'] = guide - guide_filter = super().create(validated_data) - guide.generate_elements() - return guide_filter - - class GuideElementBaseSerializer(serializers.ModelSerializer): """Serializer for model GuideElement.""" establishment_detail = EstablishmentGuideElementSerializer(read_only=True, @@ -236,8 +219,22 @@ class GuideElementExportSerializer(GuideElementBaseSerializer): """GuideElement export serializer.""" # establishment = EstablishmentGuideElementSerializer(read_only=True,) name = serializers.CharField(source='establishment.name', default=None) - public_mark = serializers.CharField(source='establishment.public_mark', default=None) + public_mark = serializers.CharField(source='establishment.public_mark_display', default=None) toque_number = serializers.CharField(source='establishment.toque_number', default=None) + schedule = serializers.DictField(source='establishment.schedule_display', + default=None) + address = serializers.CharField(source='establishment.address.full_address', + default=None) + phones = serializers.ListField(source='establishment.contact_phones', + default=None) + establishment_type = serializers.CharField(source='establishment.establishment_type.label', + default=None) + establishment_subtypes = serializers.ListField(source='establishment.establishment_subtype_labels', + default=None) + review = serializers.DictField(source='establishment.last_published_review_data', + default=None) + price_level = serializers.CharField(source='establishment.price_level_display', + default=None) class Meta: model = models.GuideElement @@ -256,6 +253,13 @@ class GuideElementExportSerializer(GuideElementBaseSerializer): # 'label_photo', 'name', 'public_mark', - 'toque_number' + 'toque_number', + 'schedule', + 'address', + 'phones', + 'establishment_type', + 'establishment_subtypes', + 'review', + 'price_level', - ] \ No newline at end of file + ] diff --git a/apps/collection/tasks.py b/apps/collection/tasks.py index 6eb9fcdd..eab7e570 100644 --- a/apps/collection/tasks.py +++ b/apps/collection/tasks.py @@ -2,6 +2,7 @@ import logging from celery import shared_task +from utils.methods import transform_into_readable_str logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.INFO) logger = logging.getLogger(__name__) @@ -28,12 +29,14 @@ def get_additional_product_data(section_node, product): @shared_task -def generate_establishment_guide_elements(guide_id: int, queryset_values: dict, section_node_name: str): +def generate_establishment_guide_elements(guide_id: int, filter_set: dict): """Generate guide elements.""" from collection.models import GuideElement, Guide from establishment.models import Establishment guide = Guide.objects.get(id=guide_id) + guide.change_state(Guide.BUILDING) + queryset_values = Establishment.objects.filter(**filter_set).values() try: for instance in queryset_values: establishment_id = instance.get('id') @@ -47,7 +50,7 @@ def generate_establishment_guide_elements(guide_id: int, queryset_values: dict, if city_node: section_node, _ = GuideElement.objects.get_or_create_establishment_section_node( city_node.id, - section_node_name, + transform_into_readable_str(establishment.establishment_type.index_name), ) if section_node: GuideElement.objects.get_or_create_establishment_node( @@ -67,19 +70,23 @@ def generate_establishment_guide_elements(guide_id: int, queryset_values: dict, logger.error(f'METHOD_NAME: {generate_establishment_guide_elements.__name__}\n' f'DETAIL: Guide ID {guide_id} - Establishment {establishment_id} id is not exists.') except Exception as e: + guide.change_state(Guide.WAITING) logger.error(f'METHOD_NAME: {generate_establishment_guide_elements.__name__}\n' f'DETAIL: Guide ID {guide_id} - {e}') else: guide.update_count_related_objects() + guide.change_state(Guide.BUILT) @shared_task -def generate_product_guide_elements(guide_id: int, queryset_values: dict): +def generate_product_guide_elements(guide_id: int, filter_set: dict): """Generate guide elements.""" from collection.models import GuideElement, Guide from product.models import Product guide = Guide.objects.get(id=guide_id) + guide.change_state(Guide.BUILDING) + queryset_values = Product.objects.filter(**filter_set).values()[0] try: for instance in queryset_values: wine_id = instance.get('id') @@ -123,10 +130,12 @@ def generate_product_guide_elements(guide_id: int, queryset_values: dict): logger.error(f'METHOD_NAME: {generate_product_guide_elements.__name__}\n' f'DETAIL: Guide ID {guide_id} - Product {wine_id} id is not exists.') except Exception as e: + 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) @shared_task diff --git a/apps/collection/views/back.py b/apps/collection/views/back.py index 2cce236d..73e650b5 100644 --- a/apps/collection/views/back.py +++ b/apps/collection/views/back.py @@ -107,9 +107,6 @@ class CollectionBackOfficeViewSet(mixins.CreateModelMixin, class GuideListCreateView(GuideBaseView, generics.ListCreateAPIView): """View for Guide model for BackOffice users.""" - def post(self, request, *args, **kwargs): - super().create(request, *args, **kwargs) - return Response(status=status.HTTP_200_OK) class GuideFilterCreateView(GuideFilterBaseView, @@ -137,7 +134,7 @@ class GuideElementListView(GuideElementBaseView, class GuideUpdateView(GuideBaseView): """View for model GuideElement for back office users.""" - def post(self, request, *args, **kwargs): + def get(self, request, *args, **kwargs): """POST-method to regenerate elements of guide instance.""" guide = get_object_or_404(models.Guide.objects.all(), pk=self.kwargs.get('pk')) @@ -195,3 +192,11 @@ class GuideElementExportDOCView(generics.ListAPIView): queryset = models.GuideElement.objects.all() serializer_class = serializers.GuideElementBaseSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly,) + + def get(self, request, *args, **kwargs): + """Overridden get_queryset method.""" + guide = get_object_or_404( + models.Guide.objects.all(), pk=self.kwargs.get('pk')) + tasks.export_guide(guide_id=guide.id, 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 81dd691b..1acffeb1 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -21,10 +21,12 @@ from phonenumber_field.modelfields import PhoneNumberField from timezone_field import TimeZoneField from location.models import Address +from timetable.models import Timetable from location.models import WineOriginAddressMixin from main.models import Award, Currency from review.models import Review from tag.models import Tag +from utils.methods import transform_into_readable_str from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin, TranslatedFieldsMixin, BaseAttributes, GalleryMixin, IntermediateGalleryModelMixin, HasTagsMixin, @@ -61,6 +63,10 @@ class EstablishmentType(TypeDefaultImageMixin, TranslatedFieldsMixin, ProjectBas verbose_name = _('Establishment type') verbose_name_plural = _('Establishment types') + @property + def label(self): + return transform_into_readable_str(self.index_name) + class EstablishmentSubTypeManager(models.Manager): """Extended manager for establishment subtype.""" @@ -737,6 +743,65 @@ class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin, def wine_origins_unique(self): return self.wine_origins.distinct('wine_region') + @property + def contact_phones(self): + if self.phones: + return [phone.as_e164 for phone in self.phones.all()] + + @property + def establishment_subtype_labels(self): + if self.establishment_subtypes: + return [transform_into_readable_str(label) + for label in self.establishment_subtypes.all().values_list('index_name', flat=True)] + + @property + def schedule_display(self): + if self.schedule: + timetable = {} + for weekday, closed_at, opening_at in self.schedule.all().values_list('weekday', 'closed_at', 'opening_at'): + weekday = dict(Timetable.WEEKDAYS_CHOICES).get(weekday) + working_hours = 'Closed' + if closed_at and opening_at: + working_hours = f'{str(opening_at)[:-3]} - {str(closed_at)[:-3]}' + timetable.update({weekday: working_hours}) + return timetable + + @property + def price_level_display(self): + if self.get_price_level() and self.currency: + min_value, max_value = self.get_price_level() + currency = self.currency.sign + return f'From {min_value}{currency} to {max_value}{currency}' + + @property + def last_published_review_data(self): + if self.last_published_review: + return self.last_published_review.text + + @property + def public_mark_display(self): + return f'{self.public_mark}/20' + + @property + def metadata(self): + if self.establishment_type: + metadata = [] + tag_categories = ( + self.establishment_type.tag_categories.exclude(index_name__in=[ + 'business_tag', 'purchased_item', 'accepted_payments_de', + 'accepted_payments_hr', 'drinks', 'bottles_per_year', + 'serial_number', 'surface', 'cooperative', 'tag']).values_list('index_name', flat=True) + ) + for category in tag_categories: + tags = self.tags.filter(category__index_name=category).values_list('value', flat=True) + + if tags.exists(): + category_tags = {category: []} + for tag in tags: + category_tags[category].append(tag) + metadata.append(category_tags) + return metadata + class EstablishmentNoteQuerySet(models.QuerySet): """QuerySet for model EstablishmentNote.""" diff --git a/apps/location/models.py b/apps/location/models.py index 600aa07a..942d78d0 100644 --- a/apps/location/models.py +++ b/apps/location/models.py @@ -241,6 +241,13 @@ class Address(models.Model): def country_id(self): return self.city.country_id + @property + def full_address(self): + full_address = self.get_street_name() + if self.number and int(self.number): + full_address = f'{self.number} {self.get_street_name()}' + return full_address + class WineRegionQuerySet(models.QuerySet): """Wine region queryset.""" diff --git a/apps/tag/models.py b/apps/tag/models.py index 4d0ab43a..1b735724 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -65,7 +65,7 @@ class Tag(models.Model): verbose_name_plural = _('Tags') def __str__(self): - return f'Tag (id = {self.id}, label_translated = {self.label_translated})' + return f'Tag (id = {self.id}, label_translated = {self.value})' class ChosenTagSettingsQuerySet(models.QuerySet): diff --git a/apps/utils/export.py b/apps/utils/export.py index 71fa956a..2ec5854d 100644 --- a/apps/utils/export.py +++ b/apps/utils/export.py @@ -1,18 +1,202 @@ +import abc import csv -import xlsxwriter import logging import os import tempfile from smtplib import SMTPException +import docx +import xlsxwriter from django.conf import settings from django.core.mail import EmailMultiAlternatives -import abc +from docx.blkcntnr import BlockItemContainer +from timetable.models import Timetable +from docx.shared import RGBColor, Pt +from utils.methods import section_name_into_index_name logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.INFO) logger = logging.getLogger(__name__) +class DocTemplate: + + DOCUMENT_FONT_NAME = 'Palatino' + DOCUMENT_FONT_SIZE = Pt(12) + DOCUMENT_FONT_COLOR = RGBColor(0, 0, 0) + + def __init__(self): + self.document = docx.Document() + style = self.document.styles['Normal'] + style.paragraph_format.space_before = Pt(12) + style.font.size = self.DOCUMENT_FONT_SIZE + style.font.name = self.DOCUMENT_FONT_NAME + style.font.color.rgb = self.DOCUMENT_FONT_COLOR + + def add_page_break(self): + self.document.add_page_break() + + def add_empty_line(self): + self.document.add_paragraph() + self.document.add_paragraph() + + def add_horizontal_line(self): + return self.document.add_paragraph(f'{"_" * 50}') + + def add_bullet_list(self, elements: (tuple, list)): + styles = { + 'name': 'Arial', + 'size': Pt(10) + } + for element in elements: + bullet = self.document.add_paragraph(style='List Bullet').add_run(element) + self.apply_font_style(bullet, params=styles) + + def add_heading(self, name: str, level: int = 2, font_style: dict = None, + color_rgb: tuple = (0, 0, 0)): + heading = self.document.add_heading(level=level).add_run(name) + self.apply_font_style(heading, font_style) + if color_rgb: + self.apply_font_color(heading, color_rgb) + + def add_paragraph(self, name: str, font_style: dict = None, color_rgb: tuple = (0, 0, 0)): + paragraph = self.document.add_paragraph().add_run(name) + self.apply_font_style(paragraph, font_style) + if color_rgb: + self.apply_font_color(paragraph, color_rgb) + + def apply_font_style(self, section, params: dict): + for attr, value in params.items(): + if not hasattr(section, 'font'): + continue + setattr(section.font, attr, value) + + def apply_font_color(self, section, color_rgb: tuple): + if len(color_rgb) == 3: + font = getattr(section, 'font', None) + color = getattr(font, 'color', None) + if font and color: + setattr(color, 'rgb', RGBColor(*color_rgb)) + + def template(self, data: list): + + for instance in data: + instance = dict(instance) + index_name = section_name_into_index_name(instance.get('section_name')) + + # ESTABLISHMENT HEADING (LEVEL 1) + self.add_heading(name=instance['name'], + font_style={'size': Pt(18), 'name': 'Palatino', 'bold': False}, + level=1) + # ESTABLISHMENT TYPE PARAGRAPH + self.add_paragraph(name=index_name, + font_style={'size': Pt(8), 'name': 'Palatino', 'bold': False, 'italic': False}) + self.add_empty_line() + + # CITY HEADING (LEVEL 2) + self.add_heading(name='City', + font_style={'size': Pt(13), 'name': 'Arial', 'bold': True}, + level=2) + # CITY NAME HEADING (LEVEL 3) + self.add_heading(name=instance['city_name'], + font_style={'size': Pt(12), 'name': 'Arial', 'bold': True, 'italic': True}, + color_rgb=(102, 102, 102)) + self.add_empty_line() + + # REVIEW HEADING (LEVEL 2) + review = instance.get('review') + if review: + self.add_heading(name='Review', + font_style={'size': Pt(13), 'name': 'Arial', 'bold': True}, + level=2) + for locale, text in review.items(): + # REVIEW LOCALE HEADING (LEVEL 6) + self.add_heading(name=locale, + font_style={'size': Pt(11), 'name': 'Arial', 'underline': True, 'italic': True}, + color_rgb=(102, 102, 102), + level=6) + # REVIEW TEXT PARAGRAPH + self.add_paragraph(name=text, + font_style={'size': Pt(10), 'name': 'Arial'}) + + # PHONE HEADING (LEVEL 2) + phones = instance.get('phones') + if phones: + self.add_heading(name='Phones', + font_style={'size': Pt(13), 'name': 'Arial', 'bold': True}, + level=2) + # PHONE NUMBER PARAGRAPH + self.add_bullet_list(phones) + self.add_empty_line() + + # ADDRESS HEADING (LEVEL 2) + address = instance.get('address') + if address: + self.add_heading(name='Address', + font_style={'size': Pt(13), 'name': 'Arial', 'bold': True}, + level=2) + # ADDRESS DATA PARAGRAPH + self.add_paragraph(name=instance.get('address'), + font_style={'size': Pt(10), 'name': 'Arial'}) + self.add_empty_line() + + # TIMETABLE HEADING (LEVEL 2) + schedule = instance.get('schedule') + if schedule: + self.add_heading(name='Schedule', + font_style={'size': Pt(13), 'name': 'Arial', 'bold': True}, + level=2) + # TIMETABLE ITEMS PARAGRAPH + for weekday, working_hours in schedule.items(): + bullet = self.document.add_paragraph(style='List Bullet').add_run(f'{weekday}: {working_hours}') + self.apply_font_style(bullet, {'name': 'Arial', 'size': Pt(10)}) + self.add_empty_line() + + # PUBLIC MARK HEADING (LEVEL 2) + public_mark = instance.get('public_mark') + if public_mark: + self.add_heading(name='Mark', + font_style={'size': Pt(13), 'name': 'Arial', 'bold': True}, + level=2) + self.add_heading(name=public_mark, + font_style={'size': Pt(10), 'name': 'Arial'}, + level=2) + + # TOQUE HEADING (LEVEL 2) + toque = instance.get('toque_number') + if toque: + self.add_heading(name='Toque', + font_style={'size': Pt(13), 'name': 'Arial', 'bold': True}, + level=2) + self.add_heading(name=toque, + font_style={'size': Pt(10), 'name': 'Arial'}, + level=2) + + # TOQUE HEADING (LEVEL 2) + price_level = instance.get('price_level') + if price_level: + self.add_heading(name='Price level', + font_style={'size': Pt(13), 'name': 'Arial', 'bold': True}, + level=2) + self.add_heading(name=price_level, + font_style={'size': Pt(10), 'name': 'Arial'}, + level=2) + + # SERVICES HEADING (LEVEL 2) + services = instance.get('services') + if services: + self.add_heading(name='Services', + font_style={'size': Pt(13), 'name': 'Arial', 'bold': True}, + level=2) + # TIMETABLE ITEMS PARAGRAPH + self.add_bullet_list(services) + self.add_empty_line() + + break + + # PAGE BREAK + self.add_page_break() + + class SendExportBase: """Base class of export and sending data.""" @@ -38,8 +222,9 @@ class SendExportBase: @abc.abstractmethod def get_emails_to(self): return [ - 'kuzmenko.da@gmail.com', - 'sinapsit@yandex.ru' + 'a.feteleu@spider.ru', + # 'kuzmenko.da@gmail.com', + # 'sinapsit@yandex.ru' ] @abc.abstractmethod @@ -82,6 +267,9 @@ class SendExportBase: def make_xml_file(self): pass + def make_doc_file(self): + pass + def send_email(self): msg = EmailMultiAlternatives( @@ -165,12 +353,18 @@ class SendGuideExport(SendExportBase): self.get_file_method = self.type_mapper[file_type] super().__init__() + def get_emails_to(self): + return [self.user.email] + super().get_emails_to() + def get_file_name(self): name = self.guide.slug return f'export_{name}.{self.file_type}' def make_doc_file(self): - pass + document = DocTemplate() + document.template(self.get_doc_data()) + document.document.save(self.file_path) + self.success = True def get_headers(self): headers = list(self.data[0].keys()) @@ -181,6 +375,29 @@ class SendGuideExport(SendExportBase): def get_data(self): return self.data + def get_doc_data(self): + init_data = self.get_data() + objects = [] + city_name = None + section_name = None + + for instance in init_data: + row_city = instance.get('city_name') + if row_city: + city_name = row_city + else: + instance['city_name'] = city_name + + row_section = instance.get('node_name') + if row_section.endswith('SectionNode'): + section_name = row_section + else: + instance['section_name'] = section_name + + if instance.pop('node_name', None) == 'EstablishmentNode': + objects.append(instance.items()) + return objects + def send(self): self.get_file_method() print(f'ok: {self.file_path}') @@ -204,4 +421,4 @@ class SendGuideExport(SendExportBase): row['city_name'] = city if row.pop("node_name") == "EstablishmentNode": - file_writer.writerow(row.values()) \ No newline at end of file + file_writer.writerow(row.values()) diff --git a/apps/utils/methods.py b/apps/utils/methods.py index 2e81990a..c925d447 100644 --- a/apps/utils/methods.py +++ b/apps/utils/methods.py @@ -143,7 +143,7 @@ def dictfetchall(cursor): ] -def slug_into_section_name(slug: str, postfix: str = 'SectionNode'): +def transform_into_readable_str(raw_string: str, postfix: str = 'SectionNode'): """ Transform slug into section name, i.e: like @@ -152,6 +152,20 @@ def slug_into_section_name(slug: str, postfix: str = 'SectionNode'): "effervescent-rose-de-saignee" """ re_exp = r'[\w]+' - result = re.findall(re_exp, slug) + result = re.findall(re_exp, raw_string) if result: return f"{''.join([i.capitalize() for i in result])}{postfix}" + + +def section_name_into_index_name(section_name: str): + """ + Transform slug into section name, i.e: + like + "Restaurant" + from + "RestaurantSectionNode" + """ + re_exp = r'[A-Z][^A-Z]*' + result = re.findall(re_exp, section_name) + if result: + return f"{' '.join([word.capitalize() if i == 0 else word for i, word in enumerate(result[:-2])])}" diff --git a/requirements/base.txt b/requirements/base.txt index 6a96cc98..f5bbf2d5 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -70,5 +70,8 @@ python-slugify==4.0.0 # Export to Excel XlsxWriter==1.2.6 +# Export to Doc +python-docx==0.8.10 + # For recursive fields djangorestframework-recursive==0.1.2