from django.core.validators import MinValueValidator, MaxValueValidator from django.core.files.base import ContentFile from rest_framework import serializers from sorl.thumbnail import get_thumbnail from sorl.thumbnail.parsers import parse_crop, ThumbnailParseError from django.utils.translation import gettext_lazy as _ from django.conf import settings from . import models class ImageSerializer(serializers.ModelSerializer): """Serializer for model Image.""" # REQUEST file = serializers.ImageField(source='image', write_only=True) # RESPONSE url = serializers.ImageField(source='image', read_only=True) orientation_display = serializers.CharField(source='get_orientation_display', read_only=True) class Meta: """Meta class""" model = models.Image fields = [ 'id', 'file', 'url', 'orientation', 'orientation_display', 'title', ] extra_kwargs = { 'orientation': {'write_only': True} } def validate(self, attrs): """Overridden validate method.""" image = attrs.get('image') if image and image.size >= settings.FILE_UPLOAD_MAX_MEMORY_SIZE: raise serializers.ValidationError({'detail': _('File size too large: %s bytes') % image.size}) return attrs class CropImageSerializer(ImageSerializer): """Serializers for image crops.""" width = serializers.IntegerField(write_only=True, required=False) height = serializers.IntegerField(write_only=True, required=False) crop = serializers.CharField(write_only=True, required=False, default='center') quality = serializers.IntegerField(write_only=True, required=False, default=settings.THUMBNAIL_QUALITY, validators=[ MinValueValidator(1), MaxValueValidator(100)]) cropped_image = serializers.DictField(read_only=True, allow_null=True) certain_aspect = serializers.CharField(allow_blank=True, allow_null=True, required=False) class Meta(ImageSerializer.Meta): """Meta class.""" fields = [ 'id', 'url', 'orientation_display', 'width', 'height', 'crop', 'quality', 'cropped_image', 'certain_aspect', ] def validate(self, attrs): """Overridden validate method.""" file = self._image.image crop_width = attrs.get('width') crop_height = attrs.get('height') crop = attrs.get('crop') if (crop_height and crop_width) and (crop and crop != 'smart'): xy_image = (file.width, file.width) xy_window = (crop_width, crop_height) try: parse_crop(crop, xy_image, xy_window) attrs['image'] = file except ThumbnailParseError: raise serializers.ValidationError({'margin': _('Unrecognized crop option: %s') % crop}) return attrs def create(self, validated_data): """Overridden create method.""" width = validated_data.pop('width', None) height = validated_data.pop('height', None) crop = validated_data.pop('crop') x1, y1 = int(crop.split(' ')[0][:-2]), int(crop.split(' ')[1][:-2]) x2, y2 = x1 + width, y1 + height crop_params = { 'geometry': f'{round(x2 - x1)}x{round(y2 - y1)}' if 'certain_aspect' not in validated_data else validated_data['certain_aspect'], 'quality': 100, 'cropbox': f'{x1},{y1},{x2},{y2}' } cropped_image = self._image.get_cropped_image(**crop_params) image = self._image image.pk = None crop_params['geometry_string'] = crop_params.pop('geometry') resized = get_thumbnail(self._image.image, **crop_params) image.image.save(resized.name, ContentFile(resized.read()), True) image.save() if image and width and height: setattr(image, 'cropped_image', cropped_image) 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.')})