Merge branch 'feature/gm-192' into feature/gm-148
This commit is contained in:
commit
2dbe65a169
|
|
@ -10,6 +10,10 @@ class EstablishmentFilter(filters.FilterSet):
|
||||||
tag_id = filters.NumberFilter(field_name='tags__metadata__id',)
|
tag_id = filters.NumberFilter(field_name='tags__metadata__id',)
|
||||||
award_id = filters.NumberFilter(field_name='awards__id',)
|
award_id = filters.NumberFilter(field_name='awards__id',)
|
||||||
search = filters.CharFilter(method='search_text')
|
search = filters.CharFilter(method='search_text')
|
||||||
|
est_type = filters.ChoiceFilter(choices=models.EstablishmentType.INDEX_NAME_TYPES,
|
||||||
|
method='by_type')
|
||||||
|
est_subtype = filters.ChoiceFilter(choices=models.EstablishmentSubType.INDEX_NAME_TYPES,
|
||||||
|
method='by_subtype')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -19,6 +23,8 @@ class EstablishmentFilter(filters.FilterSet):
|
||||||
'tag_id',
|
'tag_id',
|
||||||
'award_id',
|
'award_id',
|
||||||
'search',
|
'search',
|
||||||
|
'est_type',
|
||||||
|
'est_subtype',
|
||||||
)
|
)
|
||||||
|
|
||||||
def search_text(self, queryset, name, value):
|
def search_text(self, queryset, name, value):
|
||||||
|
|
@ -27,6 +33,12 @@ class EstablishmentFilter(filters.FilterSet):
|
||||||
return queryset.search(value, locale=self.request.locale)
|
return queryset.search(value, locale=self.request.locale)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
def by_type(self, queryset, name, value):
|
||||||
|
return queryset.by_type(value)
|
||||||
|
|
||||||
|
def by_subtype(self, queryset, name, value):
|
||||||
|
return queryset.by_subtype(value)
|
||||||
|
|
||||||
|
|
||||||
class EstablishmentTypeTagFilter(filters.FilterSet):
|
class EstablishmentTypeTagFilter(filters.FilterSet):
|
||||||
"""Establishment tag filter set."""
|
"""Establishment tag filter set."""
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-16 11:33
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def fill_establishment_type(apps, schema_editor):
|
||||||
|
# We can't import the Person model directly as it may be a newer
|
||||||
|
# version than this migration expects. We use the historical version.
|
||||||
|
EstablishmentType = apps.get_model('establishment', 'EstablishmentType')
|
||||||
|
for n, et in enumerate(EstablishmentType.objects.all()):
|
||||||
|
et.index_name = f'Type {n}'
|
||||||
|
et.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0037_auto_20191015_1404'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishmenttype',
|
||||||
|
name='index_name',
|
||||||
|
field=models.CharField(blank=True, db_index=True, max_length=50, null=True, unique=True, default=None, verbose_name='Index name'),
|
||||||
|
),
|
||||||
|
migrations.RunPython(fill_establishment_type, migrations.RunPython.noop),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='establishmenttype',
|
||||||
|
name='index_name',
|
||||||
|
field=models.CharField(choices=[('restaurant', 'Restaurant'), ('artisan', 'Artisan'),
|
||||||
|
('producer', 'Producer')], db_index=True, max_length=50,
|
||||||
|
unique=True, verbose_name='Index name'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-18 13:47
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def fill_establishment_subtype(apps, schema_editor):
|
||||||
|
# We can't import the Person model directly as it may be a newer
|
||||||
|
# version than this migration expects. We use the historical version.
|
||||||
|
EstablishmentSubType = apps.get_model('establishment', 'EstablishmentSubType')
|
||||||
|
for n, et in enumerate(EstablishmentSubType.objects.all()):
|
||||||
|
et.index_name = f'Type {n}'
|
||||||
|
et.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('establishment', '0038_establishmenttype_index_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='establishmentsubtype',
|
||||||
|
name='index_name',
|
||||||
|
field=models.CharField(blank=True, db_index=True, max_length=50, null=True, unique=True, default=None, verbose_name='Index name'),
|
||||||
|
),
|
||||||
|
migrations.RunPython(fill_establishment_subtype),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='establishmentsubtype',
|
||||||
|
name='index_name',
|
||||||
|
field=models.CharField(choices=[('winery', 'Winery'), ], db_index=True, max_length=50,
|
||||||
|
unique=True, verbose_name='Index name'),
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
||||||
|
|
@ -30,8 +30,22 @@ class EstablishmentType(TranslatedFieldsMixin, ProjectBaseMixin):
|
||||||
|
|
||||||
STR_FIELD_NAME = 'name'
|
STR_FIELD_NAME = 'name'
|
||||||
|
|
||||||
|
# INDEX NAME CHOICES
|
||||||
|
RESTAURANT = 'restaurant'
|
||||||
|
ARTISAN = 'artisan'
|
||||||
|
PRODUCER = 'producer'
|
||||||
|
|
||||||
|
INDEX_NAME_TYPES = (
|
||||||
|
(RESTAURANT, _('Restaurant')),
|
||||||
|
(ARTISAN, _('Artisan')),
|
||||||
|
(PRODUCER, _('Producer')),
|
||||||
|
)
|
||||||
|
|
||||||
name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'),
|
name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'),
|
||||||
help_text='{"en-GB":"some text"}')
|
help_text='{"en-GB":"some text"}')
|
||||||
|
index_name = models.CharField(max_length=50, choices=INDEX_NAME_TYPES,
|
||||||
|
unique=True, db_index=True,
|
||||||
|
verbose_name=_('Index name'))
|
||||||
use_subtypes = models.BooleanField(_('Use subtypes'), default=True)
|
use_subtypes = models.BooleanField(_('Use subtypes'), default=True)
|
||||||
tag_categories = models.ManyToManyField('tag.TagCategory',
|
tag_categories = models.ManyToManyField('tag.TagCategory',
|
||||||
related_name='establishment_types',
|
related_name='establishment_types',
|
||||||
|
|
@ -57,8 +71,18 @@ class EstablishmentSubTypeManager(models.Manager):
|
||||||
class EstablishmentSubType(ProjectBaseMixin, TranslatedFieldsMixin):
|
class EstablishmentSubType(ProjectBaseMixin, TranslatedFieldsMixin):
|
||||||
"""Establishment type model."""
|
"""Establishment type model."""
|
||||||
|
|
||||||
|
# INDEX NAME CHOICES
|
||||||
|
WINERY = 'winery'
|
||||||
|
|
||||||
|
INDEX_NAME_TYPES = (
|
||||||
|
(WINERY, _('Winery')),
|
||||||
|
)
|
||||||
|
|
||||||
name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'),
|
name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'),
|
||||||
help_text='{"en-GB":"some text"}')
|
help_text='{"en-GB":"some text"}')
|
||||||
|
index_name = models.CharField(max_length=50, choices=INDEX_NAME_TYPES,
|
||||||
|
unique=True, db_index=True,
|
||||||
|
verbose_name=_('Index name'))
|
||||||
establishment_type = models.ForeignKey(EstablishmentType,
|
establishment_type = models.ForeignKey(EstablishmentType,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
verbose_name=_('Type'))
|
verbose_name=_('Type'))
|
||||||
|
|
@ -93,6 +117,9 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
'phones').\
|
'phones').\
|
||||||
prefetch_actual_employees()
|
prefetch_actual_employees()
|
||||||
|
|
||||||
|
def with_type_related(self):
|
||||||
|
return self.prefetch_related('establishment_subtypes')
|
||||||
|
|
||||||
def search(self, value, locale=None):
|
def search(self, value, locale=None):
|
||||||
"""Search text in JSON fields."""
|
"""Search text in JSON fields."""
|
||||||
if locale is not None:
|
if locale is not None:
|
||||||
|
|
@ -240,6 +267,31 @@ class EstablishmentQuerySet(models.QuerySet):
|
||||||
kwargs = {unit: radius}
|
kwargs = {unit: radius}
|
||||||
return self.filter(address__coordinates__distance_lte=(center, DistanceMeasure(**kwargs)))
|
return self.filter(address__coordinates__distance_lte=(center, DistanceMeasure(**kwargs)))
|
||||||
|
|
||||||
|
def artisans(self):
|
||||||
|
"""Return artisans."""
|
||||||
|
return self.filter(establishment_type__index_name=EstablishmentType.ARTISAN)
|
||||||
|
|
||||||
|
def producers(self):
|
||||||
|
"""Return producers."""
|
||||||
|
return self.filter(establishment_type__index_name=EstablishmentType.PRODUCER)
|
||||||
|
|
||||||
|
def restaurants(self):
|
||||||
|
"""Return restaurants."""
|
||||||
|
return self.filter(establishment_type__index_name=EstablishmentType.RESTAURANT)
|
||||||
|
|
||||||
|
def wineries(self):
|
||||||
|
"""Return wineries."""
|
||||||
|
return self.producers().filter(
|
||||||
|
establishment_subtypes__index_name=EstablishmentSubType.WINERY)
|
||||||
|
|
||||||
|
def by_type(self, value):
|
||||||
|
"""Return QuerySet with type by value."""
|
||||||
|
return self.filter(establishment_type__index_name=value)
|
||||||
|
|
||||||
|
def by_subtype(self, value):
|
||||||
|
"""Return QuerySet with subtype by value."""
|
||||||
|
return self.filter(establishment_subtypes__index_name=value)
|
||||||
|
|
||||||
|
|
||||||
class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
|
class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
|
||||||
"""Establishment model."""
|
"""Establishment model."""
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ urlpatterns = [
|
||||||
path('', views.EstablishmentListView.as_view(), name='list'),
|
path('', views.EstablishmentListView.as_view(), name='list'),
|
||||||
path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(),
|
path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(),
|
||||||
name='recent-reviews'),
|
name='recent-reviews'),
|
||||||
|
# path('wineries/', views.WineriesListView.as_view(), name='wineries-list'),
|
||||||
path('slug/<slug:slug>/', views.EstablishmentRetrieveView.as_view(), name='detail'),
|
path('slug/<slug:slug>/', views.EstablishmentRetrieveView.as_view(), name='detail'),
|
||||||
path('slug/<slug:slug>/similar/', views.EstablishmentSimilarListView.as_view(), name='similar'),
|
path('slug/<slug:slug>/similar/', views.EstablishmentSimilarListView.as_view(), name='similar'),
|
||||||
path('slug/<slug:slug>/comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'),
|
path('slug/<slug:slug>/comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'),
|
||||||
|
|
|
||||||
|
|
@ -174,3 +174,14 @@ class EstablishmentNearestRetrieveView(EstablishmentListView, generics.ListAPIVi
|
||||||
return qs.by_distance_from_point(**{k: v for k, v in filter_kwargs.items()
|
return qs.by_distance_from_point(**{k: v for k, v in filter_kwargs.items()
|
||||||
if v is not None})
|
if v is not None})
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
# Wineries
|
||||||
|
# todo: find out about difference between subtypes data
|
||||||
|
# class WineriesListView(EstablishmentListView):
|
||||||
|
# """Return list establishments with type Wineries"""
|
||||||
|
#
|
||||||
|
# def get_queryset(self):
|
||||||
|
# """Overridden get_queryset method."""
|
||||||
|
# qs = super(WineriesListView, self).get_queryset()
|
||||||
|
# return qs.with_type_related().wineries()
|
||||||
|
|
|
||||||
|
|
@ -40,12 +40,8 @@ def update_document(sender, **kwargs):
|
||||||
for establishment in establishments:
|
for establishment in establishments:
|
||||||
registry.update(establishment)
|
registry.update(establishment)
|
||||||
|
|
||||||
if app_label == 'main':
|
if app_label == 'tag':
|
||||||
if model_name == 'metadata':
|
if model_name == 'tag':
|
||||||
establishments = Establishment.objects.filter(tags__metadata=instance)
|
|
||||||
for establishment in establishments:
|
|
||||||
registry.update(establishment)
|
|
||||||
if model_name == 'metadatacontent':
|
|
||||||
establishments = Establishment.objects.filter(tags=instance)
|
establishments = Establishment.objects.filter(tags=instance)
|
||||||
for establishment in establishments:
|
for establishment in establishments:
|
||||||
registry.update(establishment)
|
registry.update(establishment)
|
||||||
|
|
@ -70,12 +66,8 @@ def update_news(sender, **kwargs):
|
||||||
for news in qs:
|
for news in qs:
|
||||||
registry.update(news)
|
registry.update(news)
|
||||||
|
|
||||||
if app_label == 'main':
|
if app_label == 'tag':
|
||||||
if model_name == 'metadata':
|
if model_name == 'tag':
|
||||||
qs = News.objects.filter(tags__metadata=instance)
|
|
||||||
for news in qs:
|
|
||||||
registry.update(news)
|
|
||||||
if model_name == 'metadatacontent':
|
|
||||||
qs = News.objects.filter(tags=instance)
|
qs = News.objects.filter(tags=instance)
|
||||||
for news in qs:
|
for news in qs:
|
||||||
registry.update(news)
|
registry.update(news)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
"""Tag app filters."""
|
"""Tag app filters."""
|
||||||
from django_filters import rest_framework as filters
|
from django_filters import rest_framework as filters
|
||||||
|
from establishment.models import EstablishmentType
|
||||||
from tag import models
|
from tag import models
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -18,16 +19,9 @@ class TagCategoryFilterSet(filters.FilterSet):
|
||||||
type = filters.MultipleChoiceFilter(choices=TYPE_CHOICES,
|
type = filters.MultipleChoiceFilter(choices=TYPE_CHOICES,
|
||||||
method='filter_by_type')
|
method='filter_by_type')
|
||||||
|
|
||||||
# Establishment type choices
|
establishment_type = filters.ChoiceFilter(
|
||||||
RESTAURANT = 'restaurant'
|
choices=EstablishmentType.INDEX_NAME_TYPES,
|
||||||
|
method='by_establishment_type')
|
||||||
ESTABLISHMENT_TYPE_CHOICES = (
|
|
||||||
(RESTAURANT, 'restaurant'),
|
|
||||||
)
|
|
||||||
|
|
||||||
establishment_type = filters.MultipleChoiceFilter(
|
|
||||||
choices=ESTABLISHMENT_TYPE_CHOICES,
|
|
||||||
method='filter_by_establishment_type')
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
@ -44,5 +38,5 @@ class TagCategoryFilterSet(filters.FilterSet):
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
# todo: filter by establishment type
|
# todo: filter by establishment type
|
||||||
def filter_by_establishment_type(self, queryset, name, value):
|
def by_establishment_type(self, queryset, name, value):
|
||||||
return queryset.for_establishments()
|
return queryset.by_establishment_type(value)
|
||||||
|
|
|
||||||
19
apps/tag/migrations/0003_auto_20191018_0758.py
Normal file
19
apps/tag/migrations/0003_auto_20191018_0758.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-18 07:58
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tag', '0002_auto_20191009_1408'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='tag',
|
||||||
|
name='category',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='tags', to='tag.TagCategory', verbose_name='Category'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -11,7 +11,7 @@ class Tag(TranslatedFieldsMixin, models.Model):
|
||||||
label = TJSONField(blank=True, null=True, default=None,
|
label = TJSONField(blank=True, null=True, default=None,
|
||||||
verbose_name=_('label'),
|
verbose_name=_('label'),
|
||||||
help_text='{"en-GB":"some text"}')
|
help_text='{"en-GB":"some text"}')
|
||||||
category = models.ForeignKey('TagCategory', on_delete=models.PROTECT,
|
category = models.ForeignKey('TagCategory', on_delete=models.CASCADE,
|
||||||
null=True, related_name='tags',
|
null=True, related_name='tags',
|
||||||
verbose_name=_('Category'))
|
verbose_name=_('Category'))
|
||||||
# chosen tags should have non-null priority,other tags priority should be null
|
# chosen tags should have non-null priority,other tags priority should be null
|
||||||
|
|
@ -38,6 +38,10 @@ class TagCategoryQuerySet(models.QuerySet):
|
||||||
"""Select related objects."""
|
"""Select related objects."""
|
||||||
return self.prefetch_related('tags')
|
return self.prefetch_related('tags')
|
||||||
|
|
||||||
|
def with_extended_related(self):
|
||||||
|
"""Select related objects."""
|
||||||
|
return self.select_related('country')
|
||||||
|
|
||||||
def for_news(self):
|
def for_news(self):
|
||||||
"""Select tag categories for news."""
|
"""Select tag categories for news."""
|
||||||
return self.filter(news_types__isnull=True)
|
return self.filter(news_types__isnull=True)
|
||||||
|
|
@ -47,6 +51,10 @@ class TagCategoryQuerySet(models.QuerySet):
|
||||||
return self.filter(models.Q(establishment_types__isnull=False) |
|
return self.filter(models.Q(establishment_types__isnull=False) |
|
||||||
models.Q(establishment_subtypes__isnull=False))
|
models.Q(establishment_subtypes__isnull=False))
|
||||||
|
|
||||||
|
def by_establishment_type(self, index_name):
|
||||||
|
"""Filter by establishment type index name."""
|
||||||
|
return self.filter(establishment_types__index_name=index_name)
|
||||||
|
|
||||||
def with_tags(self, switcher=True):
|
def with_tags(self, switcher=True):
|
||||||
"""Filter by existing tags."""
|
"""Filter by existing tags."""
|
||||||
return self.exclude(tags__isnull=switcher)
|
return self.exclude(tags__isnull=switcher)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
"""Tag serializers."""
|
"""Tag serializers."""
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
from establishment.models import (Establishment, EstablishmentType,
|
||||||
|
EstablishmentSubType)
|
||||||
|
from news.models import News, NewsType
|
||||||
from tag import models
|
from tag import models
|
||||||
|
from utils.exceptions import (ObjectAlreadyAdded, BindingObjectNotFound,
|
||||||
|
RemovedBindingObjectNotFound)
|
||||||
from utils.serializers import TranslatedField
|
from utils.serializers import TranslatedField
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -49,14 +54,114 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
class TagCategoryBackOfficeDetailSerializer(TagCategoryBaseSerializer):
|
class TagCategoryBackOfficeDetailSerializer(TagCategoryBaseSerializer):
|
||||||
|
"""Tag Category detail serializer for back-office users."""
|
||||||
|
|
||||||
country_translated = TranslatedField(source='country.name_translated')
|
country_translated = TranslatedField(source='country.name_translated')
|
||||||
|
|
||||||
class Meta(TagBaseSerializer.Meta):
|
class Meta(TagCategoryBaseSerializer.Meta):
|
||||||
"""Meta class."""
|
"""Meta class."""
|
||||||
|
|
||||||
fields = TagCategoryBaseSerializer.Meta.fields + (
|
fields = TagCategoryBaseSerializer.Meta.fields + (
|
||||||
'news_types',
|
'label',
|
||||||
'country',
|
'country',
|
||||||
'country_translated',
|
'country_translated',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TagBindObjectSerializer(serializers.Serializer):
|
||||||
|
"""Serializer for binding tag category and objects"""
|
||||||
|
|
||||||
|
ESTABLISHMENT = 'establishment'
|
||||||
|
NEWS = 'news'
|
||||||
|
|
||||||
|
TYPE_CHOICES = (
|
||||||
|
(ESTABLISHMENT, 'Establishment type'),
|
||||||
|
(NEWS, '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:
|
||||||
|
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:
|
||||||
|
raise BindingObjectNotFound()
|
||||||
|
if request.method == 'POST' and tag.news.filter(pk=news.pk).exists():
|
||||||
|
raise ObjectAlreadyAdded()
|
||||||
|
if request.method == 'DELETE' and not tag.news.filter(
|
||||||
|
pk=news.pk).exists():
|
||||||
|
raise RemovedBindingObjectNotFound()
|
||||||
|
attrs['related_object'] = news
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
class TagCategoryBindObjectSerializer(serializers.Serializer):
|
||||||
|
"""Serializer for binding tag category 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_category = view.get_object()
|
||||||
|
attrs['tag_category'] = tag_category
|
||||||
|
|
||||||
|
if obj_type == self.ESTABLISHMENT_TYPE:
|
||||||
|
establishment_type = EstablishmentType.objects.filter(pk=obj_id).\
|
||||||
|
first()
|
||||||
|
if not establishment_type:
|
||||||
|
raise BindingObjectNotFound()
|
||||||
|
if request.method == 'POST' and tag_category.establishment_types.\
|
||||||
|
filter(pk=establishment_type.pk).exists():
|
||||||
|
raise ObjectAlreadyAdded()
|
||||||
|
if request.method == 'DELETE' and not tag_category.\
|
||||||
|
establishment_types.filter(pk=establishment_type.pk).\
|
||||||
|
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 tag_category.news_types.\
|
||||||
|
filter(pk=news_type.pk).exists():
|
||||||
|
raise ObjectAlreadyAdded()
|
||||||
|
if request.method == 'DELETE' and not tag_category.news_types.\
|
||||||
|
filter(pk=news_type.pk).exists():
|
||||||
|
raise RemovedBindingObjectNotFound()
|
||||||
|
attrs['related_object'] = news_type
|
||||||
|
return attrs
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,11 @@
|
||||||
"""Urlconf for app tag."""
|
"""Urlconf for app tag."""
|
||||||
from django.urls import path
|
|
||||||
from rest_framework.routers import SimpleRouter
|
from rest_framework.routers import SimpleRouter
|
||||||
from tag import views
|
from tag import views
|
||||||
|
|
||||||
app_name = 'tag'
|
app_name = 'tag'
|
||||||
|
|
||||||
router = SimpleRouter()
|
router = SimpleRouter()
|
||||||
router.register(r'', views.TagViewSet)
|
router.register(r'categories', views.TagCategoryBackOfficeViewSet)
|
||||||
|
router.register(r'', views.TagBackOfficeViewSet)
|
||||||
|
|
||||||
|
urlpatterns = router.urls
|
||||||
urlpatterns = [
|
|
||||||
path('category/', views.TagCategoryListCreateView.as_view(), name='category-list-create'),
|
|
||||||
]
|
|
||||||
|
|
||||||
urlpatterns += router.urls
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,111 @@
|
||||||
"""Tag views."""
|
"""Tag views."""
|
||||||
from rest_framework import viewsets, mixins
|
from rest_framework import viewsets, mixins, status
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.response import Response
|
||||||
from tag import filters, models, serializers
|
from tag import filters, models, serializers
|
||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
|
|
||||||
|
|
||||||
class TagViewSet(mixins.ListModelMixin, mixins.CreateModelMixin,
|
# User`s views & viewsets
|
||||||
mixins.UpdateModelMixin, mixins.DestroyModelMixin,
|
|
||||||
viewsets.GenericViewSet):
|
|
||||||
"""List/create tag view."""
|
|
||||||
|
|
||||||
pagination_class = None
|
|
||||||
queryset = models.Tag.objects.all()
|
|
||||||
serializer_class = serializers.TagBackOfficeSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class TagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
|
class TagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
|
||||||
"""ViewSet for TagCategory model."""
|
"""ViewSet for TagCategory model."""
|
||||||
|
|
||||||
filterset_class = filters.TagCategoryFilterSet
|
filterset_class = filters.TagCategoryFilterSet
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
permission_classes = (permissions.AllowAny, )
|
permission_classes = (permissions.AllowAny, )
|
||||||
queryset = models.TagCategory.objects.with_tags().with_base_related()
|
queryset = models.TagCategory.objects.with_tags().with_base_related().\
|
||||||
|
distinct()
|
||||||
serializer_class = serializers.TagCategoryBaseSerializer
|
serializer_class = serializers.TagCategoryBaseSerializer
|
||||||
|
|
||||||
|
|
||||||
|
# BackOffice user`s views & viewsets
|
||||||
|
class BindObjectMixin:
|
||||||
|
"""Bind object mixin."""
|
||||||
|
|
||||||
|
def get_serializer_class(self):
|
||||||
|
if self.action == 'bind_object':
|
||||||
|
return self.bind_object_serializer_class
|
||||||
|
return self.serializer_class
|
||||||
|
|
||||||
|
def perform_binding(self, serializer):
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
|
def perform_unbinding(self, serializer):
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
|
@action(methods=['post', 'delete'], detail=True, url_path='bind-object')
|
||||||
|
def bind_object(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,
|
||||||
|
BindObjectMixin, viewsets.GenericViewSet):
|
||||||
|
"""List/create tag view."""
|
||||||
|
|
||||||
|
pagination_class = None
|
||||||
|
permission_classes = (permissions.IsAuthenticated, )
|
||||||
|
queryset = models.Tag.objects.all()
|
||||||
|
serializer_class = serializers.TagBackOfficeSerializer
|
||||||
|
bind_object_serializer_class = serializers.TagBindObjectSerializer
|
||||||
|
|
||||||
|
def perform_binding(self, serializer):
|
||||||
|
data = serializer.validated_data
|
||||||
|
tag = data.pop('tag')
|
||||||
|
obj_type = data.get('type')
|
||||||
|
related_object = data.get('related_object')
|
||||||
|
if obj_type == self.bind_object_serializer_class.ESTABLISHMENT:
|
||||||
|
tag.establishments.add(related_object)
|
||||||
|
elif obj_type == self.bind_object_serializer_class.NEWS:
|
||||||
|
tag.news.add(related_object)
|
||||||
|
|
||||||
|
def perform_unbinding(self, serializer):
|
||||||
|
data = serializer.validated_data
|
||||||
|
tag = data.pop('tag')
|
||||||
|
obj_type = data.get('type')
|
||||||
|
related_object = data.get('related_object')
|
||||||
|
if obj_type == self.bind_object_serializer_class.ESTABLISHMENT:
|
||||||
|
tag.establishments.remove(related_object)
|
||||||
|
elif obj_type == self.bind_object_serializer_class.NEWS:
|
||||||
|
tag.news.remove(related_object)
|
||||||
|
|
||||||
|
|
||||||
|
class TagCategoryBackOfficeViewSet(mixins.CreateModelMixin,
|
||||||
|
mixins.UpdateModelMixin,
|
||||||
|
mixins.DestroyModelMixin,
|
||||||
|
mixins.RetrieveModelMixin,
|
||||||
|
BindObjectMixin,
|
||||||
|
TagCategoryViewSet):
|
||||||
|
"""ViewSet for TagCategory model for BackOffice users."""
|
||||||
|
|
||||||
|
permission_classes = (permissions.IsAuthenticated, )
|
||||||
|
queryset = TagCategoryViewSet.queryset.with_extended_related()
|
||||||
|
serializer_class = serializers.TagCategoryBackOfficeDetailSerializer
|
||||||
|
bind_object_serializer_class = serializers.TagCategoryBindObjectSerializer
|
||||||
|
|
||||||
|
def perform_binding(self, serializer):
|
||||||
|
data = serializer.validated_data
|
||||||
|
tag_category = data.pop('tag_category')
|
||||||
|
obj_type = data.get('type')
|
||||||
|
related_object = data.get('related_object')
|
||||||
|
if obj_type == self.bind_object_serializer_class.ESTABLISHMENT_TYPE:
|
||||||
|
tag_category.establishment_types.add(related_object)
|
||||||
|
elif obj_type == self.bind_object_serializer_class.NEWS_TYPE:
|
||||||
|
tag_category.news_types.add(related_object)
|
||||||
|
|
||||||
|
def perform_unbinding(self, serializer):
|
||||||
|
data = serializer.validated_data
|
||||||
|
tag_category = data.pop('tag_category')
|
||||||
|
obj_type = data.get('type')
|
||||||
|
related_object = data.get('related_object')
|
||||||
|
if obj_type == self.bind_object_serializer_class.ESTABLISHMENT_TYPE:
|
||||||
|
tag_category.establishment_types.remove(related_object)
|
||||||
|
elif obj_type == self.bind_object_serializer_class.NEWS_TYPE:
|
||||||
|
tag_category.news_types.remove(related_object)
|
||||||
|
|
|
||||||
18
apps/translation/migrations/0004_auto_20191018_0832.py
Normal file
18
apps/translation/migrations/0004_auto_20191018_0832.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.4 on 2019-10-18 08:32
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('translation', '0003_auto_20190901_1032'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='language',
|
||||||
|
name='locale',
|
||||||
|
field=models.CharField(max_length=10, unique=True, verbose_name='Locale identifier'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -22,7 +22,7 @@ class Language(models.Model):
|
||||||
|
|
||||||
title = models.CharField(max_length=255,
|
title = models.CharField(max_length=255,
|
||||||
verbose_name=_('Language title'))
|
verbose_name=_('Language title'))
|
||||||
locale = models.CharField(max_length=10,
|
locale = models.CharField(max_length=10, unique=True,
|
||||||
verbose_name=_('Locale identifier'))
|
verbose_name=_('Locale identifier'))
|
||||||
|
|
||||||
objects = LanguageQuerySet.as_manager()
|
objects = LanguageQuerySet.as_manager()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import exceptions, status
|
from rest_framework import exceptions, serializers, status
|
||||||
|
|
||||||
|
|
||||||
class ProjectBaseException(exceptions.APIException):
|
class ProjectBaseException(exceptions.APIException):
|
||||||
|
|
@ -142,3 +142,24 @@ class PasswordResetRequestExistedError(exceptions.APIException):
|
||||||
"""
|
"""
|
||||||
status_code = status.HTTP_400_BAD_REQUEST
|
status_code = status.HTTP_400_BAD_REQUEST
|
||||||
default_detail = _('Password reset request is already exists and valid.')
|
default_detail = _('Password reset request is already exists and valid.')
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectAlreadyAdded(serializers.ValidationError):
|
||||||
|
"""
|
||||||
|
The exception must be thrown if the object has already been added to the
|
||||||
|
list.
|
||||||
|
"""
|
||||||
|
|
||||||
|
default_detail = _('Object has already been added.')
|
||||||
|
|
||||||
|
|
||||||
|
class BindingObjectNotFound(serializers.ValidationError):
|
||||||
|
"""The exception must be thrown if the object not found."""
|
||||||
|
|
||||||
|
default_detail = _('Binding object not found.')
|
||||||
|
|
||||||
|
|
||||||
|
class RemovedBindingObjectNotFound(serializers.ValidationError):
|
||||||
|
"""The exception must be thrown if the object not found."""
|
||||||
|
|
||||||
|
default_detail = _('Removed binding object not found.')
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@ from django.urls import path, include
|
||||||
app_name = 'back'
|
app_name = 'back'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('gallery/', include(('gallery.urls', 'gallery'), namespace='gallery')),
|
|
||||||
path('establishments/', include('establishment.urls.back')),
|
|
||||||
path('location/', include('location.urls.back')),
|
|
||||||
path('news/', include('news.urls.back')),
|
|
||||||
# path('tags/', include(('tag.urls.back', 'tag'), namespace='tag')),
|
|
||||||
path('account/', include('account.urls.back')),
|
path('account/', include('account.urls.back')),
|
||||||
path('comment/', include('comment.urls.back')),
|
path('comment/', include('comment.urls.back')),
|
||||||
|
path('establishments/', include('establishment.urls.back')),
|
||||||
|
path('gallery/', include(('gallery.urls', 'gallery'), namespace='gallery')),
|
||||||
|
path('location/', include('location.urls.back')),
|
||||||
|
path('news/', include('news.urls.back')),
|
||||||
|
path('tags/', include(('tag.urls.back', 'tag'), namespace='tag')),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user