Merge branch 'develop' into phone_list

This commit is contained in:
Dmitriy Kuzmenko 2020-01-14 15:59:46 +03:00
commit 9aeedff81e
13 changed files with 193 additions and 16 deletions

View File

@ -147,8 +147,8 @@ class User(AbstractUser):
) )
EMAIL_FIELD = 'email' EMAIL_FIELD = 'email'
USERNAME_FIELD = 'username' USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['email'] REQUIRED_FIELDS = ['username']
roles = models.ManyToManyField( roles = models.ManyToManyField(
Role, verbose_name=_('Roles'), symmetrical=False, Role, verbose_name=_('Roles'), symmetrical=False,

View File

@ -8,6 +8,8 @@ from comment.serializers import common as comment_serializers
from establishment import models from establishment import models
from location.serializers import AddressBaseSerializer, CitySerializer, AddressDetailSerializer, \ from location.serializers import AddressBaseSerializer, CitySerializer, AddressDetailSerializer, \
CityShortSerializer CityShortSerializer
from location.serializers import EstablishmentWineRegionBaseSerializer, \
EstablishmentWineOriginBaseSerializer
from main.serializers import AwardSerializer, CurrencySerializer from main.serializers import AwardSerializer, CurrencySerializer
from review.serializers import ReviewShortSerializer from review.serializers import ReviewShortSerializer
from tag.serializers import TagBaseSerializer from tag.serializers import TagBaseSerializer
@ -16,8 +18,6 @@ from utils import exceptions as utils_exceptions
from utils.serializers import ImageBaseSerializer, CarouselCreateSerializer from utils.serializers import ImageBaseSerializer, CarouselCreateSerializer
from utils.serializers import (ProjectModelSerializer, TranslatedField, from utils.serializers import (ProjectModelSerializer, TranslatedField,
FavoritesCreateSerializer) FavoritesCreateSerializer)
from location.serializers import EstablishmentWineRegionBaseSerializer, \
EstablishmentWineOriginBaseSerializer
class ContactPhonesSerializer(serializers.ModelSerializer): class ContactPhonesSerializer(serializers.ModelSerializer):

View File

@ -6,7 +6,7 @@ from django.contrib.contenttypes import fields as generic
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.contrib.postgres.fields import JSONField from django.contrib.postgres.fields import JSONField
from django.core.validators import EMPTY_VALUES from django.core.validators import EMPTY_VALUES
from django.db import connections, connection from django.db import connections
from django.db import models from django.db import models
from django.db.models import Q from django.db.models import Q
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -16,6 +16,7 @@ from configuration.models import TranslationSettings
from location.models import Country from location.models import Country
from main import methods from main import methods
from review.models import Review from review.models import Review
from tag.models import Tag
from utils.exceptions import UnprocessableEntityError from utils.exceptions import UnprocessableEntityError
from utils.methods import dictfetchall from utils.methods import dictfetchall
from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin, from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin,
@ -117,6 +118,8 @@ class Feature(ProjectBaseMixin, PlatformMixin):
site_settings = models.ManyToManyField(SiteSettings, through='SiteFeature') site_settings = models.ManyToManyField(SiteSettings, through='SiteFeature')
old_id = models.IntegerField(null=True, blank=True) old_id = models.IntegerField(null=True, blank=True)
chosen_tags = generic.GenericRelation(to='tag.ChosenTag')
class Meta: class Meta:
"""Meta class.""" """Meta class."""
verbose_name = _('Feature') verbose_name = _('Feature')
@ -125,6 +128,10 @@ class Feature(ProjectBaseMixin, PlatformMixin):
def __str__(self): def __str__(self):
return f'{self.slug}' return f'{self.slug}'
@property
def get_chosen_tags(self):
return Tag.objects.filter(chosen_tags__in=self.chosen_tags.all()).distinct()
class SiteFeatureQuerySet(models.QuerySet): class SiteFeatureQuerySet(models.QuerySet):
"""Extended queryset for SiteFeature model.""" """Extended queryset for SiteFeature model."""

View File

@ -2,11 +2,12 @@
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from rest_framework import serializers from rest_framework import serializers
from account.models import User
from account.serializers.back import BackUserSerializer
from location.serializers import CountrySerializer from location.serializers import CountrySerializer
from main import models from main import models
from tag.serializers import TagBackOfficeSerializer
from utils.serializers import ProjectModelSerializer, TranslatedField, RecursiveFieldSerializer from utils.serializers import ProjectModelSerializer, TranslatedField, RecursiveFieldSerializer
from account.serializers.back import BackUserSerializer
from account.models import User
class FeatureSerializer(serializers.ModelSerializer): class FeatureSerializer(serializers.ModelSerializer):
@ -90,6 +91,8 @@ class SiteFeatureSerializer(serializers.ModelSerializer):
route = serializers.CharField(source='feature.route.name') route = serializers.CharField(source='feature.route.name')
source = serializers.IntegerField(source='feature.source') source = serializers.IntegerField(source='feature.source')
nested = RecursiveFieldSerializer(many=True, allow_null=True) nested = RecursiveFieldSerializer(many=True, allow_null=True)
chosen_tags = TagBackOfficeSerializer(
source='feature.get_chosen_tags', many=True, read_only=True)
class Meta: class Meta:
"""Meta class.""" """Meta class."""
@ -101,6 +104,7 @@ class SiteFeatureSerializer(serializers.ModelSerializer):
'route', 'route',
'source', 'source',
'nested', 'nested',
'chosen_tags',
) )

