diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 9334e4b7..365f9767 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -149,7 +149,7 @@ class EstablishmentQuerySet(models.QuerySet): .filter(image_url__isnull=False, public_mark__gte=10) .has_published_reviews() .annotate_distance(point=establishment.location) - .order_by('distance')[:settings.LIMITING_QUERY_NUMBER] + .order_by('distance')[:settings.LIMITING_QUERY_OBJECTS] .values('id') ) return self.filter(id__in=subquery_filter_by_distance) \ @@ -168,7 +168,7 @@ class EstablishmentQuerySet(models.QuerySet): self.filter(image_url__isnull=False, public_mark__gte=10) .has_published_reviews() .annotate_distance(point=point) - .order_by('distance')[:settings.LIMITING_QUERY_NUMBER] + .order_by('distance')[:settings.LIMITING_QUERY_OBJECTS] .values('id') ) return self.filter(id__in=subquery_filter_by_distance) \ diff --git a/apps/news/models.py b/apps/news/models.py index 140c89c9..7a057d3f 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -1,9 +1,10 @@ """News app models.""" -from django.db import models from django.contrib.contenttypes import fields as generic +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 utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin @@ -64,7 +65,7 @@ class News(BaseAttributes, TranslatedFieldsMixin): end = models.DateTimeField(blank=True, null=True, default=None, verbose_name=_('End')) slug = models.SlugField(unique=True, max_length=50, null=True, - verbose_name=_('News slug'), editable=True,) + verbose_name=_('News slug'), editable=True,) playlist = models.IntegerField(_('playlist')) is_publish = models.BooleanField(default=False, verbose_name=_('Publish status')) @@ -74,10 +75,14 @@ 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_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) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index dbcd0f62..b4b7f8fa 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -1,12 +1,81 @@ """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.""" @@ -20,6 +89,8 @@ 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() @@ -39,13 +110,16 @@ class NewsBaseSerializer(serializers.ModelSerializer): 'title_translated', 'subtitle_translated', 'is_highlighted', - 'image_url', - 'preview_image_url', '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 acad6502..5c4c399a 100644 --- a/apps/utils/methods.py +++ b/apps/utils/methods.py @@ -3,10 +3,12 @@ import random import re import string +import requests from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.http.request import HttpRequest from django.utils.timezone import datetime +from rest_framework import status from rest_framework.request import Request @@ -86,3 +88,12 @@ def get_contenttype(app_label: str, model: str): qs = ContentType.objects.filter(app_label=app_label, model=model) if qs.exists(): return qs.first() + + +def image_url_valid(url: str): + # In case if image storage is not on CDN + if not url.startswith('http') and 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 diff --git a/apps/utils/pagination.py b/apps/utils/pagination.py index 2c9e92e5..ac83f4f2 100644 --- a/apps/utils/pagination.py +++ b/apps/utils/pagination.py @@ -52,4 +52,4 @@ class EstablishmentPortionPagination(ProjectMobilePagination): """ Pagination for app establishments with limit page size equal to 12 """ - page_size = settings.LIMITING_OUTPUT_OBJECTS + page_size = settings.QUERY_OUTPUT_OBJECTS diff --git a/project/settings/base.py b/project/settings/base.py index cfea18a5..f7ae44cd 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -316,7 +316,7 @@ CELERY_TASK_SERIALIZER = 'json' CELERY_RESULT_SERIALIZER = 'json' CELERY_TIMEZONE = TIME_ZONE -# Django FCM (Firebase push notificatoins) +# Django FCM (Firebase push notifications) FCM_DJANGO_SETTINGS = { 'FCM_SERVER_KEY': ( "AAAAJcC4Vbc:APA91bGovq7233-RHu2MbZTsuMU4jNf3obOue8s" @@ -327,30 +327,18 @@ FCM_DJANGO_SETTINGS = { # Thumbnail settings THUMBNAIL_ALIASES = { - 'news_preview': { - 'web': {'size': (300, 260), } - }, - 'news_promo_horizontal': { - 'web': {'size': (1900, 600), }, - 'mobile': {'size': (375, 260), }, - }, - 'news_tile_horizontal': { - 'web': {'size': (300, 275), }, - 'mobile': {'size': (343, 180), }, - }, - 'news_tile_vertical': { - 'web': {'size': (300, 380), }, - }, - 'news_highlight_vertical': { - 'web': {'size': (460, 630), }, - }, - 'news_editor': { - 'web': {'size': (940, 430), }, # при загрузке через контент эдитор - 'mobile': {'size': (343, 260), }, # через контент эдитор в мобильном браузерe - }, - 'avatar_comments': { - 'web': {'size': (116, 116), }, - }, + '': { + 'news_preview_web': {'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_editor_web': {'size': (940, 430), }, # при загрузке через контент эдитор + 'news_editor_mobile': {'size': (343, 260), }, # через контент эдитор в мобильном браузерe + 'avatar_comments_web': {'size': (116, 116), }, + } } # Password reset @@ -431,9 +419,9 @@ SITE_NAME = 'Gault & Millau' # Used in annotations for establishments. DEFAULT_ESTABLISHMENT_PUBLIC_MARK = 10 # Limit output objects (see in pagination classes). -LIMITING_OUTPUT_OBJECTS = 12 +QUERY_OUTPUT_OBJECTS = 12 # Need to restrict objects to sort (3 times more then expected). -LIMITING_QUERY_NUMBER = LIMITING_OUTPUT_OBJECTS * 3 +LIMITING_QUERY_OBJECTS = QUERY_OUTPUT_OBJECTS * 3 # GEO # A Spatial Reference System Identifier