from django.db import models from django.utils.translation import gettext_lazy as _ from django.core import validators from sorl.thumbnail import get_thumbnail from botocore.exceptions import ClientError from django.conf import settings from project.storage_backends import PublicMediaStorage import boto3 from sorl import thumbnail from sorl.thumbnail.fields import ImageField as SORLImageField from utils.methods import image_path from utils.models import ProjectBaseMixin, SORLImageMixin, PlatformMixin, BaseAttributes class ImageQuerySet(models.QuerySet): """QuerySet for model Image.""" class Image(BaseAttributes, SORLImageMixin, PlatformMixin): """Image model.""" HORIZONTAL = 0 VERTICAL = 1 MEDIA_TYPES = ( _('photo'), _('video'), _('youtube'), ) ORIENTATIONS = ( (HORIZONTAL, _('Horizontal')), (VERTICAL, _('Vertical')), ) image = SORLImageField(max_length=255, upload_to=image_path, verbose_name=_('image file'), default=None, null=True) orientation = models.PositiveSmallIntegerField(choices=ORIENTATIONS, blank=True, null=True, default=None, verbose_name=_('image orientation')) title = models.CharField(_('title'), max_length=255, default='') is_public = models.BooleanField(default=True, verbose_name=_('Is media source public')) preview = SORLImageField(max_length=255, upload_to=image_path, verbose_name=_('image preview'), null=True, default=None) link = models.URLField(blank=True, null=True, default=None, verbose_name=_('mp4 or youtube video link')) order = models.PositiveIntegerField(default=0, verbose_name=_('Sorting order')) cropbox = models.CharField(max_length=500, validators=[validators.validate_comma_separated_integer_list], null=True, default=None, verbose_name=_('x1,y1,x2,y2 crop settings')) objects = ImageQuerySet.as_manager() class Meta: """Meta class.""" verbose_name = _('Image') verbose_name_plural = _('Images') ordering = ['-modified'] def __str__(self): """String representation""" return f'{self.id}' @property def image_by_cropbox(self): """Returns cropped image if cropbox is set""" if self.cropbox and self.image: x1, y1, x2, y2 = map(int, self.cropbox.split(',')) return get_thumbnail(self.image, geometry_string=f'{round(x2 - x1)}x{round(y2 - y1)}', cropbox=self.cropbox, quality=100) def set_pubic(self, is_public=True): if not settings.AWS_STORAGE_BUCKET_NAME: """Backend doesn't use aws s3""" return s3 = boto3.resource('s3', region_name=settings.AWS_S3_REGION_NAME, aws_access_key_id=settings.AWS_ACCESS_KEY_ID, aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY) bucket = s3.Bucket(settings.AWS_STORAGE_BUCKET_NAME) if self.image: file_object = bucket.Object(f'{PublicMediaStorage.location}/{str(self.image.file)}') if is_public: file_object.Acl().put(ACL='public-read') else: file_object.Acl().put(ACL='authenticated-read') @property def is_main(self) -> bool: establishment_gallery_list = list(self.establishment_gallery.all()) if establishment_gallery_list and len(establishment_gallery_list): return establishment_gallery_list[0].is_main @property def type(self) -> str: if self.image: return self.MEDIA_TYPES[0] if self.link is not None and self.link.endswith('.mp4'): return self.MEDIA_TYPES[1] return self.MEDIA_TYPES[2] @property def image_size_in_KB(self): try: return self.image.size / 1000 if self.image else None except (FileNotFoundError, ClientError): return None def delete_image(self, completely: bool = True): """ Deletes an instance and crops of instance from media storage. :param completely: if set to False then removed only crop neither original image. """ try: # Delete from remote storage thumbnail.delete(file_=self.image.file, delete_file=completely) except FileNotFoundError: pass finally: if completely: # Delete an instance of image super().delete()