View File

@ -85,6 +85,14 @@ class EstablishmentDocument(Document):
'value': fields.KeywordField(), 'value': fields.KeywordField(),
}, },
multi=True) multi=True)
distillery_types = fields.ObjectField(
properties={
'id': fields.IntegerField(attr='id'),
'label': fields.ObjectField(attr='label_indexing',
properties=OBJECT_FIELD_PROPERTIES),
'value': fields.KeywordField(),
},
multi=True)
products = fields.ObjectField( products = fields.ObjectField(
properties={ properties={
'wine_origins': fields.ListField( 'wine_origins': fields.ListField(

View File

@ -277,6 +277,7 @@ class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer):
tags = TagsDocumentSerializer(many=True, source='visible_tags') tags = TagsDocumentSerializer(many=True, source='visible_tags')
restaurant_category = TagsDocumentSerializer(many=True, allow_null=True) restaurant_category = TagsDocumentSerializer(many=True, allow_null=True)
restaurant_cuisine = TagsDocumentSerializer(many=True, allow_null=True) restaurant_cuisine = TagsDocumentSerializer(many=True, allow_null=True)
distillery_types = TagsDocumentSerializer(many=True, allow_null=True)
artisan_category = TagsDocumentSerializer(many=True, allow_null=True) artisan_category = TagsDocumentSerializer(many=True, allow_null=True)
schedule = ScheduleDocumentSerializer(many=True, allow_null=True) schedule = ScheduleDocumentSerializer(many=True, allow_null=True)
wine_origins = WineOriginSerializer(many=True) wine_origins = WineOriginSerializer(many=True)
@ -310,6 +311,7 @@ class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer):
# 'collections', # 'collections',
'type', 'type',
'subtypes', 'subtypes',
'distillery_types',
) )

View File

@ -21,7 +21,6 @@ class TagsBaseFilterSet(filters.FilterSet):
type = filters.MultipleChoiceFilter(choices=TYPE_CHOICES, type = filters.MultipleChoiceFilter(choices=TYPE_CHOICES,
method='filter_by_type') method='filter_by_type')
def filter_by_type(self, queryset, name, value): def filter_by_type(self, queryset, name, value):
if self.NEWS in value: if self.NEWS in value:
queryset = queryset.for_news() queryset = queryset.for_news()

View File

@ -0,0 +1,19 @@
# Generated by Django 2.2.7 on 2020-01-13 13:57
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('tag', '0017_auto_20191220_1623'),
]
operations = [
migrations.AlterField(
model_name='tagcategory',
name='country',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='location.Country'),
),
]

View File

@ -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')),
],
),
]

