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 django.shortcuts import get_object_or_404 from establishment.models import Establishment, EstablishmentGallery from account.serializers.common import UserBaseSerializer 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 EstablishmentGallerySerializer(serializers.ModelSerializer): """Serializer for creating and retrieving establishment media""" type = serializers.ChoiceField(read_only=True, choices=models.Image.MEDIA_TYPES) created_by = UserBaseSerializer(read_only=True, allow_null=True) image_size_in_KB = serializers.DecimalField(read_only=True, decimal_places=2, max_digits=20) is_main = serializers.BooleanField() cropped_image = serializers.ImageField(source='image_by_cropbox', allow_null=True, read_only=True) class Meta: model = models.Image fields = ( 'id', 'image', 'type', 'link', 'order', 'preview', 'is_public', 'title', 'is_main', 'created_by', 'image_size_in_KB', 'cropbox', 'cropped_image', ) extra_kwargs = { 'created': {'read_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}) if attrs.get('cropbox'): if len(attrs['cropbox'].split(',')) != 4: raise serializers.ValidationError({'detail': _('Cropbox contains 4 integer values separated by comma.')}) return attrs def create(self, validated_data): is_main = validated_data.pop('is_main') establishment = get_object_or_404(klass=Establishment, pk=self.context['view'].kwargs['establishment_id']) instance = super().create(validated_data) instance.created_by = self.context['request'].user instance.establishment_set.add(establishment) instance.save() if is_main: EstablishmentGallery.objects.filter( establishment=establishment ).update(is_main=False) # reset all before setting True on some instance EstablishmentGallery.objects.filter( image=instance ).update(is_main=is_main) return instance def update(self, instance: models.Image, validated_data): if instance.is_public != validated_data.get('is_public'): instance.set_pubic(validated_data.get('is_public', True)) if 'is_main' in validated_data: is_main = validated_data.pop('is_main') if is_main: establishment = instance.establishment_gallery.all()[0].establishment EstablishmentGallery.objects.filter( establishment=establishment ).update(is_main=False) # reset all before setting True on some instance EstablishmentGallery.objects.filter( image=instance ).update(is_main=is_main) return super().update(instance, validated_data) 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.')})