From 402bd32eda8e1399ad065c2f210a76fe83733932 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 2 Oct 2019 14:10:23 +0300 Subject: [PATCH] gm-148: modified news gallery --- apps/gallery/models.py | 17 +++++++++++ apps/gallery/serializers.py | 5 ++-- apps/gallery/tasks.py | 27 +++++++++++++++++ apps/news/models.py | 13 ++++----- apps/news/serializers.py | 52 +++++++++++++++++++++++++-------- apps/news/views.py | 15 +++++++++- project/settings/development.py | 2 +- 7 files changed, 107 insertions(+), 24 deletions(-) create mode 100644 apps/gallery/tasks.py diff --git a/apps/gallery/models.py b/apps/gallery/models.py index 687b42fc..81bfbb40 100644 --- a/apps/gallery/models.py +++ b/apps/gallery/models.py @@ -3,12 +3,18 @@ from django.utils.translation import gettext_lazy as _ from sorl.thumbnail.fields import ImageField as SORLImageField from utils.methods import image_path +from django.conf import settings +from . import tasks from utils.models import ProjectBaseMixin, SORLImageMixin, PlatformMixin class ImageQuerySet(models.QuerySet): """QuerySet for model Image.""" + def originals(self): + """Return QuerySet with original images.""" + return self.filter(parent__isnull=True) + class Image(ProjectBaseMixin, SORLImageMixin, PlatformMixin): """Image model.""" @@ -42,3 +48,14 @@ class Image(ProjectBaseMixin, SORLImageMixin, PlatformMixin): def __str__(self): """String representation""" return str(self.id) + + def delete_from_remote_storage(self, delete_original: bool = True): + """Delete from remote storage""" + if settings.USE_CELERY: + tasks.delete_image_from_remote_storage.delay(self.id, delete_original) + else: + tasks.delete_image_from_remote_storage(self.id, delete_original) + + @property + def childs(self): + return self.parent_image.filter(parent=self) diff --git a/apps/gallery/serializers.py b/apps/gallery/serializers.py index f575001a..08ca4a0f 100644 --- a/apps/gallery/serializers.py +++ b/apps/gallery/serializers.py @@ -8,8 +8,6 @@ class ImageSerializer(serializers.ModelSerializer): # REQUEST file = serializers.ImageField(source='image', write_only=True) - orientation = serializers.ChoiceField(choices=models.Image.ORIENTATIONS, - write_only=True) # RESPONSE url = serializers.ImageField(source='image', @@ -29,3 +27,6 @@ class ImageSerializer(serializers.ModelSerializer): 'orientation_display', 'title', ] + extra_kwargs = { + 'orientation': {'write_only': True} + } diff --git a/apps/gallery/tasks.py b/apps/gallery/tasks.py new file mode 100644 index 00000000..8f3f5453 --- /dev/null +++ b/apps/gallery/tasks.py @@ -0,0 +1,27 @@ +"""Gallery app celery tasks.""" +import logging + +from celery import shared_task +from sorl.thumbnail import delete + +from . import models + +logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.INFO) +logger = logging.getLogger(__name__) + + +@shared_task +def delete_image_from_remote_storage(image_id, delete_original=True): + """Delete an image from remote storage.""" + image_qs = models.Image.objects.filter(id=image_id) + if image_qs.exists(): + try: + image = image_qs.first() + # Delete from remote storage + delete(file_=image.image.file, delete_file=delete_original) + # Delete an instance of image + image.delete() + except: + logger.error(f'METHOD_NAME: delete_image_from_remote_storage\n' + f'DETAIL: Exception occurred while deleting an image ' + f'from remote storage: image_id - {image_id}') diff --git a/apps/news/models.py b/apps/news/models.py index ed40e09b..c128b8ed 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -4,6 +4,7 @@ from django.db import models from django.utils import timezone from django.utils.translation import gettext_lazy as _ from rest_framework.reverse import reverse +from sorl.thumbnail import delete from utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin from random import sample as random_sample @@ -157,18 +158,14 @@ class News(BaseAttributes, TranslatedFieldsMixin): def web_url(self): return reverse('web:news:rud', kwargs={'slug': self.slug}) + @property + def original_images(self): + return self.gallery.originals() + class NewsGalleryQuerySet(models.QuerySet): """QuerySet for model News""" - def originals(self): - """Return QuerySet with original images.""" - return self.filter(gallery__parent__isnull=True) - - def crops(self): - """Return QuerySet with cropped images.""" - return self.filter(gallery__parent__isnull=False) - class NewsGallery(models.Model): diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 8bed7692..e4b8c1c7 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -12,23 +12,51 @@ from news import models from utils.serializers import TranslatedField, ProjectModelSerializer -class NewsImageSerializer(ImageSerializer): - """News images""" - promo_web_url = serializers.SerializerMethodField() - promo_mobile_url = serializers.SerializerMethodField() +class NewsCropImageSerializer(ImageSerializer): + """Serializer for returning crop images of news image.""" + orientation_display = serializers.CharField(source='get_orientation_display', + read_only=True) + web_url = serializers.SerializerMethodField() + mobile_url = serializers.SerializerMethodField() class Meta: model = Image - fields = ImageSerializer.Meta.fields + [ - 'promo_web_url', - 'promo_mobile_url' + fields = [ + 'id', + 'title', + 'orientation_display', + 'web_url', + 'mobile_url', ] + extra_kwargs = { + 'orientation': {'write_only': True} + } - def get_promo_web_url(self, obj): - return obj.get_image_url(thumbnail_key='news_promo_horizontal_web') + def get_web_url(self, obj): + """Return URL of cropped image by thumbnail.""" + return obj.get_image_url('news_promo_horizontal_web') - def get_promo_mobile_url(self, obj): - return obj.get_image_url(thumbnail_key='news_promo_horizontal_mobile') + def get_mobile_url(self, obj): + """Return URL of cropped image by thumbnail.""" + return obj.get_image_url('news_promo_horizontal_mobile') + + +class NewsImageSerializer(ImageSerializer): + """News images""" + url = serializers.URLField(source='image.url', read_only=True) + crops = NewsCropImageSerializer(source='childs', allow_null=True, many=True) + + class Meta: + model = Image + fields = [ + 'id', + 'title', + 'url', + 'crops', + ] + extra_kwargs = { + 'orientation': {'write_only': True} + } class NewsTypeSerializer(serializers.ModelSerializer): @@ -51,7 +79,7 @@ class NewsBaseSerializer(ProjectModelSerializer): # related fields news_type = NewsTypeSerializer(read_only=True) tags = MetaDataContentSerializer(read_only=True, many=True) - gallery = NewsImageSerializer(read_only=True, many=True) + gallery = NewsImageSerializer(source='original_images', read_only=True, many=True) class Meta: """Meta class.""" diff --git a/apps/news/views.py b/apps/news/views.py index ca2c01cd..ba998c11 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -4,6 +4,7 @@ from rest_framework import generics, permissions, status from rest_framework.response import Response from news import filters, models, serializers +from gallery.serializers import ImageSerializer class NewsMixinView: @@ -90,10 +91,22 @@ class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView, super().create(request, *args, **kwargs) return Response(status=status.HTTP_201_CREATED) + def destroy(self, request, *args, **kwargs): + """Override destroy method.""" + gallery_obj = self.get_object() + image_obj = gallery_obj.image + + # Delete an instances of NewsGallery model + gallery_obj.delete() + # Delete an instance of Image model + image_obj.delete_from_remote_storage() + + return Response(status=status.HTTP_204_NO_CONTENT) + class NewsBackOfficeGalleryListView(NewsBackOfficeMixinView, generics.ListAPIView): """Resource for returning gallery for news for back-office users.""" - serializer_class = serializers.NewsImageSerializer + serializer_class = ImageSerializer def get_object(self): """Override get_object method.""" diff --git a/project/settings/development.py b/project/settings/development.py index 29564575..376fd9cb 100644 --- a/project/settings/development.py +++ b/project/settings/development.py @@ -8,7 +8,7 @@ ALLOWED_HOSTS = ['gm.id-east.ru', '95.213.204.126', '0.0.0.0'] SEND_SMS = False SMS_CODE_SHOW = True -USE_CELERY = False +USE_CELERY = True SCHEMA_URI = 'http' DEFAULT_SUBDOMAIN = 'www'