diff --git a/apps/gallery/serializers.py b/apps/gallery/serializers.py index 1f96dca8..2c2e50d0 100644 --- a/apps/gallery/serializers.py +++ b/apps/gallery/serializers.py @@ -3,6 +3,7 @@ from django.core.validators import MinValueValidator, MaxValueValidator from rest_framework import serializers from sorl.thumbnail.parsers import parse_crop from sorl.thumbnail.parsers import ThumbnailParseError +from django.utils.translation import gettext_lazy as _ from . import models @@ -12,21 +13,10 @@ class ImageSerializer(serializers.ModelSerializer): # REQUEST file = serializers.ImageField(source='image', write_only=True) - width = serializers.IntegerField(write_only=True, required=False) - height = serializers.IntegerField(write_only=True, required=False) - margin = serializers.CharField(write_only=True, allow_null=True, - required=False, - default='center') - quality = serializers.IntegerField(write_only=True, allow_null=True, required=False, - default=settings.THUMBNAIL_QUALITY, - validators=[ - MinValueValidator(1), - MaxValueValidator(100)]) # RESPONSE url = serializers.ImageField(source='image', read_only=True) - cropped_image = serializers.DictField(read_only=True, allow_null=True) orientation_display = serializers.CharField(source='get_orientation_display', read_only=True) @@ -40,30 +30,55 @@ class ImageSerializer(serializers.ModelSerializer): 'orientation', 'orientation_display', 'title', + ] + extra_kwargs = { + 'orientation': {'write_only': True} + } + + +class CropImageSerializer(ImageSerializer): + """Serializers for image crops.""" + + width = serializers.IntegerField(write_only=True) + height = serializers.IntegerField(write_only=True) + margin = serializers.CharField(write_only=True, allow_null=True, + required=False, + default='center') + quality = serializers.IntegerField(write_only=True, allow_null=True, required=False, + default=settings.THUMBNAIL_QUALITY, + validators=[ + MinValueValidator(1), + MaxValueValidator(100)]) + cropped_image = serializers.DictField(read_only=True, allow_null=True) + + class Meta(ImageSerializer.Meta): + """Meta class.""" + fields = [ + 'id', + 'url', + 'orientation_display', 'width', 'height', 'margin', 'quality', 'cropped_image', ] - extra_kwargs = { - 'orientation': {'write_only': True} - } def validate(self, attrs): """Overridden validate method.""" - image = attrs.get('image').image + file = self._image.image crop_width = attrs.get('width') crop_height = attrs.get('height') margin = attrs.get('margin') if crop_height and crop_width and margin: - xy_image = (image.width, image.width) + xy_image = (file.width, file.width) xy_window = (crop_width, crop_height) try: parse_crop(margin, xy_image, xy_window) + attrs['image'] = file except ThumbnailParseError: - raise serializers.ValidationError({'margin': 'Unrecognized crop option: %s' % margin}) + raise serializers.ValidationError({'margin': _('Unrecognized crop option: %s') % margin}) return attrs def create(self, validated_data): @@ -73,13 +88,32 @@ class ImageSerializer(serializers.ModelSerializer): quality = validated_data.pop('quality') margin = validated_data.pop('margin') - instance = super().create(validated_data) + image = self._image - if instance and width and height: - setattr(instance, + if image and width and height: + setattr(image, 'cropped_image', - instance.get_cropped_image( + image.get_cropped_image( geometry=f'{width}x{height}', quality=quality, margin=margin)) - return instance + return image + + @property + def view(self): + return self.context.get('view') + + @property + def lookup_field(self): + lookup_field = 'pk' + + if lookup_field in self.view.kwargs: + return self.view.kwargs.get(lookup_field) + + @property + def _image(self): + """Return image from url_kwargs.""" + qs = models.Image.objects.filter(id=self.lookup_field) + if qs.exists(): + return qs.first() + raise serializers.ValidationError({'detail': _('Image not found.')}) diff --git a/apps/gallery/urls.py b/apps/gallery/urls.py index 8258092c..987685cb 100644 --- a/apps/gallery/urls.py +++ b/apps/gallery/urls.py @@ -6,6 +6,7 @@ from . import views app_name = 'gallery' urlpatterns = [ - path('', views.ImageListCreateView.as_view(), name='list-create-image'), - path('/', views.ImageRetrieveDestroyView.as_view(), name='retrieve-destroy-image'), + path('', views.ImageListCreateView.as_view(), name='list-create'), + path('/', views.ImageRetrieveDestroyView.as_view(), name='retrieve-destroy'), + path('/crop/', views.CropImageCreateView.as_view(), name='create-crop'), ] diff --git a/apps/gallery/views.py b/apps/gallery/views.py index 2b155035..1515707f 100644 --- a/apps/gallery/views.py +++ b/apps/gallery/views.py @@ -28,3 +28,8 @@ class ImageRetrieveDestroyView(ImageBaseView, generics.RetrieveDestroyAPIView): else: on_commit(lambda: tasks.delete_image(image_id=instance.id)) return Response(status=status.HTTP_204_NO_CONTENT) + + +class CropImageCreateView(ImageBaseView, generics.CreateAPIView): + """Create crop image.""" + serializer_class = serializers.CropImageSerializer