see changes
This commit is contained in:
parent
02d0112d8b
commit
b41c185a65
|
|
@ -15,7 +15,7 @@ from product.models import Product
|
||||||
from review.models import Review
|
from review.models import Review
|
||||||
from collection import tasks
|
from collection import tasks
|
||||||
from translation.models import Language
|
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 (
|
from utils.models import (
|
||||||
ProjectBaseMixin, TJSONField, TranslatedFieldsMixin,
|
ProjectBaseMixin, TJSONField, TranslatedFieldsMixin,
|
||||||
URLImageMixin, IntermediateGalleryModelMixin
|
URLImageMixin, IntermediateGalleryModelMixin
|
||||||
|
|
@ -357,25 +357,23 @@ class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin):
|
||||||
if settings.USE_CELERY:
|
if settings.USE_CELERY:
|
||||||
tasks.generate_establishment_guide_elements.delay(
|
tasks.generate_establishment_guide_elements.delay(
|
||||||
guide_id=self.id,
|
guide_id=self.id,
|
||||||
queryset_values=self.guidefilter.filtered_queryset_values,
|
filter_set=self.guidefilter.establishment_filter_set,
|
||||||
section_node_name='RestaurantSectionNode' if self.RESTAURANT else self.ARTISAN
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
tasks.generate_establishment_guide_elements(
|
tasks.generate_establishment_guide_elements(
|
||||||
guide_id=self.id,
|
guide_id=self.id,
|
||||||
queryset_values=self.guidefilter.filtered_queryset_values,
|
filter_set=self.guidefilter.establishment_filter_set,
|
||||||
section_node_name='RestaurantSectionNode' if self.RESTAURANT else self.ARTISAN
|
|
||||||
)
|
)
|
||||||
elif self.guide_type == self.WINE:
|
elif self.guide_type == self.WINE:
|
||||||
if settings.USE_CELERY:
|
if settings.USE_CELERY:
|
||||||
tasks.generate_product_guide_elements.delay(
|
tasks.generate_product_guide_elements.delay(
|
||||||
guide_id=self.id,
|
guide_id=self.id,
|
||||||
queryset_values=self.guidefilter.filtered_queryset_values,
|
filter_set=self.guidefilter.product_filter_set,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
tasks.generate_product_guide_elements(
|
tasks.generate_product_guide_elements(
|
||||||
guide_id=self.id,
|
guide_id=self.id,
|
||||||
queryset_values=self.guidefilter.filtered_queryset_values,
|
filter_set=self.guidefilter.product_filter_set,
|
||||||
)
|
)
|
||||||
|
|
||||||
def regenerate_elements(self):
|
def regenerate_elements(self):
|
||||||
|
|
@ -388,6 +386,10 @@ class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin):
|
||||||
# update count elements
|
# update count elements
|
||||||
self.update_count_related_objects()
|
self.update_count_related_objects()
|
||||||
|
|
||||||
|
def change_state(self, state: int):
|
||||||
|
self.state = state
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
|
||||||
class AdvertorialQuerySet(models.QuerySet):
|
class AdvertorialQuerySet(models.QuerySet):
|
||||||
"""QuerySet for model Advertorial."""
|
"""QuerySet for model Advertorial."""
|
||||||
|
|
@ -469,7 +471,7 @@ class GuideFilter(ProjectBaseMixin):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
value_list = []
|
value_list = []
|
||||||
if hasattr(model, 'objects'):
|
if hasattr(model, 'objects') and json_field:
|
||||||
for value in getattr(json_field, 'get')(search_field):
|
for value in getattr(json_field, 'get')(search_field):
|
||||||
qs = model.objects.filter(**{search_field: value})
|
qs = model.objects.filter(**{search_field: value})
|
||||||
if qs.exists():
|
if qs.exists():
|
||||||
|
|
@ -668,17 +670,6 @@ class GuideFilter(ProjectBaseMixin):
|
||||||
|
|
||||||
return filters
|
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):
|
class GuideElementType(models.Model):
|
||||||
"""Model for type of guide elements."""
|
"""Model for type of guide elements."""
|
||||||
|
|
@ -861,7 +852,7 @@ class GuideElementManager(models.Manager):
|
||||||
parent_node_qs = GuideElement.objects.filter(id=yard_node_id)
|
parent_node_qs = GuideElement.objects.filter(id=yard_node_id)
|
||||||
|
|
||||||
if not wine_color_name.endswith('SectionNode'):
|
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(
|
wine_color_section, _ = GuideWineColorSection.objects.get_or_create(
|
||||||
name=wine_color_name,
|
name=wine_color_name,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ from rest_framework import serializers
|
||||||
from rest_framework_recursive.fields import RecursiveField
|
from rest_framework_recursive.fields import RecursiveField
|
||||||
|
|
||||||
from collection import models
|
from collection import models
|
||||||
|
from review.serializers import ReviewBaseSerializer
|
||||||
from establishment.serializers import EstablishmentGuideElementSerializer
|
from establishment.serializers import EstablishmentGuideElementSerializer
|
||||||
from location import models as location_models
|
from location import models as location_models
|
||||||
from main.serializers import SiteShortSerializer
|
from main.serializers import SiteShortSerializer
|
||||||
|
|
@ -53,24 +54,46 @@ class CollectionSerializer(CollectionBaseSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class GuideFilterBaseSerialzer(serializers.ModelSerializer):
|
class GuideFilterBaseSerializer(serializers.ModelSerializer):
|
||||||
"""Serializer for model GuideFilter."""
|
"""GuideFilter serializer"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
model = models.GuideFilter
|
model = models.GuideFilter
|
||||||
fields = [
|
fields = [
|
||||||
'establishment_types',
|
'id',
|
||||||
'locales',
|
'establishment_type_json',
|
||||||
'review_states',
|
'country_json',
|
||||||
'country_names',
|
'region_json',
|
||||||
'region_names',
|
'sub_region_json',
|
||||||
'sub_region_names',
|
'wine_region_json',
|
||||||
'review_vintages',
|
'with_mark',
|
||||||
|
'locale_json',
|
||||||
'max_mark',
|
'max_mark',
|
||||||
'min_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):
|
class GuideBaseSerializer(serializers.ModelSerializer):
|
||||||
|
|
@ -80,8 +103,8 @@ class GuideBaseSerializer(serializers.ModelSerializer):
|
||||||
guide_type_display = serializers.CharField(read_only=True)
|
guide_type_display = serializers.CharField(read_only=True)
|
||||||
site_detail = SiteShortSerializer(read_only=True,
|
site_detail = SiteShortSerializer(read_only=True,
|
||||||
source='site')
|
source='site')
|
||||||
guide_filters = GuideFilterBaseSerialzer(read_only=True,
|
guide_filters = GuideFilterBaseSerializer(read_only=True,
|
||||||
source='guidefilter')
|
source='guidefilter')
|
||||||
# counters
|
# counters
|
||||||
restaurant_counter = serializers.IntegerField(read_only=True)
|
restaurant_counter = serializers.IntegerField(read_only=True)
|
||||||
shop_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):
|
class GuideElementBaseSerializer(serializers.ModelSerializer):
|
||||||
"""Serializer for model GuideElement."""
|
"""Serializer for model GuideElement."""
|
||||||
establishment_detail = EstablishmentGuideElementSerializer(read_only=True,
|
establishment_detail = EstablishmentGuideElementSerializer(read_only=True,
|
||||||
|
|
@ -236,8 +219,22 @@ class GuideElementExportSerializer(GuideElementBaseSerializer):
|
||||||
"""GuideElement export serializer."""
|
"""GuideElement export serializer."""
|
||||||
# establishment = EstablishmentGuideElementSerializer(read_only=True,)
|
# establishment = EstablishmentGuideElementSerializer(read_only=True,)
|
||||||
name = serializers.CharField(source='establishment.name', default=None)
|
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)
|
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:
|
class Meta:
|
||||||
model = models.GuideElement
|
model = models.GuideElement
|
||||||
|
|
@ -256,6 +253,13 @@ class GuideElementExportSerializer(GuideElementBaseSerializer):
|
||||||
# 'label_photo',
|
# 'label_photo',
|
||||||
'name',
|
'name',
|
||||||
'public_mark',
|
'public_mark',
|
||||||
'toque_number'
|
'toque_number',
|
||||||
|
'schedule',
|
||||||
|
'address',
|
||||||
|
'phones',
|
||||||
|
'establishment_type',
|
||||||
|
'establishment_subtypes',
|
||||||
|
'review',
|
||||||
|
'price_level',
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
from utils.methods import transform_into_readable_str
|
||||||
|
|
||||||
logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.INFO)
|
logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.INFO)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
@ -28,12 +29,14 @@ def get_additional_product_data(section_node, product):
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@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."""
|
"""Generate guide elements."""
|
||||||
from collection.models import GuideElement, Guide
|
from collection.models import GuideElement, Guide
|
||||||
from establishment.models import Establishment
|
from establishment.models import Establishment
|
||||||
|
|
||||||
guide = Guide.objects.get(id=guide_id)
|
guide = Guide.objects.get(id=guide_id)
|
||||||
|
guide.change_state(Guide.BUILDING)
|
||||||
|
queryset_values = Establishment.objects.filter(**filter_set).values()
|
||||||
try:
|
try:
|
||||||
for instance in queryset_values:
|
for instance in queryset_values:
|
||||||
establishment_id = instance.get('id')
|
establishment_id = instance.get('id')
|
||||||
|
|
@ -47,7 +50,7 @@ def generate_establishment_guide_elements(guide_id: int, queryset_values: dict,
|
||||||
if city_node:
|
if city_node:
|
||||||
section_node, _ = GuideElement.objects.get_or_create_establishment_section_node(
|
section_node, _ = GuideElement.objects.get_or_create_establishment_section_node(
|
||||||
city_node.id,
|
city_node.id,
|
||||||
section_node_name,
|
transform_into_readable_str(establishment.establishment_type.index_name),
|
||||||
)
|
)
|
||||||
if section_node:
|
if section_node:
|
||||||
GuideElement.objects.get_or_create_establishment_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'
|
logger.error(f'METHOD_NAME: {generate_establishment_guide_elements.__name__}\n'
|
||||||
f'DETAIL: Guide ID {guide_id} - Establishment {establishment_id} id is not exists.')
|
f'DETAIL: Guide ID {guide_id} - Establishment {establishment_id} id is not exists.')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
guide.change_state(Guide.WAITING)
|
||||||
logger.error(f'METHOD_NAME: {generate_establishment_guide_elements.__name__}\n'
|
logger.error(f'METHOD_NAME: {generate_establishment_guide_elements.__name__}\n'
|
||||||
f'DETAIL: Guide ID {guide_id} - {e}')
|
f'DETAIL: Guide ID {guide_id} - {e}')
|
||||||
else:
|
else:
|
||||||
guide.update_count_related_objects()
|
guide.update_count_related_objects()
|
||||||
|
guide.change_state(Guide.BUILT)
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@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."""
|
"""Generate guide elements."""
|
||||||
from collection.models import GuideElement, Guide
|
from collection.models import GuideElement, Guide
|
||||||
from product.models import Product
|
from product.models import Product
|
||||||
|
|
||||||
guide = Guide.objects.get(id=guide_id)
|
guide = Guide.objects.get(id=guide_id)
|
||||||
|
guide.change_state(Guide.BUILDING)
|
||||||
|
queryset_values = Product.objects.filter(**filter_set).values()[0]
|
||||||
try:
|
try:
|
||||||
for instance in queryset_values:
|
for instance in queryset_values:
|
||||||
wine_id = instance.get('id')
|
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'
|
logger.error(f'METHOD_NAME: {generate_product_guide_elements.__name__}\n'
|
||||||
f'DETAIL: Guide ID {guide_id} - Product {wine_id} id is not exists.')
|
f'DETAIL: Guide ID {guide_id} - Product {wine_id} id is not exists.')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
guide.change_state(Guide.WAITING)
|
||||||
logger.error(f'METHOD_NAME: {generate_product_guide_elements.__name__}\n'
|
logger.error(f'METHOD_NAME: {generate_product_guide_elements.__name__}\n'
|
||||||
f'DETAIL: Guide ID {guide_id} - {e}')
|
f'DETAIL: Guide ID {guide_id} - {e}')
|
||||||
else:
|
else:
|
||||||
guide.update_count_related_objects()
|
guide.update_count_related_objects()
|
||||||
|
guide.change_state(Guide.BUILT)
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
|
|
|
||||||
|
|
@ -107,9 +107,6 @@ class CollectionBackOfficeViewSet(mixins.CreateModelMixin,
|
||||||
class GuideListCreateView(GuideBaseView,
|
class GuideListCreateView(GuideBaseView,
|
||||||
generics.ListCreateAPIView):
|
generics.ListCreateAPIView):
|
||||||
"""View for Guide model for BackOffice users."""
|
"""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,
|
class GuideFilterCreateView(GuideFilterBaseView,
|
||||||
|
|
@ -137,7 +134,7 @@ class GuideElementListView(GuideElementBaseView,
|
||||||
class GuideUpdateView(GuideBaseView):
|
class GuideUpdateView(GuideBaseView):
|
||||||
"""View for model GuideElement for back office users."""
|
"""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."""
|
"""POST-method to regenerate elements of guide instance."""
|
||||||
guide = get_object_or_404(models.Guide.objects.all(),
|
guide = get_object_or_404(models.Guide.objects.all(),
|
||||||
pk=self.kwargs.get('pk'))
|
pk=self.kwargs.get('pk'))
|
||||||
|
|
@ -195,3 +192,11 @@ class GuideElementExportDOCView(generics.ListAPIView):
|
||||||
queryset = models.GuideElement.objects.all()
|
queryset = models.GuideElement.objects.all()
|
||||||
serializer_class = serializers.GuideElementBaseSerializer
|
serializer_class = serializers.GuideElementBaseSerializer
|
||||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
|
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)
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,12 @@ from phonenumber_field.modelfields import PhoneNumberField
|
||||||
from timezone_field import TimeZoneField
|
from timezone_field import TimeZoneField
|
||||||
|
|
||||||
from location.models import Address
|
from location.models import Address
|
||||||
|
from timetable.models import Timetable
|
||||||
from location.models import WineOriginAddressMixin
|
from location.models import WineOriginAddressMixin
|
||||||
from main.models import Award, Currency
|
from main.models import Award, Currency
|
||||||
from review.models import Review
|
from review.models import Review
|
||||||
from tag.models import Tag
|
from tag.models import Tag
|
||||||
|
from utils.methods import transform_into_readable_str
|
||||||
from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin,
|
from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin,
|
||||||
TranslatedFieldsMixin, BaseAttributes, GalleryMixin,
|
TranslatedFieldsMixin, BaseAttributes, GalleryMixin,
|
||||||
IntermediateGalleryModelMixin, HasTagsMixin,
|
IntermediateGalleryModelMixin, HasTagsMixin,
|
||||||
|
|
@ -61,6 +63,10 @@ class EstablishmentType(TypeDefaultImageMixin, TranslatedFieldsMixin, ProjectBas
|
||||||
verbose_name = _('Establishment type')
|
verbose_name = _('Establishment type')
|
||||||
verbose_name_plural = _('Establishment types')
|
verbose_name_plural = _('Establishment types')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def label(self):
|
||||||
|
return transform_into_readable_str(self.index_name)
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentSubTypeManager(models.Manager):
|
class EstablishmentSubTypeManager(models.Manager):
|
||||||
"""Extended manager for establishment subtype."""
|
"""Extended manager for establishment subtype."""
|
||||||
|
|
@ -737,6 +743,65 @@ class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin,
|
||||||
def wine_origins_unique(self):
|
def wine_origins_unique(self):
|
||||||
return self.wine_origins.distinct('wine_region')
|
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):
|
class EstablishmentNoteQuerySet(models.QuerySet):
|
||||||
"""QuerySet for model EstablishmentNote."""
|
"""QuerySet for model EstablishmentNote."""
|
||||||
|
|
|
||||||
|
|
@ -241,6 +241,13 @@ class Address(models.Model):
|
||||||
def country_id(self):
|
def country_id(self):
|
||||||
return self.city.country_id
|
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):
|
class WineRegionQuerySet(models.QuerySet):
|
||||||
"""Wine region queryset."""
|
"""Wine region queryset."""
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ class Tag(models.Model):
|
||||||
verbose_name_plural = _('Tags')
|
verbose_name_plural = _('Tags')
|
||||||
|
|
||||||
def __str__(self):
|
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):
|
class ChosenTagSettingsQuerySet(models.QuerySet):
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,202 @@
|
||||||
|
import abc
|
||||||
import csv
|
import csv
|
||||||
import xlsxwriter
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
from smtplib import SMTPException
|
from smtplib import SMTPException
|
||||||
|
|
||||||
|
import docx
|
||||||
|
import xlsxwriter
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.mail import EmailMultiAlternatives
|
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)
|
logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.INFO)
|
||||||
logger = logging.getLogger(__name__)
|
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:
|
class SendExportBase:
|
||||||
"""Base class of export and sending data."""
|
"""Base class of export and sending data."""
|
||||||
|
|
||||||
|
|
@ -38,8 +222,9 @@ class SendExportBase:
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_emails_to(self):
|
def get_emails_to(self):
|
||||||
return [
|
return [
|
||||||
'kuzmenko.da@gmail.com',
|
'a.feteleu@spider.ru',
|
||||||
'sinapsit@yandex.ru'
|
# 'kuzmenko.da@gmail.com',
|
||||||
|
# 'sinapsit@yandex.ru'
|
||||||
]
|
]
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
|
|
@ -82,6 +267,9 @@ class SendExportBase:
|
||||||
def make_xml_file(self):
|
def make_xml_file(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def make_doc_file(self):
|
||||||
|
pass
|
||||||
|
|
||||||
def send_email(self):
|
def send_email(self):
|
||||||
|
|
||||||
msg = EmailMultiAlternatives(
|
msg = EmailMultiAlternatives(
|
||||||
|
|
@ -165,12 +353,18 @@ class SendGuideExport(SendExportBase):
|
||||||
self.get_file_method = self.type_mapper[file_type]
|
self.get_file_method = self.type_mapper[file_type]
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
def get_emails_to(self):
|
||||||
|
return [self.user.email] + super().get_emails_to()
|
||||||
|
|
||||||
def get_file_name(self):
|
def get_file_name(self):
|
||||||
name = self.guide.slug
|
name = self.guide.slug
|
||||||
return f'export_{name}.{self.file_type}'
|
return f'export_{name}.{self.file_type}'
|
||||||
|
|
||||||
def make_doc_file(self):
|
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):
|
def get_headers(self):
|
||||||
headers = list(self.data[0].keys())
|
headers = list(self.data[0].keys())
|
||||||
|
|
@ -181,6 +375,29 @@ class SendGuideExport(SendExportBase):
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
return self.data
|
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):
|
def send(self):
|
||||||
self.get_file_method()
|
self.get_file_method()
|
||||||
print(f'ok: {self.file_path}')
|
print(f'ok: {self.file_path}')
|
||||||
|
|
@ -204,4 +421,4 @@ class SendGuideExport(SendExportBase):
|
||||||
row['city_name'] = city
|
row['city_name'] = city
|
||||||
|
|
||||||
if row.pop("node_name") == "EstablishmentNode":
|
if row.pop("node_name") == "EstablishmentNode":
|
||||||
file_writer.writerow(row.values())
|
file_writer.writerow(row.values())
|
||||||
|
|
|
||||||
|
|
@ -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:
|
Transform slug into section name, i.e:
|
||||||
like
|
like
|
||||||
|
|
@ -152,6 +152,20 @@ def slug_into_section_name(slug: str, postfix: str = 'SectionNode'):
|
||||||
"effervescent-rose-de-saignee"
|
"effervescent-rose-de-saignee"
|
||||||
"""
|
"""
|
||||||
re_exp = r'[\w]+'
|
re_exp = r'[\w]+'
|
||||||
result = re.findall(re_exp, slug)
|
result = re.findall(re_exp, raw_string)
|
||||||
if result:
|
if result:
|
||||||
return f"{''.join([i.capitalize() for i in result])}{postfix}"
|
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])])}"
|
||||||
|
|
|
||||||
|
|
@ -70,5 +70,8 @@ python-slugify==4.0.0
|
||||||
# Export to Excel
|
# Export to Excel
|
||||||
XlsxWriter==1.2.6
|
XlsxWriter==1.2.6
|
||||||
|
|
||||||
|
# Export to Doc
|
||||||
|
python-docx==0.8.10
|
||||||
|
|
||||||
# For recursive fields
|
# For recursive fields
|
||||||
djangorestframework-recursive==0.1.2
|
djangorestframework-recursive==0.1.2
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user