View File

@ -0,0 +1,14 @@
# Generated by Django 2.2.7 on 2020-01-14 07:56
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('tag', '0018_auto_20200113_1357'),
('tag', '0018_chosentag'),
]
operations = [
]

View File

@ -1,4 +1,5 @@
"""Tag app models.""" """Tag app models."""
from django.contrib.contenttypes import fields as generic
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -145,8 +146,8 @@ class TagCategory(models.Model):
(BOOLEAN, _('boolean')), (BOOLEAN, _('boolean')),
) )
country = models.ForeignKey('location.Country', country = models.ForeignKey('location.Country',
on_delete=models.SET_NULL, null=True, on_delete=models.SET_NULL,
default=None) blank=True, null=True, default=None)
public = models.BooleanField(default=False) public = models.BooleanField(default=False)
index_name = models.CharField(max_length=255, blank=True, null=True, index_name = models.CharField(max_length=255, blank=True, null=True,
verbose_name=_('indexing name'), unique=True) verbose_name=_('indexing name'), unique=True)
@ -154,7 +155,8 @@ class TagCategory(models.Model):
value_type = models.CharField(_('value type'), max_length=255, value_type = models.CharField(_('value type'), max_length=255,
choices=VALUE_TYPE_CHOICES, default=LIST, ) choices=VALUE_TYPE_CHOICES, default=LIST, )
old_id = models.IntegerField(blank=True, null=True) old_id = models.IntegerField(blank=True, null=True)
translation = models.OneToOneField('translation.SiteInterfaceDictionary', on_delete=models.SET_NULL, translation = models.OneToOneField(
'translation.SiteInterfaceDictionary', on_delete=models.SET_NULL,
null=True, related_name='tag_category', verbose_name=_('Translation')) null=True, related_name='tag_category', verbose_name=_('Translation'))
@property @property
@ -175,3 +177,20 @@ class TagCategory(models.Model):
def __str__(self): def __str__(self):
return self.index_name 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}'

View File

