diff --git a/apps/collection/migrations/0024_auto_20191215_2156.py b/apps/collection/migrations/0024_auto_20191215_2156.py new file mode 100644 index 00000000..1b494867 --- /dev/null +++ b/apps/collection/migrations/0024_auto_20191215_2156.py @@ -0,0 +1,27 @@ +# Generated by Django 2.2.7 on 2019-12-15 21:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('collection', '0023_advertorial'), + ] + + operations = [ + migrations.AddField( + model_name='collection', + name='rank', + field=models.IntegerField(default=None, null=True), + ), + migrations.AlterField( + model_name='collection', + name='start', + field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='start'), + ), + migrations.RemoveField( + model_name='collection', + name='description', + ) + ] diff --git a/apps/collection/migrations/0025_collection_description.py b/apps/collection/migrations/0025_collection_description.py new file mode 100644 index 00000000..d7638db3 --- /dev/null +++ b/apps/collection/migrations/0025_collection_description.py @@ -0,0 +1,20 @@ +# Generated by Django 2.2.7 on 2019-12-16 17:25 + +from django.db import migrations + +import utils.models + +class Migration(migrations.Migration): + dependencies = [ + ('collection', '0024_auto_20191215_2156'), + ] + + operations = [ + migrations.AddField( + model_name='collection', + name='description', + field=utils.models.TJSONField(blank=True, default=None, + help_text='{"en-GB":"some text"}', null=True, + verbose_name='description'), + ), + ] diff --git a/apps/collection/models.py b/apps/collection/models.py index 7acd9991..41e0118a 100644 --- a/apps/collection/models.py +++ b/apps/collection/models.py @@ -6,9 +6,10 @@ from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.utils.translation import gettext_lazy as _ -from utils.models import ProjectBaseMixin, URLImageMixin -from utils.models import TJSONField -from utils.models import TranslatedFieldsMixin +from utils.models import ( + ProjectBaseMixin, TJSONField, TranslatedFieldsMixin, + URLImageMixin, +) from utils.querysets import RelatedObjectsCountMixin @@ -24,7 +25,8 @@ class CollectionNameMixin(models.Model): class CollectionDateMixin(models.Model): """CollectionDate mixin""" - start = models.DateTimeField(_('start')) + start = models.DateTimeField(blank=True, null=True, default=None, + verbose_name=_('start')) end = models.DateTimeField(blank=True, null=True, default=None, verbose_name=_('end')) @@ -80,6 +82,8 @@ class Collection(ProjectBaseMixin, CollectionDateMixin, verbose_name=_('Collection slug'), editable=True, null=True) old_id = models.IntegerField(null=True, blank=True) + rank = models.IntegerField(null=True, default=None) + objects = CollectionQuerySet.as_manager() class Meta: @@ -108,20 +112,32 @@ class Collection(ProjectBaseMixin, CollectionDateMixin, @property def related_object_names(self) -> list: """Return related object names.""" - raw_object_names = [] + raw_objects = [] for related_object in [related_object.name for related_object in self._related_objects]: instances = getattr(self, f'{related_object}') if instances.exists(): for instance in instances.all(): - raw_object_names.append(instance.slug if hasattr(instance, 'slug') else None) + raw_object = (instance.id, instance.slug) if hasattr(instance, 'slug') else ( + instance.id, None + ) + raw_objects.append(raw_object) # parse slugs - object_names = [] + related_objects = [] + object_names = set() re_pattern = r'[\w]+' - for raw_name in raw_object_names: + for object_id, raw_name, in raw_objects: result = re.findall(re_pattern, raw_name) - if result: object_names.append(' '.join(result).capitalize()) - return set(object_names) + if result: + name = ' '.join(result).capitalize() + if name not in object_names: + related_objects.append({ + 'id': object_id, + 'name': name + }) + object_names.add(name) + + return related_objects class GuideTypeQuerySet(models.QuerySet): diff --git a/apps/collection/serializers/back.py b/apps/collection/serializers/back.py index 48c25f6c..35917142 100644 --- a/apps/collection/serializers/back.py +++ b/apps/collection/serializers/back.py @@ -7,7 +7,8 @@ from location.models import Country from location.serializers import CountrySimpleSerializer from product.models import Product from utils.exceptions import ( - BindingObjectNotFound, RemovedBindingObjectNotFound, ObjectAlreadyAdded + BindingObjectNotFound, ObjectAlreadyAdded, + RemovedBindingObjectNotFound, ) @@ -33,13 +34,14 @@ class CollectionBackOfficeSerializer(CollectionBaseSerializer): 'on_top', 'country', 'country_id', - 'block_size', + # 'block_size', 'description', 'slug', - 'start', - 'end', + # 'start', + # 'end', 'count_related_objects', 'related_object_names', + 'rank', ] @@ -68,15 +70,15 @@ class CollectionBindObjectSerializer(serializers.Serializer): attrs['collection'] = collection if obj_type == self.ESTABLISHMENT: - establishment = Establishment.objects.filter(pk=obj_id).\ + establishment = Establishment.objects.filter(pk=obj_id). \ first() if not establishment: raise BindingObjectNotFound() - if request.method == 'POST' and collection.establishments.\ + if request.method == 'POST' and collection.establishments. \ filter(pk=establishment.pk).exists(): raise ObjectAlreadyAdded() - if request.method == 'DELETE' and not collection.\ - establishments.filter(pk=establishment.pk).\ + if request.method == 'DELETE' and not collection. \ + establishments.filter(pk=establishment.pk). \ exists(): raise RemovedBindingObjectNotFound() attrs['related_object'] = establishment @@ -84,10 +86,10 @@ class CollectionBindObjectSerializer(serializers.Serializer): product = Product.objects.filter(pk=obj_id).first() if not product: raise BindingObjectNotFound() - if request.method == 'POST' and collection.products.\ + if request.method == 'POST' and collection.products. \ filter(pk=product.pk).exists(): raise ObjectAlreadyAdded() - if request.method == 'DELETE' and not collection.products.\ + if request.method == 'DELETE' and not collection.products. \ filter(pk=product.pk).exists(): raise RemovedBindingObjectNotFound() attrs['related_object'] = product diff --git a/apps/collection/views/back.py b/apps/collection/views/back.py index ff924073..b8ff3181 100644 --- a/apps/collection/views/back.py +++ b/apps/collection/views/back.py @@ -1,5 +1,6 @@ -from rest_framework import permissions -from rest_framework import viewsets, mixins +from django_filters.rest_framework import DjangoFilterBackend +from rest_framework import mixins, permissions, viewsets +from rest_framework.filters import OrderingFilter from collection import models from collection.serializers import back as serializers @@ -31,9 +32,13 @@ class CollectionBackOfficeViewSet(mixins.CreateModelMixin, permission_classes = (permissions.IsAuthenticated,) queryset = models.Collection.objects.all() + filter_backends = [DjangoFilterBackend, OrderingFilter] serializer_class = serializers.CollectionBackOfficeSerializer bind_object_serializer_class = serializers.CollectionBindObjectSerializer + ordering_fields = ('rank', 'start') + ordering = ('-start', ) + def perform_binding(self, serializer): data = serializer.validated_data collection = data.pop('collection') diff --git a/apps/establishment/models.py b/apps/establishment/models.py index ab7f14fa..5ff6f921 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -541,9 +541,15 @@ class Establishment(GalleryModelMixin, ProjectBaseMixin, URLImageMixin, def visible_tags(self): return super().visible_tags \ .exclude(category__index_name__in=['guide', 'collection', 'purchased_item', - 'business_tag', 'business_tags_de', 'tag']) + 'business_tag', 'business_tags_de']) \ + .exclude(value__in=['rss', 'rss_selection']) # todo: recalculate toque_number + @property + def visible_tags_detail(self): + """Removes some tags from detail Establishment representation""" + return self.visible_tags.exclude(category__index_name__in=['tag']) + def recalculate_toque_number(self): toque_number = 0 if self.address and self.public_mark: diff --git a/apps/news/migrations/0043_auto_20191216_1920.py b/apps/news/migrations/0043_auto_20191216_1920.py new file mode 100644 index 00000000..03f5a991 --- /dev/null +++ b/apps/news/migrations/0043_auto_20191216_1920.py @@ -0,0 +1,37 @@ +# Generated by Django 2.2.7 on 2019-12-16 19:20 + +import django.contrib.postgres.fields.hstore +from django.db import migrations, models +import uuid + +def fill_uuid(apps, schemaeditor): + News = apps.get_model('news', 'News') + for news in News.objects.all(): + news.duplication_uuid = uuid.uuid4() + news.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0042_news_duplication_date'), + ] + + operations = [ + migrations.AddField( + model_name='news', + name='description_to_locale_is_active', + field=django.contrib.postgres.fields.hstore.HStoreField(blank=True, default=dict, help_text='{"en-GB": true, "fr-FR": false}', null=True, verbose_name='Is description for certain locale active'), + ), + migrations.AddField( + model_name='news', + name='duplication_uuid', + field=models.UUIDField(default=uuid.uuid4, verbose_name='Field to detect doubles'), + ), + migrations.AlterField( + model_name='news', + name='slugs', + field=django.contrib.postgres.fields.hstore.HStoreField(blank=True, default=dict, help_text='{"en-GB":"some slug"}', null=True, verbose_name='Slugs for current news obj'), + ), + migrations.RunPython(fill_uuid, migrations.RunPython.noop), + ] diff --git a/apps/news/migrations/0044_auto_20191216_2044.py b/apps/news/migrations/0044_auto_20191216_2044.py new file mode 100644 index 00000000..3854cc70 --- /dev/null +++ b/apps/news/migrations/0044_auto_20191216_2044.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.7 on 2019-12-16 20:44 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0043_auto_20191216_1920'), + ] + + operations = [ + migrations.RenameField( + model_name='news', + old_name='description_to_locale_is_active', + new_name='locale_to_description_is_active', + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index 6ebdca76..149b8480 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -1,5 +1,9 @@ """News app models.""" +import uuid + +from django.conf import settings from django.contrib.contenttypes import fields as generic +from django.contrib.postgres.fields import HStoreField from django.db import models from django.db.models import Case, When from django.utils import timezone @@ -11,8 +15,6 @@ from utils.models import (BaseAttributes, TJSONField, TranslatedFieldsMixin, Has ProjectBaseMixin, GalleryModelMixin, IntermediateGalleryModelMixin, FavoritesMixin) from utils.querysets import TranslationQuerysetMixin -from django.conf import settings -from django.contrib.postgres.fields import HStoreField class Agenda(ProjectBaseMixin, TranslatedFieldsMixin): @@ -177,11 +179,14 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi description = TJSONField(blank=True, null=True, default=None, verbose_name=_('description'), help_text='{"en-GB":"some text"}') + locale_to_description_is_active = HStoreField(null=True, default=dict, blank=True, + verbose_name=_('Is description for certain locale active'), + help_text='{"en-GB": true, "fr-FR": false}') start = models.DateTimeField(blank=True, null=True, default=None, verbose_name=_('Start')) end = models.DateTimeField(blank=True, null=True, default=None, verbose_name=_('End')) - slugs = HStoreField(null=True, blank=True, default=None, + slugs = HStoreField(null=True, blank=True, default=dict, verbose_name=_('Slugs for current news obj'), help_text='{"en-GB":"some slug"}') state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES, @@ -213,6 +218,8 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi on_delete=models.SET_NULL, verbose_name=_('site settings')) duplication_date = models.DateTimeField(blank=True, null=True, default=None, verbose_name=_('Duplication datetime')) + duplication_uuid = models.UUIDField(default=uuid.uuid4, editable=True, unique=False, + verbose_name=_('Field to detect doubles')) objects = NewsQuerySet.as_manager() class Meta: @@ -233,6 +240,12 @@ class News(GalleryModelMixin, BaseAttributes, TranslatedFieldsMixin, HasTagsMixi self.duplication_date = timezone.now() self.save() + + @property + def duplicates(self): + """Duplicates for this news item excluding same country code labeled""" + return News.objects.filter(duplication_uuid=self.duplication_uuid).exclude(country=self.country) + @property def is_publish(self): return self.state in self.PUBLISHED_STATES diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 5b9a8162..ad29f1ac 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -182,6 +182,7 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer): 'backoffice_title', 'subtitle', 'slugs', + 'locale_to_description_is_active', 'is_published', 'duplication_date', ) @@ -209,6 +210,20 @@ class NewsBackOfficeBaseSerializer(NewsBaseSerializer): return super().update(instance, validated_data) +class NewsBackOfficeDuplicationInfoSerializer(serializers.ModelSerializer): + """Duplication info for news detail.""" + + country = CountrySimpleSerializer(read_only=True) + + class Meta: + model = models.News + fields = ( + 'id', + 'duplication_date', + 'country', + ) + + class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer, NewsDetailSerializer): """News detail serializer for back-office users.""" @@ -224,6 +239,7 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer, queryset=SiteSettings.objects.all()) template_display = serializers.CharField(source='get_template_display', read_only=True) + duplicates = NewsBackOfficeDuplicationInfoSerializer(many=True, allow_null=True, read_only=True) class Meta(NewsBackOfficeBaseSerializer.Meta, NewsDetailSerializer.Meta): """Meta class.""" @@ -237,6 +253,7 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer, 'template', 'template_display', 'is_international', + 'duplicates', ) diff --git a/apps/news/views.py b/apps/news/views.py index c8acb3ac..fe9a75e7 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -1,7 +1,7 @@ """News app views.""" from django.conf import settings from django.shortcuts import get_object_or_404 -from rest_framework import generics, permissions +from rest_framework import generics, permissions, response from news import filters, models, serializers from rating.tasks import add_rating @@ -99,7 +99,10 @@ class NewsBackOfficeLCView(NewsBackOfficeMixinView, def get_queryset(self): """Override get_queryset method.""" - return super().get_queryset().with_extended_related() + qs = super().get_queryset().with_extended_related() + if self.request.country_code: + qs = qs.by_country_code(self.request.country_code) + return qs class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView, @@ -107,6 +110,13 @@ class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView, """Resource for a create gallery for news for back-office users.""" serializer_class = serializers.NewsBackOfficeGallerySerializer + def create(self, request, *args, **kwargs): + _ = super().create(request, *args, **kwargs) + news_qs = self.filter_queryset(self.get_queryset()) + return response.Response( + data=serializers.NewsDetailSerializer(get_object_or_404(news_qs, pk=kwargs.get('pk'))).data + ) + def get_object(self): """ Returns the object the view is displaying. diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index 4b18bea6..f088bf8b 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -61,11 +61,18 @@ class NewsDocumentViewSet(BaseDocumentViewSet): ) filter_fields = { + 'tags_id': { + 'field': 'tags.id', + 'lookups': [ + constants.LOOKUP_QUERY_IN, + constants.LOOKUP_QUERY_EXCLUDE, + ], + }, 'tag': { 'field': 'tags.id', 'lookups': [ constants.LOOKUP_QUERY_IN, - constants.LOOKUP_QUERY_EXCLUDE + constants.LOOKUP_QUERY_EXCLUDE, ] }, 'tag_value': {