diff --git a/apps/account/serializers/common.py b/apps/account/serializers/common.py index 20016297..d2933747 100644 --- a/apps/account/serializers/common.py +++ b/apps/account/serializers/common.py @@ -99,6 +99,18 @@ class UserBaseSerializer(serializers.ModelSerializer): read_only_fields = fields +class UserShortSerializer(UserSerializer): + """Compact serializer for model User.""" + + class Meta(UserSerializer.Meta): + """Meta class.""" + fields = [ + 'id', + 'fullname', + 'email', + ] + + class ChangePasswordSerializer(serializers.ModelSerializer): """Serializer for model User.""" diff --git a/apps/establishment/admin.py b/apps/establishment/admin.py index e500ed6a..e6b0d991 100644 --- a/apps/establishment/admin.py +++ b/apps/establishment/admin.py @@ -64,13 +64,18 @@ class CompanyInline(admin.TabularInline): extra = 0 +class EstablishmentNote(admin.TabularInline): + model = models.EstablishmentNote + extra = 0 + + @admin.register(models.Establishment) class EstablishmentAdmin(BaseModelAdminMixin, admin.ModelAdmin): """Establishment admin.""" list_display = ['id', '__str__', 'image_tag', ] search_fields = ['id', 'name', 'index_name', 'slug'] list_filter = ['public_mark', 'toque_number'] - inlines = [GalleryImageInline, CompanyInline] + inlines = [GalleryImageInline, CompanyInline, EstablishmentNote] # inlines = [ # AwardInline, ContactPhoneInline, ContactEmailInline, diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 7fc0b2d6..e9f230f1 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -9,15 +9,15 @@ from django.contrib.contenttypes import fields as generic from django.contrib.gis.db.models.functions import Distance from django.contrib.gis.geos import Point from django.contrib.gis.measure import Distance as DistanceMeasure +from django.contrib.postgres.fields import ArrayField from django.core.exceptions import ValidationError +from django.core.validators import MinValueValidator, MaxValueValidator from django.db import models from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q from django.utils import timezone from django.utils.translation import gettext_lazy as _ from phonenumber_field.modelfields import PhoneNumberField from timezone_field import TimeZoneField -from django.contrib.postgres.fields import ArrayField -from django.core.validators import MinValueValidator, MaxValueValidator from collection.models import Collection from location.models import Address diff --git a/apps/product/models.py b/apps/product/models.py index aec37ae7..bd28d533 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -231,6 +231,12 @@ class Product(GalleryModelMixin, TranslatedFieldsMixin, BaseAttributes, HasTagsM """Override str dunder method.""" return f'{self.name}' + def delete(self, using=None, keep_parents=False): + """Overridden delete method""" + # Delete all related notes + self.notes.all().delete() + return super().delete(using, keep_parents) + @property def product_type_translated_name(self): """Get translated name of product type.""" @@ -431,7 +437,7 @@ class ProductNote(ProjectBaseMixin): old_id = models.PositiveIntegerField(null=True, blank=True) text = models.TextField(verbose_name=_('text')) product = models.ForeignKey(Product, on_delete=models.PROTECT, - related_name='product_notes', + related_name='notes', verbose_name=_('product')) user = models.ForeignKey('account.User', on_delete=models.PROTECT, null=True, diff --git a/apps/product/serializers/back.py b/apps/product/serializers/back.py index 630a815a..ffbf690d 100644 --- a/apps/product/serializers/back.py +++ b/apps/product/serializers/back.py @@ -7,6 +7,7 @@ from product import models from product.serializers import ProductDetailSerializer, ProductTypeBaseSerializer, \ ProductSubTypeBaseSerializer from tag.models import TagCategory +from account.serializers.common import UserShortSerializer class ProductBackOfficeGallerySerializer(serializers.ModelSerializer): @@ -127,3 +128,55 @@ class ProductSubTypeBackOfficeDetailSerializer(ProductSubTypeBaseSerializer): 'name', 'index_name', ] + + +class ProductNoteBaseSerializer(serializers.ModelSerializer): + """Serializer for model ProductNote.""" + + user_detail = UserShortSerializer(read_only=True, source='user') + + class Meta: + """Meta class.""" + model = models.ProductNote + fields = [ + 'id', + 'created', + 'modified', + 'text', + 'user', + 'user_detail', + 'product', + ] + extra_kwargs = { + 'created': {'read_only': True}, + 'modified': {'read_only': True}, + 'product': {'required': False, 'write_only': True}, + 'user': {'required': False, 'write_only': True}, + } + + @property + def serializer_view(self): + """Return view instance.""" + return self.context.get('view') + + +class ProductNoteListCreateSerializer(ProductNoteBaseSerializer): + """Serializer for List|Create action for model ProductNote.""" + + def create(self, validated_data): + """Overridden create method.""" + validated_data['user'] = self.user + validated_data['product'] = self.product + return super().create(validated_data) + + @property + def user(self): + """Return user instance from view.""" + if self.serializer_view: + return self.serializer_view.request.user + + @property + def product(self): + """Return product instance from view.""" + if self.serializer_view: + return self.serializer_view.get_object() diff --git a/apps/product/urls/back.py b/apps/product/urls/back.py index 7d3b1611..fc2aaad0 100644 --- a/apps/product/urls/back.py +++ b/apps/product/urls/back.py @@ -6,6 +6,8 @@ from product import views urlpatterns = [ path('', views.ProductListCreateBackOfficeView.as_view(), name='list-create'), path('/', views.ProductDetailBackOfficeView.as_view(), name='rud'), + path('/notes/', views.ProductNoteListCreateView.as_view(), name='note-list-create'), + path('/notes//', views.ProductNoteRUDView.as_view(), name='note-rud'), path('/gallery/', views.ProductBackOfficeGalleryListView.as_view(), name='gallery-list'), path('/gallery//', views.ProductBackOfficeGalleryCreateDestroyView.as_view(), diff --git a/apps/product/views/back.py b/apps/product/views/back.py index 2e072fc6..ac780849 100644 --- a/apps/product/views/back.py +++ b/apps/product/views/back.py @@ -12,7 +12,7 @@ from utils.views import CreateDestroyGalleryViewMixin class ProductBackOfficeMixinView(ProductBaseView): """Product back-office mixin view.""" - permission_classes = (permissions.IsAuthenticated,) + permission_classes = (permissions.IsAuthenticated, ) def get_queryset(self): """Override get_queryset method.""" @@ -135,3 +135,48 @@ class ProductSubTypeRUDBackOfficeView(BackOfficeListCreateMixin, generics.RetrieveUpdateDestroyAPIView): """Product sub type back-office retrieve-update-destroy view.""" serializer_class = serializers.ProductSubTypeBackOfficeDetailSerializer + + +class ProductNoteListCreateView(ProductBackOfficeMixinView, + BackOfficeListCreateMixin, + generics.ListCreateAPIView): + """Retrieve|Update|Destroy product note view.""" + + serializer_class = serializers.ProductNoteListCreateSerializer + + def get_object(self): + """Returns the object the view is displaying.""" + product_qs = models.Product.objects.all() + filtered_product_qs = self.filter_queryset(product_qs) + + product = get_object_or_404(filtered_product_qs, pk=self.kwargs['pk']) + + # May raise a permission denied + self.check_object_permissions(self.request, product) + + return product + + def get_queryset(self): + """Overridden get_queryset method.""" + return self.get_object().notes.all() + + +class ProductNoteRUDView(ProductBackOfficeMixinView, + BackOfficeListCreateMixin, + generics.RetrieveUpdateDestroyAPIView): + """Create|Retrieve|Update|Destroy product note view.""" + + serializer_class = serializers.ProductNoteBaseSerializer + + def get_object(self): + """Returns the object the view is displaying.""" + product_qs = models.Product.objects.all() + filtered_product_qs = self.filter_queryset(product_qs) + + product = get_object_or_404(filtered_product_qs, pk=self.kwargs['pk']) + note = get_object_or_404(product.notes.all(), pk=self.kwargs['note_pk']) + + # May raise a permission denied + self.check_object_permissions(self.request, note) + + return note