@ -9,6 +9,7 @@ from tag import models
from utils.exceptions import BindingObjectNotFound, ObjectAlreadyAdded, RemovedBindingObjectNotFound from utils.exceptions import BindingObjectNotFound, ObjectAlreadyAdded, RemovedBindingObjectNotFound
from utils.serializers import TranslatedField from utils.serializers import TranslatedField
from utils.models import get_default_locale, get_language, to_locale from utils.models import get_default_locale, get_language, to_locale
from main.models import Feature
def translate_obj(obj): def translate_obj(obj):
@ -56,7 +57,8 @@ class TagBackOfficeSerializer(TagBaseSerializer):
fields = TagBaseSerializer.Meta.fields + ( fields = TagBaseSerializer.Meta.fields + (
'label', 'label',
'category' 'category',
'value',
) )
@ -191,7 +193,7 @@ class TagCategoryBackOfficeDetailSerializer(TagCategoryBaseSerializer):
class TagBindObjectSerializer(serializers.Serializer): class TagBindObjectSerializer(serializers.Serializer):
"""Serializer for binding tag category and objects""" """Serializer for binding tag category and objects."""
ESTABLISHMENT = 'establishment' ESTABLISHMENT = 'establishment'
NEWS = 'news' NEWS = 'news'
@ -216,15 +218,20 @@ class TagBindObjectSerializer(serializers.Serializer):
if obj_type == self.ESTABLISHMENT: if obj_type == self.ESTABLISHMENT:
establishment = Establishment.objects.filter(pk=obj_id).first() establishment = Establishment.objects.filter(pk=obj_id).first()
if not establishment: if not establishment:
raise BindingObjectNotFound() raise BindingObjectNotFound()
if request.method == 'POST' and tag.establishments.filter( if request.method == 'POST' and tag.establishments.filter(
pk=establishment.pk).exists(): pk=establishment.pk).exists():
raise ObjectAlreadyAdded() raise ObjectAlreadyAdded()
if request.method == 'DELETE' and not tag.establishments.filter( if request.method == 'DELETE' and not tag.establishments.filter(
pk=establishment.pk).exists(): pk=establishment.pk).exists():
raise RemovedBindingObjectNotFound() raise RemovedBindingObjectNotFound()
attrs['related_object'] = establishment attrs['related_object'] = establishment
elif obj_type == self.NEWS: elif obj_type == self.NEWS:
news = News.objects.filter(pk=obj_id).first() news = News.objects.filter(pk=obj_id).first()
if not news: if not news:
@ -287,3 +294,41 @@ class TagCategoryBindObjectSerializer(serializers.Serializer):
raise RemovedBindingObjectNotFound() raise RemovedBindingObjectNotFound()
attrs['related_object'] = news_type attrs['related_object'] = news_type
return attrs 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"""
feature_id = serializers.IntegerField()
def validate(self, attrs):
view = self.context.get('view')
request = self.context.get('request')
obj_id = attrs.get('feature_id')
tag = view.get_object()
attrs['tag'] = tag
feature = Feature.objects.filter(pk=obj_id). \
first()
if not feature:
raise BindingObjectNotFound()
if request.method == 'DELETE' and not feature. \
chosen_tags.filter(tag=tag). \
exists():
raise RemovedBindingObjectNotFound()
attrs['related_object'] = feature
return attrs

View File

@ -1,14 +1,15 @@
"""Tag views.""" """Tag views."""
from django.conf import settings 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 import generics, mixins, permissions, status, viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.serializers import ValidationError 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 location.models import WineRegion
from product.models import ProductType from product.models import ProductType
from search_indexes import views as search_views
from tag import filters, models, serializers from tag import filters, models, serializers
@ -292,6 +293,8 @@ class BindObjectMixin:
def get_serializer_class(self): def get_serializer_class(self):
if self.action == 'bind_object': if self.action == 'bind_object':
return self.bind_object_serializer_class return self.bind_object_serializer_class
elif self.action == 'chosen':
return self.chosen_serializer_class
return self.serializer_class return self.serializer_class
def perform_binding(self, serializer): def perform_binding(self, serializer):
@ -311,6 +314,17 @@ class BindObjectMixin:
self.perform_unbinding(serializer) self.perform_unbinding(serializer)
return Response(status=status.HTTP_204_NO_CONTENT) 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, class TagBackOfficeViewSet(mixins.ListModelMixin, mixins.CreateModelMixin,
mixins.UpdateModelMixin, mixins.DestroyModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin,
@ -322,12 +336,27 @@ class TagBackOfficeViewSet(mixins.ListModelMixin, mixins.CreateModelMixin,
queryset = models.Tag.objects.all() queryset = models.Tag.objects.all()
serializer_class = serializers.TagBackOfficeSerializer serializer_class = serializers.TagBackOfficeSerializer
bind_object_serializer_class = serializers.TagBindObjectSerializer bind_object_serializer_class = serializers.TagBindObjectSerializer
chosen_serializer_class = serializers.ChosenTagBindObjectSerializer
def perform_binding(self, serializer): def perform_binding(self, serializer):
data = serializer.validated_data data = serializer.validated_data
tag = data.pop('tag') tag = data.pop('tag')
obj_type = data.get('type') obj_type = data.get('type')
related_object = data.get('related_object') 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: if obj_type == self.bind_object_serializer_class.ESTABLISHMENT:
tag.establishments.add(related_object) tag.establishments.add(related_object)
elif obj_type == self.bind_object_serializer_class.NEWS: elif obj_type == self.bind_object_serializer_class.NEWS:
@ -338,6 +367,11 @@ class TagBackOfficeViewSet(mixins.ListModelMixin, mixins.CreateModelMixin,
tag = data.pop('tag') tag = data.pop('tag')
obj_type = data.get('type') obj_type = data.get('type')
related_object = data.get('related_object') 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: if obj_type == self.bind_object_serializer_class.ESTABLISHMENT:
tag.establishments.remove(related_object) tag.establishments.remove(related_object)
elif obj_type == self.bind_object_serializer_class.NEWS: elif obj_type == self.bind_object_serializer_class.NEWS: