Merge branch 'feature/gm-192' into feature/gm-148

This commit is contained in:
evgeniy-st 2019-10-21 10:31:06 +03:00
commit 2dbe65a169
17 changed files with 439 additions and 54 deletions

View File

@ -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."""

View File

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

View File

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

View File

@ -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."""

View File

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

View File

@ -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()

View File

@ -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)

View File

@ -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)

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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

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

View File

@ -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()

View File

@ -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.')

View File

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