Backoffice Tag&TagCategory

This commit is contained in:
evgeniy-st 2019-10-18 18:02:24 +03:00
parent 7be7315cef
commit 21e3f76f5a
9 changed files with 272 additions and 29 deletions

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

View File

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

View File

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

View File

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

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

View File

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

View File

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