diff --git a/apps/gallery/models.py b/apps/gallery/models.py index 022a24e0..7678c29a 100644 --- a/apps/gallery/models.py +++ b/apps/gallery/models.py @@ -1,15 +1,38 @@ +from django.db import models from django.utils.translation import gettext_lazy as _ from easy_thumbnails.fields import ThumbnailerImageField from utils.methods import image_path -from utils.models import ProjectBaseMixin, ImageMixin +from utils.models import ProjectBaseMixin, ImageMixin, PlatformMixin -class Image(ProjectBaseMixin, ImageMixin): +class ImageQuerySet(models.QuerySet): + """QuerySet for model Image.""" + + +class Image(ProjectBaseMixin, ImageMixin, PlatformMixin): """Image model.""" + HORIZONTAL = 0 + VERTICAL = 1 + + ORIENTATIONS = ( + (HORIZONTAL, _('Horizontal')), + (VERTICAL, _('Vertical')), + ) image = ThumbnailerImageField(upload_to=image_path, - verbose_name=_('Image file')) + verbose_name=_('image file')) + parent = models.ForeignKey('self', + blank=True, null=True, default=None, + related_name='parent_image', + on_delete=models.SET_DEFAULT, + verbose_name=_('parent image')) + orientation = models.PositiveSmallIntegerField(choices=ORIENTATIONS, + blank=True, null=True, default=None, + verbose_name=_('image orientation')) + title = models.CharField(_('title'), max_length=255, default='') + + objects = ImageQuerySet.as_manager() class Meta: """Meta class.""" diff --git a/apps/gallery/serializers.py b/apps/gallery/serializers.py index a7e3f0e1..f78e4b63 100644 --- a/apps/gallery/serializers.py +++ b/apps/gallery/serializers.py @@ -8,10 +8,13 @@ class ImageSerializer(serializers.ModelSerializer): # REQUEST file = serializers.ImageField(source='image', write_only=True) + title = serializers.CharField() # RESPONSE url = serializers.ImageField(source='image', read_only=True) + orientation_display = serializers.CharField(source='get_orientation_display', + read_only=True) class Meta: """Meta class""" @@ -19,6 +22,10 @@ class ImageSerializer(serializers.ModelSerializer): fields = ( 'id', 'file', - 'url' + 'url', + 'parent', + 'orientation', + 'orientation_display', + 'title', ) diff --git a/apps/news/models.py b/apps/news/models.py index 7a057d3f..a442b621 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -75,14 +75,6 @@ class News(BaseAttributes, TranslatedFieldsMixin): verbose_name=_('Is highlighted')) # TODO: metadata_keys - описание ключей для динамического построения полей метаданных # TODO: metadata_values - Описание значений для динамических полей из MetadataKeys - # image_url = models.URLField(blank=True, null=True, default=None, - # verbose_name=_('Image URL path')) - # preview_image_url = models.URLField(blank=True, null=True, default=None, - # verbose_name=_('Preview image URL path')) - image = models.ForeignKey('gallery.Image', - blank=True, null=True, - default=None, verbose_name=_('Image'), - on_delete=models.SET_NULL) address = models.ForeignKey('location.Address', blank=True, null=True, default=None, verbose_name=_('address'), on_delete=models.SET_NULL) @@ -105,3 +97,34 @@ class News(BaseAttributes, TranslatedFieldsMixin): @property def web_url(self): return reverse('web:news:rud', kwargs={'slug': self.slug}) + + +class NewsGalleryQuerySet(models.QuerySet): + """QuerySet for model News""" + + def originals(self): + """Return QuerySet with originals 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): + + news = models.ForeignKey(News, null=True, + related_name='news_gallery', + on_delete=models.SET_NULL, + verbose_name=_('news')) + gallery = models.ForeignKey('gallery.Image', null=True, + related_name='news_gallery', + on_delete=models.SET_NULL, + verbose_name=_('gallery')) + + objects = NewsGalleryQuerySet.as_manager() + + class Meta: + """NewsGallery meta class.""" + verbose_name = _('news gallery') + verbose_name_plural = _('news galleries') diff --git a/apps/news/serializers.py b/apps/news/serializers.py index b4b7f8fa..b7b11a06 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -1,81 +1,13 @@ """News app common serializers.""" from rest_framework import serializers -from gallery.models import Image from location import models as location_models from location.serializers import CountrySimpleSerializer from main.serializers import MetaDataContentSerializer from news import models -from utils.methods import image_url_valid from utils.serializers import TranslatedField -class NewsPromoImageSerializer(serializers.Serializer): - - horizontal_web_image = serializers.SerializerMethodField() - horizontal_mobile_image = serializers.SerializerMethodField() - - def get_horizontal_web_image(self, obj): - return obj['news_promo_horizontal_web'].url - - def get_horizontal_mobile_image(self, obj): - return obj['news_promo_horizontal_mobile'].url - - -class NewsTileImageSerializer(serializers.Serializer): - - horizontal_web_image = serializers.SerializerMethodField() - horizontal_mobile_image = serializers.SerializerMethodField() - vertical_web_image = serializers.SerializerMethodField() - - def get_horizontal_web_image(self, obj): - return obj['news_tile_horizontal_web'].url - - def get_horizontal_mobile_image(self, obj): - return obj['news_tile_horizontal_mobile'].url - - def get_vertical_web_image(self, obj): - return obj['news_tile_vertical_web'].url - - -class NewsHighlightImageSerializer(serializers.Serializer): - - vertical_web_image = serializers.SerializerMethodField() - - def get_vertical_web_image(self, obj): - return obj['news_highlight_vertical'].url - - -class NewsEditorImageSerializer(serializers.Serializer): - - web_image = serializers.SerializerMethodField() - mobile_image = serializers.SerializerMethodField() - - def get_web_image(self, obj): - return obj['news_editor_web'].url - - def get_mobile_image(self, obj): - return obj['news_editor_mobile'].url - - -class NewsGallerySerializer(serializers.ModelSerializer): - - promo_images = NewsPromoImageSerializer(source='image') - tile_images = NewsTileImageSerializer(source='image') - highlight_images = NewsHighlightImageSerializer(source='image') - editor_images = NewsEditorImageSerializer(source='image') - - class Meta: - """Meta class.""" - model = Image - fields = [ - 'promo_images', - 'tile_images', - 'highlight_images', - 'editor_images', - ] - - class NewsTypeSerializer(serializers.ModelSerializer): """News type serializer.""" @@ -89,8 +21,6 @@ class NewsTypeSerializer(serializers.ModelSerializer): class NewsBaseSerializer(serializers.ModelSerializer): """Base serializer for News model.""" - gallery = serializers.SerializerMethodField() - # read only fields title_translated = TranslatedField() subtitle_translated = TranslatedField() @@ -113,13 +43,8 @@ class NewsBaseSerializer(serializers.ModelSerializer): 'news_type', 'tags', 'slug', - 'gallery', ) - def get_gallery(self, obj): - if image_url_valid(url=obj.image.image.url): - return NewsGallerySerializer(obj.image).data - class NewsDetailSerializer(NewsBaseSerializer): """News detail serializer.""" diff --git a/apps/utils/methods.py b/apps/utils/methods.py index 5c4c399a..25027ae9 100644 --- a/apps/utils/methods.py +++ b/apps/utils/methods.py @@ -92,8 +92,20 @@ def get_contenttype(app_label: str, model: str): def image_url_valid(url: str): # In case if image storage is not on CDN - if not url.startswith('http') and url.startswith('/media/'): + if url.startswith('/media/'): url = f'{settings.SCHEMA_URI}://{settings.DOMAIN_URI}{url}' response = requests.request('head', url) if response.status_code == status.HTTP_200_OK: return True + + +def absolute_url_decorator(func): + def get_absolute_image_url(self, obj): + """Get absolute image url""" + url_path = func(self, obj) + if url_path: + if url_path.startswith('/media/'): + return f'{settings.SCHEMA_URI}://{settings.DOMAIN_URI}{url_path}/' + else: + return url_path + return get_absolute_image_url diff --git a/project/settings/base.py b/project/settings/base.py index f7ae44cd..0ae088ae 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -328,19 +328,23 @@ FCM_DJANGO_SETTINGS = { # Thumbnail settings THUMBNAIL_ALIASES = { '': { - 'news_preview_web': {'size': (300, 260), }, + 'news_preview': {'size': (300, 260), }, 'news_promo_horizontal_web': {'size': (1900, 600), }, 'news_promo_horizontal_mobile': {'size': (375, 260), }, 'news_tile_horizontal_web': {'size': (300, 275), }, 'news_tile_horizontal_mobile': {'size': (343, 180), }, 'news_tile_vertical_web': {'size': (300, 380), }, - 'news_highlight_vertical': {'size': (460, 630), }, + 'news_highlight_vertical_web': {'size': (460, 630), }, 'news_editor_web': {'size': (940, 430), }, # при загрузке через контент эдитор 'news_editor_mobile': {'size': (343, 260), }, # через контент эдитор в мобильном браузерe 'avatar_comments_web': {'size': (116, 116), }, } } +THUMBNAIL_DEFAULT_OPTIONS = { + 'crop': 'smart', +} + # Password reset RESETTING_TOKEN_EXPIRATION = 24 # hours