From 1189b6ff58c1c9f280cc526c16a2992300423711 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Thu, 26 Dec 2019 10:24:08 +0300 Subject: [PATCH] see changes --- .../migrations/0031_auto_20191226_0621.py | 49 +++++++ apps/collection/models.py | 120 +++++++++++------- apps/collection/serializers/common.py | 39 ++---- apps/collection/tasks.py | 4 - apps/collection/urls/back.py | 10 +- apps/collection/views/back.py | 34 ++++- apps/establishment/models.py | 3 +- apps/transfer/serializers/guide.py | 7 +- 8 files changed, 182 insertions(+), 84 deletions(-) create mode 100644 apps/collection/migrations/0031_auto_20191226_0621.py diff --git a/apps/collection/migrations/0031_auto_20191226_0621.py b/apps/collection/migrations/0031_auto_20191226_0621.py new file mode 100644 index 00000000..c857fb67 --- /dev/null +++ b/apps/collection/migrations/0031_auto_20191226_0621.py @@ -0,0 +1,49 @@ +# Generated by Django 2.2.7 on 2019-12-26 06:21 + +from django.db import migrations, models + +GUIDE_TYPE_RESTAURANT = 0 +GUIDE_TYPE_WINE = 1 + + +def transform_guide_type(apps, schema_editor): + Guide = apps.get_model('collection', 'Guide') + to_update = [] + for guide in Guide.objects.all(): + if guide.guide_type.name.startswith('restaurant'): + guide.guide_type_2 = GUIDE_TYPE_RESTAURANT + elif guide.guide_type.name.startswith('wine'): + guide.guide_type_2 = GUIDE_TYPE_WINE + Guide.objects.bulk_update(to_update, ['guide_type_2', ]) + + +class Migration(migrations.Migration): + + dependencies = [ + ('collection', '0030_guidefilter_guide_type'), + ] + + operations = [ + migrations.RemoveField( + model_name='guidefilter', + name='guide_type', + ), + migrations.AddField( + model_name='guide', + name='guide_type_2', + field=models.PositiveSmallIntegerField(choices=[(0, 'Restaurant'), (1, 'Artisan'), (2, 'Wine')], default=0, verbose_name='guide type'), + ), + migrations.RunPython(transform_guide_type, migrations.RunPython.noop), + migrations.RemoveField( + model_name='guide', + name='guide_type', + ), + migrations.DeleteModel( + name='GuideType', + ), + migrations.RenameField( + model_name='guide', + old_name='guide_type_2', + new_name='guide_type', + ), + ] diff --git a/apps/collection/models.py b/apps/collection/models.py index 35e5a386..76d76d62 100644 --- a/apps/collection/models.py +++ b/apps/collection/models.py @@ -4,20 +4,22 @@ from django.contrib.contenttypes.fields import ContentType from django.contrib.postgres.fields import JSONField from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models +from django.conf import settings from django.utils.translation import gettext_lazy as _ -from mptt.models import MPTTModel from mptt.models import MPTTModel, TreeForeignKey from slugify import slugify +from establishment.models import Establishment from location.models import Country, Region, WineRegion, WineSubRegion, City -from review.models import Review 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.models import ( ProjectBaseMixin, TJSONField, TranslatedFieldsMixin, URLImageMixin, IntermediateGalleryModelMixin ) -from utils.methods import slug_into_section_name from utils.querysets import RelatedObjectsCountMixin @@ -158,28 +160,6 @@ class Collection(ProjectBaseMixin, CollectionDateMixin, super(Collection, self).save(*args, **kwargs) -class GuideTypeQuerySet(models.QuerySet): - """QuerySet for model GuideType.""" - - -class GuideType(ProjectBaseMixin): - """GuideType model.""" - - name = models.SlugField(max_length=255, unique=True, - verbose_name=_('code')) - - objects = GuideTypeQuerySet.as_manager() - - class Meta: - """Meta class.""" - verbose_name = _('guide type') - verbose_name_plural = _('guide types') - - def __str__(self): - """Overridden str dunder method.""" - return self.name - - class GuideQuerySet(models.QuerySet): """QuerySet for Guide.""" @@ -268,6 +248,19 @@ class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin): (BUILDING, 'building'), ) + RESTAURANT = 0 + ARTISAN = 1 + WINE = 2 + + GUIDE_TYPE_CHOICES = ( + (RESTAURANT, _('Restaurant')), + (ARTISAN, _('Artisan')), + (WINE, _('Wine')), + ) + + guide_type = models.PositiveSmallIntegerField(choices=GUIDE_TYPE_CHOICES, + default=RESTAURANT, + verbose_name=_('guide type')) start = models.DateTimeField(null=True, verbose_name=_('start')) vintage = models.IntegerField(validators=[MinValueValidator(1900), @@ -276,9 +269,6 @@ class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin): verbose_name=_('guide vintage year')) slug = models.SlugField(max_length=255, unique=True, null=True, verbose_name=_('slug')) - guide_type = models.ForeignKey('GuideType', on_delete=models.PROTECT, - null=True, - verbose_name=_('type')) site = models.ForeignKey('main.SiteSettings', on_delete=models.SET_NULL, null=True, verbose_name=_('site settings')) @@ -301,6 +291,16 @@ class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin): """String method.""" return f'{self.name}' + def save(self, *args, **kwargs): + if not self.pk: + if not self.slug: + slugify_slug = slugify( + f'{self.name} {self.vintage}', + word_boundary=True + ) + self.slug = slugify_slug + super(Guide, self).save(*args, **kwargs) + # todo: for test use, use annotation instead # @property # def restaurant_counter_prop(self): @@ -351,6 +351,43 @@ class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin): self.count_related_objects = updated_count self.save() + def generate_elements(self): + if self.guidefilter: + if self.guide_type in [self.ARTISAN, self.RESTAURANT]: + 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 + ) + 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 + ) + 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, + ) + else: + tasks.generate_product_guide_elements( + guide_id=self.id, + queryset_values=self.guidefilter.filtered_queryset_values, + ) + + def regenerate_elements(self): + # get Root node + root_node = GuideElement.objects.get_root_node(self) + # get all descendants related to this guide and delete it + root_node.get_descendants().delete() + # re-generate elements + self.generate_elements() + # update count elements + self.update_count_related_objects() + class AdvertorialQuerySet(models.QuerySet): """QuerySet for model Advertorial.""" @@ -384,19 +421,6 @@ class GuideFilterQuerySet(models.QuerySet): class GuideFilter(ProjectBaseMixin): """Guide filter model.""" - RESTAURANT = 0 - ARTISAN = 1 - WINE = 2 - - GUIDE_TYPES = ( - (RESTAURANT, _('Restaurant')), - (ARTISAN, _('Artisan')), - (WINE, _('Wine')), - ) - - guide_type = models.PositiveSmallIntegerField(choices=GUIDE_TYPES, - default=RESTAURANT, - verbose_name=_('guide type')) establishment_type_json = JSONField(blank=True, null=True, verbose_name='establishment types') country_json = JSONField(blank=True, null=True, @@ -645,11 +669,15 @@ class GuideFilter(ProjectBaseMixin): return filters @property - def filter_set(self): - if self.guide_type in [self.RESTAURANT, self.ARTISAN]: - return self.establishment_filter_set - elif self.guide_type == self.WINE: - return self.product_filter_set + 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): diff --git a/apps/collection/serializers/common.py b/apps/collection/serializers/common.py index 81308a9f..2b137571 100644 --- a/apps/collection/serializers/common.py +++ b/apps/collection/serializers/common.py @@ -1,14 +1,14 @@ +from django.shortcuts import get_object_or_404 from rest_framework import serializers +from rest_framework_recursive.fields import RecursiveField from collection import models +from establishment.serializers import EstablishmentGuideElementSerializer from location import models as location_models from main.serializers import SiteShortSerializer -from utils.serializers import TranslatedField -from rest_framework_recursive.fields import RecursiveField -from establishment.serializers import EstablishmentGuideElementSerializer from product.serializers import ProductGuideElementSerializer -from django.shortcuts import get_object_or_404 from utils import exceptions +from utils.serializers import TranslatedField class CollectionBaseSerializer(serializers.ModelSerializer): @@ -53,18 +53,6 @@ class CollectionSerializer(CollectionBaseSerializer): ] -class GuideTypeBaseSerializer(serializers.ModelSerializer): - """GuideType serializer.""" - - class Meta: - """Meta class.""" - model = models.GuideType - fields = [ - 'id', - 'name', - ] - - class GuideFilterBaseSerialzer(serializers.ModelSerializer): """Serializer for model GuideFilter.""" @@ -89,8 +77,7 @@ class GuideBaseSerializer(serializers.ModelSerializer): """Guide serializer""" state_display = serializers.CharField(source='get_state_display', read_only=True) - guide_type_detail = GuideTypeBaseSerializer(read_only=True, - source='guide_type') + guide_type_display = serializers.CharField(read_only=True) site_detail = SiteShortSerializer(read_only=True, source='site') guide_filters = GuideFilterBaseSerialzer(read_only=True, @@ -112,7 +99,7 @@ class GuideBaseSerializer(serializers.ModelSerializer): 'vintage', 'slug', 'guide_type', - 'guide_type_detail', + 'guide_type_display', 'site', 'site_detail', 'state', @@ -128,8 +115,9 @@ class GuideBaseSerializer(serializers.ModelSerializer): 'site': {'write_only': True}, 'state': {'write_only': True}, 'start': {'required': True}, - 'slug': {'required': True}, - 'count_objects_during_init': {'read_only': True} + 'slug': {'required': False}, + 'count_objects_during_init': {'read_only': True}, + 'vintage': {'required': True}, } @@ -155,7 +143,7 @@ class GuideFilterBaseSerializer(serializers.ModelSerializer): 'guide', ] extra_kwargs = { - 'guide': {'write_only': True} + 'guide': {'write_only': True, 'required': False}, } @property @@ -168,8 +156,9 @@ class GuideFilterBaseSerializer(serializers.ModelSerializer): guide = get_object_or_404(models.Guide.objects.all(), pk=self.request_kwargs.get('pk')) validated_data['guide'] = guide - - return super().create(validated_data) + guide_filter = super().create(validated_data) + guide.generate_elements() + return guide_filter class GuideElementBaseSerializer(serializers.ModelSerializer): @@ -230,7 +219,7 @@ class AdvertorialBaseSerializer(serializers.ModelSerializer): guide = get_object_or_404(models.Guide.objects.all(), pk=self.request_kwargs.get('pk')) root_node = models.GuideElement.objects.get_root_node(guide) - guide_element_qs = root_node.get_children().filter(pk=self.request_kwargs.get('element_pk')) + guide_element_qs = root_node.get_descendants().filter(pk=self.request_kwargs.get('element_pk')) guide_element = guide_element_qs.first() if not guide_element_qs.exists(): diff --git a/apps/collection/tasks.py b/apps/collection/tasks.py index b8668536..d97071ab 100644 --- a/apps/collection/tasks.py +++ b/apps/collection/tasks.py @@ -71,8 +71,6 @@ def generate_establishment_guide_elements(guide_id: int, queryset_values: dict, f'DETAIL: Guide ID {guide_id} - {e}') else: guide.update_count_related_objects() - # Update tree indexes - GuideElement._tree_manager.rebuild() @shared_task @@ -129,5 +127,3 @@ def generate_product_guide_elements(guide_id: int, queryset_values: dict): f'DETAIL: Guide ID {guide_id} - {e}') else: guide.update_count_related_objects() - # Update tree indexes - GuideElement._tree_manager.rebuild() diff --git a/apps/collection/urls/back.py b/apps/collection/urls/back.py index c5972372..1de62687 100644 --- a/apps/collection/urls/back.py +++ b/apps/collection/urls/back.py @@ -14,8 +14,14 @@ urlpatterns = [ name='guide-list-create'), path('guides//', views.GuideElementListView.as_view(), name='guide-element-list'), - path('guides//element//advertorial/', views.AdvertorialCreateDestroyView.as_view(), - name='guide-advertorial-create-destroy'), + path('guides//regenerate/', views.GuideUpdateView.as_view(), + name='guide-regenerate'), + path('guides//element//advertorial/', + views.AdvertorialCreateView.as_view(), + name='guide-advertorial-create'), + path('guides//element//advertorial//', + views.AdvertorialDestroyView.as_view(), + name='guide-advertorial-destroy'), path('guides//filters/', views.GuideFilterCreateView.as_view(), name='guide-filter-list-create'), ] + router.urls diff --git a/apps/collection/views/back.py b/apps/collection/views/back.py index 1948bc44..856f5dbd 100644 --- a/apps/collection/views/back.py +++ b/apps/collection/views/back.py @@ -36,7 +36,8 @@ class GuideBaseView(generics.GenericAPIView): .annotate_restaurant_counter() \ .annotate_shop_counter() \ .annotate_wine_counter() \ - .annotate_present_objects_counter() + .annotate_present_objects_counter() \ + .distinct() class GuideFilterBaseView(generics.GenericAPIView): @@ -131,7 +132,32 @@ class GuideElementListView(GuideElementBaseView, return models.GuideElement.objects.none() -class AdvertorialCreateDestroyView(AdvertorialBaseView, - generics.CreateAPIView, - generics.DestroyAPIView): +class GuideUpdateView(GuideBaseView): + """View for model GuideElement for back office users.""" + + def post(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')) + guide.regenerate_elements() + return Response(status=status.HTTP_200_OK) + + +class AdvertorialCreateView(AdvertorialBaseView, + generics.CreateAPIView): """View for model Advertorial for back office users.""" + + def post(self, request, *args, **kwargs): + """Overridden post method.""" + super(AdvertorialCreateView, self).create(request, *args, **kwargs) + return Response(status=status.HTTP_200_OK) + + +class AdvertorialDestroyView(AdvertorialBaseView, + generics.DestroyAPIView): + """View for model Advertorial for back office users.""" + lookup_url_kwarg = 'advertorial_pk' + + def delete(self, request, *args, **kwargs): + """Overridden delete method.""" + return self.destroy(request, *args, **kwargs) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index d0c86244..81dd691b 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -20,7 +20,6 @@ from django.utils.translation import gettext_lazy as _ from phonenumber_field.modelfields import PhoneNumberField from timezone_field import TimeZoneField -from collection.models import Collection from location.models import Address from location.models import WineOriginAddressMixin from main.models import Award, Currency @@ -200,6 +199,8 @@ class EstablishmentQuerySet(models.QuerySet): If establishments in collection POP and its mark is null, then intermediate_mark is set to 10; """ + from collection.models import Collection + return self.annotate(intermediate_public_mark=Case( When( collections__collection_type=Collection.POP, diff --git a/apps/transfer/serializers/guide.py b/apps/transfer/serializers/guide.py index f0cddd02..e85ebcaf 100644 --- a/apps/transfer/serializers/guide.py +++ b/apps/transfer/serializers/guide.py @@ -61,8 +61,11 @@ class GuideSerializer(TransferSerializerMixin): return qs.first() def get_guide_type(self, inserter_field): - guide_type, _ = models.GuideType.objects.get_or_create(name=inserter_field) - return guide_type + if inserter_field: + if inserter_field.startswith('restaurant'): + return models.Guide.RESTAURANT + elif inserter_field.startswith('wine'): + return models.Guide.WINE class GuideFilterSerializer(TransferSerializerMixin):