Backoffice Tag&TagCategory
This commit is contained in:
parent
7be7315cef
commit
21e3f76f5a
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,
|
||||
verbose_name=_('label'),
|
||||
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',
|
||||
verbose_name=_('Category'))
|
||||
|
||||
|
|
@ -36,6 +36,10 @@ class TagCategoryQuerySet(models.QuerySet):
|
|||
"""Select related objects."""
|
||||
return self.prefetch_related('tags')
|
||||
|
||||
def with_extended_related(self):
|
||||
"""Select related objects."""
|
||||
return self.select_related('country')
|
||||
|
||||
def for_news(self):
|
||||
"""Select tag categories for news."""
|
||||
return self.filter(news_types__isnull=True)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
"""Tag serializers."""
|
||||
from rest_framework import serializers
|
||||
from establishment.models import (Establishment, EstablishmentType,
|
||||
EstablishmentSubType)
|
||||
from news.models import News, NewsType
|
||||
from tag import models
|
||||
from utils.exceptions import (ObjectAlreadyAdded, BindingObjectNotFound,
|
||||
RemovedBindingObjectNotFound)
|
||||
from utils.serializers import TranslatedField
|
||||
|
||||
|
||||
|
|
@ -49,14 +54,109 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer):
|
|||
|
||||
|
||||
class TagCategoryBackOfficeDetailSerializer(TagCategoryBaseSerializer):
|
||||
"""Tag Category detail serializer for back-office users."""
|
||||
|
||||
country_translated = TranslatedField(source='country.name_translated')
|
||||
|
||||
class Meta(TagBaseSerializer.Meta):
|
||||
class Meta(TagCategoryBaseSerializer.Meta):
|
||||
"""Meta class."""
|
||||
|
||||
fields = TagCategoryBaseSerializer.Meta.fields + (
|
||||
'news_types',
|
||||
'label',
|
||||
'country',
|
||||
'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):
|
||||
obj_type = attrs.get('type')
|
||||
obj_id = attrs.get('object_id')
|
||||
request = self.context.get('request')
|
||||
view = self.context.get('view')
|
||||
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):
|
||||
obj_type = attrs.get('type')
|
||||
obj_id = attrs.get('object_id')
|
||||
view = self.context.get('view')
|
||||
tag_category = view.get_object()
|
||||
attrs['tag_category'] = tag_category
|
||||
request = self.context.get('request')
|
||||
|
||||
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:
|
||||
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."""
|
||||
from django.urls import path
|
||||
from rest_framework.routers import SimpleRouter
|
||||
from tag import views
|
||||
|
||||
app_name = 'tag'
|
||||
|
||||
router = SimpleRouter()
|
||||
router.register(r'', views.TagViewSet)
|
||||
router.register(r'categories', views.TagCategoryBackOfficeViewSet)
|
||||
router.register(r'', views.TagBackOfficeViewSet)
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('category/', views.TagCategoryListCreateView.as_view(), name='category-list-create'),
|
||||
]
|
||||
|
||||
urlpatterns += router.urls
|
||||
urlpatterns = router.urls
|
||||
|
|
|
|||
|
|
@ -1,19 +1,12 @@
|
|||
"""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 rest_framework import permissions
|
||||
|
||||
|
||||
class TagViewSet(mixins.ListModelMixin, mixins.CreateModelMixin,
|
||||
mixins.UpdateModelMixin, mixins.DestroyModelMixin,
|
||||
viewsets.GenericViewSet):
|
||||
"""List/create tag view."""
|
||||
|
||||
pagination_class = None
|
||||
queryset = models.Tag.objects.all()
|
||||
serializer_class = serializers.TagBackOfficeSerializer
|
||||
|
||||
|
||||
# User`s views & viewsets
|
||||
class TagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
|
||||
"""ViewSet for TagCategory model."""
|
||||
|
||||
|
|
@ -23,3 +16,96 @@ class TagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
|
|||
queryset = models.TagCategory.objects.with_tags().with_base_related().\
|
||||
distinct()
|
||||
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,
|
||||
verbose_name=_('Language title'))
|
||||
locale = models.CharField(max_length=10,
|
||||
locale = models.CharField(max_length=10, unique=True,
|
||||
verbose_name=_('Locale identifier'))
|
||||
|
||||
objects = LanguageQuerySet.as_manager()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
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):
|
||||
|
|
@ -142,3 +142,24 @@ class PasswordResetRequestExistedError(exceptions.APIException):
|
|||
"""
|
||||
status_code = status.HTTP_400_BAD_REQUEST
|
||||
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'
|
||||
|
||||
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('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