diff --git a/apps/account/models.py b/apps/account/models.py index 5e280919..5bf66321 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -147,8 +147,8 @@ class User(AbstractUser): ) EMAIL_FIELD = 'email' - USERNAME_FIELD = 'username' - REQUIRED_FIELDS = ['email'] + USERNAME_FIELD = 'email' + REQUIRED_FIELDS = ['username'] roles = models.ManyToManyField( Role, verbose_name=_('Roles'), symmetrical=False, diff --git a/apps/establishment/models.py b/apps/establishment/models.py index e4087b4b..f15a8b1c 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -58,6 +58,8 @@ class EstablishmentType(TypeDefaultImageMixin, TranslatedFieldsMixin, ProjectBas blank=True, null=True, default=None, verbose_name='default image') + chosen_tags = generic.GenericRelation(to='tag.ChosenTag') + class Meta: """Meta class.""" diff --git a/apps/news/models.py b/apps/news/models.py index 79e2bbd0..3beb0d80 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -54,6 +54,7 @@ class NewsType(models.Model): name = models.CharField(_('name'), max_length=250) tag_categories = models.ManyToManyField('tag.TagCategory', related_name='news_types') + chosen_tags = generic.GenericRelation(to='tag.ChosenTag') class Meta: """Meta class.""" diff --git a/apps/tag/filters.py b/apps/tag/filters.py index 28c3db33..7d82bf84 100644 --- a/apps/tag/filters.py +++ b/apps/tag/filters.py @@ -21,7 +21,6 @@ class TagsBaseFilterSet(filters.FilterSet): type = filters.MultipleChoiceFilter(choices=TYPE_CHOICES, method='filter_by_type') - def filter_by_type(self, queryset, name, value): if self.NEWS in value: queryset = queryset.for_news() diff --git a/apps/tag/migrations/0018_chosentag.py b/apps/tag/migrations/0018_chosentag.py new file mode 100644 index 00000000..c394cc7c --- /dev/null +++ b/apps/tag/migrations/0018_chosentag.py @@ -0,0 +1,26 @@ +# Generated by Django 2.2.7 on 2020-01-13 08:36 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0045_carousel_is_international'), + ('contenttypes', '0002_remove_content_type_name'), + ('tag', '0017_auto_20191220_1623'), + ] + + operations = [ + migrations.CreateModel( + name='ChosenTag', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('object_id', models.PositiveIntegerField()), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')), + ('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.SiteSettings', verbose_name='site')), + ('tag', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='chosen_tags', to='tag.Tag', verbose_name='tag')), + ], + ), + ] diff --git a/apps/tag/models.py b/apps/tag/models.py index 1b735724..006802f9 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -1,4 +1,5 @@ """Tag app models.""" +from django.contrib.contenttypes import fields as generic from django.contrib.contenttypes.models import ContentType from django.db import models from django.utils.translation import gettext_lazy as _ @@ -154,8 +155,9 @@ class TagCategory(models.Model): value_type = models.CharField(_('value type'), max_length=255, choices=VALUE_TYPE_CHOICES, default=LIST, ) old_id = models.IntegerField(blank=True, null=True) - translation = models.OneToOneField('translation.SiteInterfaceDictionary', on_delete=models.SET_NULL, - null=True, related_name='tag_category', verbose_name=_('Translation')) + translation = models.OneToOneField( + 'translation.SiteInterfaceDictionary', on_delete=models.SET_NULL, + null=True, related_name='tag_category', verbose_name=_('Translation')) @property def label_indexing(self): @@ -175,3 +177,20 @@ class TagCategory(models.Model): def __str__(self): return self.index_name + + +class ChosenTag(models.Model): + """Chosen tag for type.""" + tag = models.ForeignKey( + 'Tag', verbose_name=_('tag'), related_name='chosen_tags', + on_delete=models.CASCADE) + site = models.ForeignKey( + 'main.SiteSettings', verbose_name=_('site'), on_delete=models.CASCADE) + + content_type = models.ForeignKey( + generic.ContentType, on_delete=models.CASCADE) + object_id = models.PositiveIntegerField() + content_object = generic.GenericForeignKey('content_type', 'object_id') + + def __str__(self): + return f'chosen_tag:{self.tag}' diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py index 5ec87d17..06da4207 100644 --- a/apps/tag/serializers.py +++ b/apps/tag/serializers.py @@ -30,10 +30,6 @@ class TagBaseSerializer(serializers.ModelSerializer): return super().get_extra_kwargs() index_name = serializers.CharField(source='value', read_only=True, allow_null=True) - label_translated = serializers.SerializerMethodField(read_only=True, allow_null=True) - - def get_label_translated(self, obj): - return translate_obj(obj) class Meta: """Meta class.""" @@ -41,7 +37,6 @@ class TagBaseSerializer(serializers.ModelSerializer): model = models.Tag fields = ( 'id', - 'label_translated', 'index_name', ) @@ -49,13 +44,11 @@ class TagBaseSerializer(serializers.ModelSerializer): class TagBackOfficeSerializer(TagBaseSerializer): """Serializer for Tag model for Back office users.""" - label = serializers.DictField(source='translation.text') - class Meta(TagBaseSerializer.Meta): """Meta class.""" fields = TagBaseSerializer.Meta.fields + ( - 'label', + 'value', 'category' ) @@ -191,7 +184,7 @@ class TagCategoryBackOfficeDetailSerializer(TagCategoryBaseSerializer): class TagBindObjectSerializer(serializers.Serializer): - """Serializer for binding tag category and objects""" + """Serializer for binding tag category and objects.""" ESTABLISHMENT = 'establishment' NEWS = 'news' @@ -216,15 +209,20 @@ class TagBindObjectSerializer(serializers.Serializer): if obj_type == self.ESTABLISHMENT: establishment = Establishment.objects.filter(pk=obj_id).first() + if not establishment: raise BindingObjectNotFound() + if request.method == 'POST' and tag.establishments.filter( pk=establishment.pk).exists(): raise ObjectAlreadyAdded() + if request.method == 'DELETE' and not tag.establishments.filter( pk=establishment.pk).exists(): raise RemovedBindingObjectNotFound() + attrs['related_object'] = establishment + elif obj_type == self.NEWS: news = News.objects.filter(pk=obj_id).first() if not news: @@ -287,3 +285,64 @@ class TagCategoryBindObjectSerializer(serializers.Serializer): raise RemovedBindingObjectNotFound() attrs['related_object'] = news_type return attrs + + +class ChosenTagSerializer(serializers.ModelSerializer): + tag = TagBackOfficeSerializer(read_only=True) + + class Meta: + model = models.ChosenTag + fields = [ + 'id', + 'tag', + ] + + +class ChosenTagBindObjectSerializer(serializers.Serializer): + """Serializer for binding chosen tag and objects""" + + ESTABLISHMENT_TYPE = 'establishment_type' + NEWS_TYPE = 'news_type' + + TYPE_CHOICES = ( + (ESTABLISHMENT_TYPE, 'Establishment type'), + (NEWS_TYPE, 'News type'), + ) + + type = serializers.ChoiceField(TYPE_CHOICES) + object_id = serializers.IntegerField() + + def validate(self, attrs): + view = self.context.get('view') + request = self.context.get('request') + + obj_type = attrs.get('type') + obj_id = attrs.get('object_id') + + tag = view.get_object() + attrs['tag'] = tag + + if obj_type == self.ESTABLISHMENT_TYPE: + establishment_type = EstablishmentType.objects.filter(pk=obj_id). \ + first() + if not establishment_type: + raise BindingObjectNotFound() + if request.method == 'DELETE' and not establishment_type. \ + chosen_tags.filter(tag=tag). \ + exists(): + raise RemovedBindingObjectNotFound() + attrs['related_object'] = establishment_type + + elif obj_type == self.NEWS_TYPE: + news_type = NewsType.objects.filter(pk=obj_id).first() + if not news_type: + raise BindingObjectNotFound() + if request.method == 'POST' and news_type.chosen_tags. \ + filter(tag=tag).exists(): + raise ObjectAlreadyAdded() + if request.method == 'DELETE' and not news_type.chosen_tags. \ + filter(tag=tag).exists(): + raise RemovedBindingObjectNotFound() + attrs['related_object'] = news_type + + return attrs diff --git a/apps/tag/views.py b/apps/tag/views.py index 2b8eb4ef..4f1cd9ba 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -1,14 +1,15 @@ """Tag views.""" from django.conf import settings +from django.contrib.contenttypes.models import ContentType +from django.utils.translation import gettext_lazy as _ from rest_framework import generics, mixins, permissions, status, viewsets from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.serializers import ValidationError -from django.utils.translation import gettext_lazy as _ -from search_indexes import views as search_views from location.models import WineRegion from product.models import ProductType +from search_indexes import views as search_views from tag import filters, models, serializers @@ -292,6 +293,8 @@ class BindObjectMixin: def get_serializer_class(self): if self.action == 'bind_object': return self.bind_object_serializer_class + elif self.action == 'chosen': + return self.chosen_serializer_class return self.serializer_class def perform_binding(self, serializer): @@ -311,6 +314,17 @@ class BindObjectMixin: self.perform_unbinding(serializer) return Response(status=status.HTTP_204_NO_CONTENT) + @action(methods=['post', 'delete'], detail=True, url_path='chosen') + def chosen(self, request, pk=None): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + if request.method == 'POST': + self.perform_binding(serializer) + return Response(serializer.data, status=status.HTTP_201_CREATED) + elif request.method == 'DELETE': + self.perform_unbinding(serializer) + return Response(status=status.HTTP_204_NO_CONTENT) + class TagBackOfficeViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, @@ -322,12 +336,27 @@ class TagBackOfficeViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, queryset = models.Tag.objects.all() serializer_class = serializers.TagBackOfficeSerializer bind_object_serializer_class = serializers.TagBindObjectSerializer + chosen_serializer_class = serializers.ChosenTagBindObjectSerializer def perform_binding(self, serializer): data = serializer.validated_data tag = data.pop('tag') obj_type = data.get('type') related_object = data.get('related_object') + + # for compatible exist code + if self.action == 'chosen': + obj_type = ContentType.objects.get_for_model(models.ChosenTag) + models.ChosenTag.objects.update_or_create( + tag=tag, + content_type=obj_type, + object_id=related_object.id, + defaults={ + "content_object": related_object, + "site": self.request.user.last_country + }, + ) + if obj_type == self.bind_object_serializer_class.ESTABLISHMENT: tag.establishments.add(related_object) elif obj_type == self.bind_object_serializer_class.NEWS: @@ -338,6 +367,11 @@ class TagBackOfficeViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, tag = data.pop('tag') obj_type = data.get('type') related_object = data.get('related_object') + + # for compatible exist code + if self.action == 'chosen': + related_object.chosen_tags.filter(tag=tag).delete() + if obj_type == self.bind_object_serializer_class.ESTABLISHMENT: tag.establishments.remove(related_object) elif obj_type == self.bind_object_serializer_class.NEWS: