From d2075a1bf8e555f55272d2b3e5a381f1271a204b Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Fri, 20 Sep 2019 12:13:26 +0300 Subject: [PATCH 01/87] Initial app Product --- apps/{products => product}/__init__.py | 0 apps/product/apps.py | 7 +++++++ apps/{products => product}/migrations/__init__.py | 0 apps/{products => product}/models.py | 0 apps/{products => product}/serializers/__init__.py | 0 apps/{products => product}/serializers/common.py | 0 apps/{products => product}/serializers/mobile.py | 0 apps/{products => product}/serializers/web.py | 0 apps/{products => product}/urls/__init__.py | 0 apps/{products => product}/urls/common.py | 0 apps/{products => product}/urls/mobile.py | 0 apps/{products => product}/urls/web.py | 0 apps/{products => product}/views/__init__.py | 0 apps/{products => product}/views/common.py | 0 apps/{products => product}/views/mobile.py | 0 apps/{products => product}/views/web.py | 0 apps/products/admin.py | 3 --- apps/products/apps.py | 8 -------- apps/products/tests.py | 3 --- apps/products/views/views.py | 1 - project/settings/base.py | 1 + 21 files changed, 8 insertions(+), 15 deletions(-) rename apps/{products => product}/__init__.py (100%) create mode 100644 apps/product/apps.py rename apps/{products => product}/migrations/__init__.py (100%) rename apps/{products => product}/models.py (100%) rename apps/{products => product}/serializers/__init__.py (100%) rename apps/{products => product}/serializers/common.py (100%) rename apps/{products => product}/serializers/mobile.py (100%) rename apps/{products => product}/serializers/web.py (100%) rename apps/{products => product}/urls/__init__.py (100%) rename apps/{products => product}/urls/common.py (100%) rename apps/{products => product}/urls/mobile.py (100%) rename apps/{products => product}/urls/web.py (100%) rename apps/{products => product}/views/__init__.py (100%) rename apps/{products => product}/views/common.py (100%) rename apps/{products => product}/views/mobile.py (100%) rename apps/{products => product}/views/web.py (100%) delete mode 100644 apps/products/admin.py delete mode 100644 apps/products/apps.py delete mode 100644 apps/products/tests.py delete mode 100644 apps/products/views/views.py diff --git a/apps/products/__init__.py b/apps/product/__init__.py similarity index 100% rename from apps/products/__init__.py rename to apps/product/__init__.py diff --git a/apps/product/apps.py b/apps/product/apps.py new file mode 100644 index 00000000..7d1fc554 --- /dev/null +++ b/apps/product/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class ProductConfig(AppConfig): + name = 'product' + verbose_name = _('Product') diff --git a/apps/products/migrations/__init__.py b/apps/product/migrations/__init__.py similarity index 100% rename from apps/products/migrations/__init__.py rename to apps/product/migrations/__init__.py diff --git a/apps/products/models.py b/apps/product/models.py similarity index 100% rename from apps/products/models.py rename to apps/product/models.py diff --git a/apps/products/serializers/__init__.py b/apps/product/serializers/__init__.py similarity index 100% rename from apps/products/serializers/__init__.py rename to apps/product/serializers/__init__.py diff --git a/apps/products/serializers/common.py b/apps/product/serializers/common.py similarity index 100% rename from apps/products/serializers/common.py rename to apps/product/serializers/common.py diff --git a/apps/products/serializers/mobile.py b/apps/product/serializers/mobile.py similarity index 100% rename from apps/products/serializers/mobile.py rename to apps/product/serializers/mobile.py diff --git a/apps/products/serializers/web.py b/apps/product/serializers/web.py similarity index 100% rename from apps/products/serializers/web.py rename to apps/product/serializers/web.py diff --git a/apps/products/urls/__init__.py b/apps/product/urls/__init__.py similarity index 100% rename from apps/products/urls/__init__.py rename to apps/product/urls/__init__.py diff --git a/apps/products/urls/common.py b/apps/product/urls/common.py similarity index 100% rename from apps/products/urls/common.py rename to apps/product/urls/common.py diff --git a/apps/products/urls/mobile.py b/apps/product/urls/mobile.py similarity index 100% rename from apps/products/urls/mobile.py rename to apps/product/urls/mobile.py diff --git a/apps/products/urls/web.py b/apps/product/urls/web.py similarity index 100% rename from apps/products/urls/web.py rename to apps/product/urls/web.py diff --git a/apps/products/views/__init__.py b/apps/product/views/__init__.py similarity index 100% rename from apps/products/views/__init__.py rename to apps/product/views/__init__.py diff --git a/apps/products/views/common.py b/apps/product/views/common.py similarity index 100% rename from apps/products/views/common.py rename to apps/product/views/common.py diff --git a/apps/products/views/mobile.py b/apps/product/views/mobile.py similarity index 100% rename from apps/products/views/mobile.py rename to apps/product/views/mobile.py diff --git a/apps/products/views/web.py b/apps/product/views/web.py similarity index 100% rename from apps/products/views/web.py rename to apps/product/views/web.py diff --git a/apps/products/admin.py b/apps/products/admin.py deleted file mode 100644 index 8c38f3f3..00000000 --- a/apps/products/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/apps/products/apps.py b/apps/products/apps.py deleted file mode 100644 index 17d75292..00000000 --- a/apps/products/apps.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.apps import AppConfig -from django.utils.translation import gettext_lazy as _ - - -class ProductsConfig(AppConfig): - """Products model.""" - name = 'products' - verbose_name = _('products') diff --git a/apps/products/tests.py b/apps/products/tests.py deleted file mode 100644 index 7ce503c2..00000000 --- a/apps/products/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/apps/products/views/views.py b/apps/products/views/views.py deleted file mode 100644 index 60f00ef0..00000000 --- a/apps/products/views/views.py +++ /dev/null @@ -1 +0,0 @@ -# Create your views here. diff --git a/project/settings/base.py b/project/settings/base.py index 4aec6f4c..77701c8f 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -63,6 +63,7 @@ PROJECT_APPS = [ 'news.apps.NewsConfig', 'notification.apps.NotificationConfig', 'partner.apps.PartnerConfig', + 'product.apps.ProductConfig', 'recipe.apps.RecipeConfig', 'search_indexes.apps.SearchIndexesConfig', 'translation.apps.TranslationConfig', From 14ae75f088a05f033b5179940b6639dfc5f2b727 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Thu, 26 Sep 2019 18:38:57 +0300 Subject: [PATCH 02/87] gm-148: refactored news serializer --- apps/establishment/models.py | 4 +- apps/news/models.py | 17 +++++--- apps/news/serializers.py | 78 +++++++++++++++++++++++++++++++++++- apps/utils/methods.py | 11 +++++ apps/utils/pagination.py | 2 +- project/settings/base.py | 42 +++++++------------ 6 files changed, 116 insertions(+), 38 deletions(-) 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 From 7f79ce89455a321c88284757ddd88a6944a4d7ad Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 27 Sep 2019 17:48:27 +0300 Subject: [PATCH 03/87] gm-148: in progress --- apps/gallery/models.py | 29 ++++++++++++-- apps/gallery/serializers.py | 9 ++++- apps/news/models.py | 39 +++++++++++++++---- apps/news/serializers.py | 75 ------------------------------------- apps/utils/methods.py | 14 ++++++- project/settings/base.py | 8 +++- 6 files changed, 84 insertions(+), 90 deletions(-) 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 From 501b8833edcc612003be3269d1715c0ea8527b40 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Mon, 30 Sep 2019 15:40:28 +0300 Subject: [PATCH 04/87] gm-148: finish --- .../migrations/0002_auto_20190930_0714.py | 41 +++++++++++++++++ apps/gallery/serializers.py | 2 + apps/gallery/urls.py | 2 +- apps/gallery/views.py | 13 +++++- apps/news/admin.py | 6 +++ .../migrations/0014_auto_20190926_1156.py | 21 +++++++++ apps/news/migrations/0015_newsgallery.py | 27 +++++++++++ apps/news/migrations/0016_news_gallery.py | 19 ++++++++ apps/news/models.py | 12 ++--- apps/news/serializers.py | 45 +++++++++++++++++++ apps/news/urls/back.py | 7 ++- apps/news/views.py | 43 ++++++++++++++++++ 12 files changed, 229 insertions(+), 9 deletions(-) create mode 100644 apps/gallery/migrations/0002_auto_20190930_0714.py create mode 100644 apps/news/migrations/0014_auto_20190926_1156.py create mode 100644 apps/news/migrations/0015_newsgallery.py create mode 100644 apps/news/migrations/0016_news_gallery.py diff --git a/apps/gallery/migrations/0002_auto_20190930_0714.py b/apps/gallery/migrations/0002_auto_20190930_0714.py new file mode 100644 index 00000000..8423910f --- /dev/null +++ b/apps/gallery/migrations/0002_auto_20190930_0714.py @@ -0,0 +1,41 @@ +# Generated by Django 2.2.4 on 2019-09-30 07:14 + +from django.db import migrations, models +import django.db.models.deletion +import easy_thumbnails.fields +import utils.methods + + +class Migration(migrations.Migration): + + dependencies = [ + ('gallery', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='image', + name='orientation', + field=models.PositiveSmallIntegerField(blank=True, choices=[(0, 'Horizontal'), (1, 'Vertical')], default=None, null=True, verbose_name='image orientation'), + ), + migrations.AddField( + model_name='image', + name='parent', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='parent_image', to='gallery.Image', verbose_name='parent image'), + ), + migrations.AddField( + model_name='image', + name='source', + field=models.PositiveSmallIntegerField(choices=[(0, 'Mobile'), (1, 'Web'), (2, 'All')], default=0, verbose_name='Source'), + ), + migrations.AddField( + model_name='image', + name='title', + field=models.CharField(default='', max_length=255, verbose_name='title'), + ), + migrations.AlterField( + model_name='image', + name='image', + field=easy_thumbnails.fields.ThumbnailerImageField(upload_to=utils.methods.image_path, verbose_name='image file'), + ), + ] diff --git a/apps/gallery/serializers.py b/apps/gallery/serializers.py index f78e4b63..c428b8f2 100644 --- a/apps/gallery/serializers.py +++ b/apps/gallery/serializers.py @@ -9,6 +9,8 @@ class ImageSerializer(serializers.ModelSerializer): file = serializers.ImageField(source='image', write_only=True) title = serializers.CharField() + orientation = serializers.ChoiceField(choices=models.Image.ORIENTATIONS, + write_only=True) # RESPONSE url = serializers.ImageField(source='image', diff --git a/apps/gallery/urls.py b/apps/gallery/urls.py index 53d1c097..2faa95e4 100644 --- a/apps/gallery/urls.py +++ b/apps/gallery/urls.py @@ -6,5 +6,5 @@ from . import views app_name = 'gallery' urlpatterns = [ - path('upload/', views.ImageUploadView.as_view(), name='upload-image') + path('upload/', views.ImageBaseView.as_view(), name='upload-image'), ] diff --git a/apps/gallery/views.py b/apps/gallery/views.py index 8a9195c3..b35df131 100644 --- a/apps/gallery/views.py +++ b/apps/gallery/views.py @@ -3,8 +3,17 @@ from rest_framework import generics from . import models, serializers -class ImageUploadView(generics.CreateAPIView): - """Upload image to gallery""" +class ImageBaseView(generics.CreateAPIView): + """Upload image to gallery.""" model = models.Image queryset = models.Image.objects.all() serializer_class = serializers.ImageSerializer + + +class NewsImageListView(ImageBaseView, generics.ListAPIView): + """Return list of uploaded images for news object.""" + + def get_queryset(self): + """Override get_queryset method.""" + qs = super(NewsImageListView, self).get_queryset() + return qs.filter(news_gallery__news=self.kwargs.get('news_id')) diff --git a/apps/news/admin.py b/apps/news/admin.py index 7cbfb049..0bb4c8cc 100644 --- a/apps/news/admin.py +++ b/apps/news/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin + from news import models @@ -12,3 +13,8 @@ class NewsTypeAdmin(admin.ModelAdmin): @admin.register(models.News) class NewsAdmin(admin.ModelAdmin): """News admin.""" + + +@admin.register(models.NewsGallery) +class NewsGalleryAdmin(admin.ModelAdmin): + """News gallery admin.""" diff --git a/apps/news/migrations/0014_auto_20190926_1156.py b/apps/news/migrations/0014_auto_20190926_1156.py new file mode 100644 index 00000000..d16f0ad3 --- /dev/null +++ b/apps/news/migrations/0014_auto_20190926_1156.py @@ -0,0 +1,21 @@ +# Generated by Django 2.2.4 on 2019-09-26 11:56 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0013_auto_20190924_0806'), + ] + + operations = [ + migrations.RemoveField( + model_name='news', + name='image_url', + ), + migrations.RemoveField( + model_name='news', + name='preview_image_url', + ), + ] diff --git a/apps/news/migrations/0015_newsgallery.py b/apps/news/migrations/0015_newsgallery.py new file mode 100644 index 00000000..a8422dab --- /dev/null +++ b/apps/news/migrations/0015_newsgallery.py @@ -0,0 +1,27 @@ +# Generated by Django 2.2.4 on 2019-09-30 08:57 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('gallery', '0002_auto_20190930_0714'), + ('news', '0014_auto_20190926_1156'), + ] + + operations = [ + migrations.CreateModel( + name='NewsGallery', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('image', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='news_gallery', to='gallery.Image', verbose_name='gallery')), + ('news', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='news_gallery', to='news.News', verbose_name='news')), + ], + options={ + 'verbose_name': 'news gallery', + 'verbose_name_plural': 'news galleries', + }, + ), + ] diff --git a/apps/news/migrations/0016_news_gallery.py b/apps/news/migrations/0016_news_gallery.py new file mode 100644 index 00000000..7917cf26 --- /dev/null +++ b/apps/news/migrations/0016_news_gallery.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.4 on 2019-09-30 12:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gallery', '0002_auto_20190930_0714'), + ('news', '0015_newsgallery'), + ] + + operations = [ + migrations.AddField( + model_name='news', + name='gallery', + field=models.ManyToManyField(through='news.NewsGallery', to='gallery.Image'), + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index a442b621..4b9176cd 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -83,6 +83,8 @@ class News(BaseAttributes, TranslatedFieldsMixin): verbose_name=_('country')) tags = generic.GenericRelation(to='main.MetaDataContent') + gallery = models.ManyToManyField('gallery.Image', through='news.NewsGallery') + objects = NewsQuerySet.as_manager() class Meta: @@ -103,7 +105,7 @@ class NewsGalleryQuerySet(models.QuerySet): """QuerySet for model News""" def originals(self): - """Return QuerySet with originals images.""" + """Return QuerySet with original images.""" return self.filter(gallery__parent__isnull=True) def crops(self): @@ -117,10 +119,10 @@ class NewsGallery(models.Model): 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')) + image = models.ForeignKey('gallery.Image', null=True, + related_name='news_gallery', + on_delete=models.SET_NULL, + verbose_name=_('gallery')) objects = NewsGalleryQuerySet.as_manager() diff --git a/apps/news/serializers.py b/apps/news/serializers.py index b7b11a06..8901b834 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -1,6 +1,9 @@ """News app common serializers.""" +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers +from gallery.models import Image +from gallery.serializers import ImageSerializer from location import models as location_models from location.serializers import CountrySimpleSerializer from main.serializers import MetaDataContentSerializer @@ -28,6 +31,7 @@ class NewsBaseSerializer(serializers.ModelSerializer): # related fields news_type = NewsTypeSerializer(read_only=True) tags = MetaDataContentSerializer(read_only=True, many=True) + gallery = ImageSerializer(read_only=True, many=True) slug = serializers.SlugField(allow_blank=False, required=True, max_length=50) @@ -43,6 +47,7 @@ class NewsBaseSerializer(serializers.ModelSerializer): 'news_type', 'tags', 'slug', + 'gallery', ) @@ -100,3 +105,43 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer, 'country_id', ) + +class NewsBackOfficeGallerySerializer(serializers.ModelSerializer): + """Serializer class for model NewsGallery.""" + image = ImageSerializer(read_only=True) + + class Meta: + """Meta class""" + model = models.NewsGallery + fields = [ + 'id', + 'image', + ] + + def get_request_kwargs(self): + """Get url kwargs from request.""" + return self.context.get('request').parser_context.get('kwargs') + + def validate(self, attrs): + """Override validate method.""" + news_pk = self.get_request_kwargs().get('pk') + image_id = self.get_request_kwargs().get('image_id') + + news_qs = models.News.objects.filter(pk=news_pk) + image_qs = Image.objects.filter(id=image_id) + + if not news_qs.exists(): + raise serializers.ValidationError({'detail': _('News not found')}) + if not image_qs.exists(): + raise serializers.ValidationError({'detail': _('Image not found')}) + + news = news_qs.first() + image = image_qs.first() + + if news.news_gallery.filter(image=image).exists(): + raise serializers.ValidationError({'detail': _('Image is already added')}) + + attrs['news'] = news + attrs['image'] = image + + return attrs diff --git a/apps/news/urls/back.py b/apps/news/urls/back.py index 8522592e..4ab11727 100644 --- a/apps/news/urls/back.py +++ b/apps/news/urls/back.py @@ -1,5 +1,6 @@ """News app urlpatterns for backoffice""" from django.urls import path + from news import views app_name = 'news' @@ -7,5 +8,9 @@ app_name = 'news' urlpatterns = [ path('', views.NewsBackOfficeLCView.as_view(), name='list-create'), path('/', views.NewsBackOfficeRUDView.as_view(), - name='retrieve-update-destroy'), + name='gallery-retrieve-update-destroy'), + path('/gallery/', views.NewsBackOfficeGalleryListView.as_view(), + name='gallery-list'), + path('/gallery//', views.NewsBackOfficeGalleryCreateDestroyView.as_view(), + name='gallery-create-destroy'), ] \ No newline at end of file diff --git a/apps/news/views.py b/apps/news/views.py index 74abe33f..6231fd3b 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -1,5 +1,8 @@ """News app views.""" +from django.shortcuts import get_object_or_404 from rest_framework import generics, permissions + +from gallery.serializers import ImageSerializer from news import filters, models, serializers @@ -59,6 +62,46 @@ class NewsBackOfficeLCView(NewsBackOfficeMixinView, return super().get_serializer_class() +class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView, + generics.CreateAPIView, + generics.DestroyAPIView): + """Resource for a create gallery for news for back-office users.""" + serializer_class = serializers.NewsBackOfficeGallerySerializer + + def get_object(self): + """ + Returns the object the view is displaying. + """ + news_qs = self.filter_queryset(self.get_queryset()) + + news = get_object_or_404(news_qs, pk=self.kwargs['pk']) + gallery = get_object_or_404(news.news_gallery, image_id=self.kwargs['image_id']) + + # May raise a permission denied + self.check_object_permissions(self.request, gallery) + + return gallery + + +class NewsBackOfficeGalleryListView(NewsBackOfficeMixinView, generics.ListAPIView): + """Resource for returning gallery for news for back-office users.""" + serializer_class = ImageSerializer + + def get_object(self): + """Override get_object method.""" + qs = super(NewsBackOfficeGalleryListView, self).get_queryset() + news = get_object_or_404(qs, pk=self.kwargs['pk']) + + # May raise a permission denied + self.check_object_permissions(self.request, news) + + return news + + def get_queryset(self): + """Override get_queryset method.""" + return self.get_object().gallery.all() + + class NewsBackOfficeRUDView(NewsBackOfficeMixinView, generics.RetrieveUpdateDestroyAPIView): """Resource for detailed information about news for back-office users.""" From d9635a85999b48d444b9178600fa5e315399aedd Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 2 Oct 2019 10:16:32 +0300 Subject: [PATCH 05/87] gm-148: refactored --- .../migrations/0009_auto_20191002_0648.py | 18 ++++++ apps/account/models.py | 2 +- apps/authorization/serializers/common.py | 12 ++-- .../migrations/0003_auto_20191001_0647.py | 20 +++++++ apps/gallery/models.py | 10 ++-- apps/gallery/serializers.py | 6 +- .../migrations/0020_merge_20190930_1251.py | 14 +++++ apps/news/serializers.py | 27 +++++++-- apps/news/views.py | 11 +++- apps/utils/methods.py | 24 +++++--- apps/utils/models.py | 41 +++++++++++++- project/settings/amazon_s3.py | 19 +++++++ project/settings/base.py | 56 +++++++++++++------ project/settings/development.py | 3 +- project/settings/local.py | 8 ++- project/settings/production.py | 1 + project/settings/stage.py | 1 + project/storage_backends.py | 12 ++++ project/urls/__init__.py | 2 +- requirements/base.txt | 8 ++- 20 files changed, 241 insertions(+), 54 deletions(-) create mode 100644 apps/account/migrations/0009_auto_20191002_0648.py create mode 100644 apps/gallery/migrations/0003_auto_20191001_0647.py create mode 100644 apps/news/migrations/0020_merge_20190930_1251.py create mode 100644 project/settings/amazon_s3.py create mode 100644 project/storage_backends.py diff --git a/apps/account/migrations/0009_auto_20191002_0648.py b/apps/account/migrations/0009_auto_20191002_0648.py new file mode 100644 index 00000000..d9734907 --- /dev/null +++ b/apps/account/migrations/0009_auto_20191002_0648.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-10-02 06:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0008_auto_20190912_1325'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='email', + field=models.EmailField(default=None, max_length=254, null=True, unique=True, verbose_name='email address'), + ), + ] diff --git a/apps/account/models.py b/apps/account/models.py index 81ade4fc..4ba03521 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -58,7 +58,7 @@ class User(AbstractUser): blank=True, null=True, default=None) cropped_image_url = models.URLField(verbose_name=_('Cropped image URL path'), blank=True, null=True, default=None) - email = models.EmailField(_('email address'), blank=True, + email = models.EmailField(_('email address'), unique=True, null=True, default=None) email_confirmed = models.BooleanField(_('email status'), default=False) newsletter = models.NullBooleanField(default=True) diff --git a/apps/authorization/serializers/common.py b/apps/authorization/serializers/common.py index 5d8bb3a8..6ee108dd 100644 --- a/apps/authorization/serializers/common.py +++ b/apps/authorization/serializers/common.py @@ -18,12 +18,6 @@ from utils.tokens import GMRefreshToken # Serializers class SignupSerializer(serializers.ModelSerializer): """Signup serializer serializer mixin""" - # REQUEST - username = serializers.CharField(write_only=True) - password = serializers.CharField(write_only=True) - email = serializers.EmailField(write_only=True) - newsletter = serializers.BooleanField(write_only=True) - class Meta: model = account_models.User fields = ( @@ -32,6 +26,12 @@ class SignupSerializer(serializers.ModelSerializer): 'email', 'newsletter' ) + extra_kwargs = { + 'username': {'write_only': True}, + 'password': {'write_only': True}, + 'email': {'write_only': True}, + 'newsletter': {'write_only': True} + } def validate_username(self, value): """Custom username validation""" diff --git a/apps/gallery/migrations/0003_auto_20191001_0647.py b/apps/gallery/migrations/0003_auto_20191001_0647.py new file mode 100644 index 00000000..4defed2b --- /dev/null +++ b/apps/gallery/migrations/0003_auto_20191001_0647.py @@ -0,0 +1,20 @@ +# Generated by Django 2.2.4 on 2019-10-01 06:47 + +from django.db import migrations +import sorl.thumbnail.fields +import utils.methods + + +class Migration(migrations.Migration): + + dependencies = [ + ('gallery', '0002_auto_20190930_0714'), + ] + + operations = [ + migrations.AlterField( + model_name='image', + name='image', + field=sorl.thumbnail.fields.ImageField(upload_to=utils.methods.image_path, verbose_name='image file'), + ), + ] diff --git a/apps/gallery/models.py b/apps/gallery/models.py index 7678c29a..687b42fc 100644 --- a/apps/gallery/models.py +++ b/apps/gallery/models.py @@ -1,16 +1,16 @@ from django.db import models from django.utils.translation import gettext_lazy as _ -from easy_thumbnails.fields import ThumbnailerImageField +from sorl.thumbnail.fields import ImageField as SORLImageField from utils.methods import image_path -from utils.models import ProjectBaseMixin, ImageMixin, PlatformMixin +from utils.models import ProjectBaseMixin, SORLImageMixin, PlatformMixin class ImageQuerySet(models.QuerySet): """QuerySet for model Image.""" -class Image(ProjectBaseMixin, ImageMixin, PlatformMixin): +class Image(ProjectBaseMixin, SORLImageMixin, PlatformMixin): """Image model.""" HORIZONTAL = 0 VERTICAL = 1 @@ -20,8 +20,8 @@ class Image(ProjectBaseMixin, ImageMixin, PlatformMixin): (VERTICAL, _('Vertical')), ) - image = ThumbnailerImageField(upload_to=image_path, - verbose_name=_('image file')) + image = SORLImageField(upload_to=image_path, + verbose_name=_('image file')) parent = models.ForeignKey('self', blank=True, null=True, default=None, related_name='parent_image', diff --git a/apps/gallery/serializers.py b/apps/gallery/serializers.py index c428b8f2..f575001a 100644 --- a/apps/gallery/serializers.py +++ b/apps/gallery/serializers.py @@ -8,7 +8,6 @@ class ImageSerializer(serializers.ModelSerializer): # REQUEST file = serializers.ImageField(source='image', write_only=True) - title = serializers.CharField() orientation = serializers.ChoiceField(choices=models.Image.ORIENTATIONS, write_only=True) @@ -21,7 +20,7 @@ class ImageSerializer(serializers.ModelSerializer): class Meta: """Meta class""" model = models.Image - fields = ( + fields = [ 'id', 'file', 'url', @@ -29,5 +28,4 @@ class ImageSerializer(serializers.ModelSerializer): 'orientation', 'orientation_display', 'title', - ) - + ] diff --git a/apps/news/migrations/0020_merge_20190930_1251.py b/apps/news/migrations/0020_merge_20190930_1251.py new file mode 100644 index 00000000..deb45e63 --- /dev/null +++ b/apps/news/migrations/0020_merge_20190930_1251.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.4 on 2019-09-30 12:51 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0019_news_author'), + ('news', '0016_news_gallery'), + ] + + operations = [ + ] diff --git a/apps/news/serializers.py b/apps/news/serializers.py index daee42b2..fad2f355 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -2,6 +2,7 @@ from django.utils.translation import gettext_lazy as _ from rest_framework import serializers +from account.serializers.common import UserSerializer from gallery.models import Image from gallery.serializers import ImageSerializer from location import models as location_models @@ -9,7 +10,26 @@ from location.serializers import CountrySimpleSerializer from main.serializers import MetaDataContentSerializer from news import models from utils.serializers import TranslatedField -from account.serializers.common import UserSerializer + + +class NewsImageSerializer(ImageSerializer): + """News images""" + promo_web_url = serializers.SerializerMethodField() + promo_mobile_url = serializers.SerializerMethodField() + + class Meta: + model = Image + fields = ImageSerializer.Meta.fields + [ + 'promo_web_url', + 'promo_mobile_url' + ] + + def get_promo_web_url(self, obj): + return obj.get_image_url(thumbnail_key='news_promo_horizontal_web') + + def get_promo_mobile_url(self, obj): + return obj.get_image_url(thumbnail_key='news_promo_horizontal_mobile') + class NewsTypeSerializer(serializers.ModelSerializer): """News type serializer.""" @@ -31,7 +51,7 @@ class NewsBaseSerializer(serializers.ModelSerializer): # related fields news_type = NewsTypeSerializer(read_only=True) tags = MetaDataContentSerializer(read_only=True, many=True) - gallery = ImageSerializer(read_only=True, many=True) + gallery = NewsImageSerializer(read_only=True, many=True) class Meta: """Meta class.""" @@ -115,14 +135,11 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer, class NewsBackOfficeGallerySerializer(serializers.ModelSerializer): """Serializer class for model NewsGallery.""" - image = ImageSerializer(read_only=True) - class Meta: """Meta class""" model = models.NewsGallery fields = [ 'id', - 'image', ] def get_request_kwargs(self): diff --git a/apps/news/views.py b/apps/news/views.py index 6231fd3b..3eb7f9ab 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -1,8 +1,8 @@ """News app views.""" from django.shortcuts import get_object_or_404 -from rest_framework import generics, permissions +from rest_framework import generics, permissions, status +from rest_framework.response import Response -from gallery.serializers import ImageSerializer from news import filters, models, serializers @@ -82,10 +82,15 @@ class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView, return gallery + def create(self, request, *args, **kwargs): + """Override create method""" + super().create(request, *args, **kwargs) + return Response(status=status.HTTP_201_CREATED) + class NewsBackOfficeGalleryListView(NewsBackOfficeMixinView, generics.ListAPIView): """Resource for returning gallery for news for back-office users.""" - serializer_class = ImageSerializer + serializer_class = serializers.NewsImageSerializer def get_object(self): """Override get_object method.""" diff --git a/apps/utils/methods.py b/apps/utils/methods.py index 25027ae9..bea8fec7 100644 --- a/apps/utils/methods.py +++ b/apps/utils/methods.py @@ -1,4 +1,5 @@ """Utils app method.""" +import logging import random import re import string @@ -10,6 +11,9 @@ from django.http.request import HttpRequest from django.utils.timezone import datetime from rest_framework import status from rest_framework.request import Request +from os.path import exists + +logger = logging.getLogger(__name__) def generate_code(digits=6, string_output=True): @@ -91,12 +95,18 @@ def get_contenttype(app_label: str, model: str): def image_url_valid(url: str): - # In case if image storage is not on CDN - 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 + """ + Check if requested URL is valid. + :param url: string + :return: boolean + """ + try: + assert url.startswith('http') + response = requests.request('head', url) + except Exception as e: + logger.info(f'ConnectionError: {e}') + else: + return response.status_code == status.HTTP_200_OK def absolute_url_decorator(func): @@ -105,7 +115,7 @@ def absolute_url_decorator(func): url_path = func(self, obj) if url_path: if url_path.startswith('/media/'): - return f'{settings.SCHEMA_URI}://{settings.DOMAIN_URI}{url_path}/' + return f'{settings.MEDIA_URL}{url_path}/' else: return url_path return get_absolute_image_url diff --git a/apps/utils/models.py b/apps/utils/models.py index 4e6df35e..299013b7 100644 --- a/apps/utils/models.py +++ b/apps/utils/models.py @@ -1,4 +1,5 @@ """Utils app models.""" +import logging from os.path import exists from django.contrib.auth.tokens import PasswordResetTokenGenerator from django.contrib.gis.db import models @@ -10,8 +11,12 @@ from django.utils.translation import ugettext_lazy as _, get_language from easy_thumbnails.fields import ThumbnailerImageField from utils.methods import image_path, svg_image_path from utils.validators import svg_image_validator -from django.db.models.fields import Field -from django.core import exceptions +from sorl.thumbnail.fields import ImageField as SORLImageField +from sorl.thumbnail import get_thumbnail +from django.conf import settings +from utils.methods import image_url_valid + +logger = logging.getLogger(__name__) class ProjectBaseMixin(models.Model): @@ -177,6 +182,38 @@ class ImageMixin(models.Model): image_tag.allow_tags = True +class SORLImageMixin(models.Model): + """Abstract model for SORL ImageField""" + + image = SORLImageField(upload_to=image_path, + blank=True, null=True, default=None, + verbose_name=_('Image')) + + class Meta: + """Meta class.""" + abstract = True + + def get_image(self, thumbnail_key=None): + """Get thumbnail image file.""" + if thumbnail_key in settings.SORL_THUMBNAIL_ALIASES: + return get_thumbnail(file_=self.image, + **settings.SORL_THUMBNAIL_ALIASES[thumbnail_key]) + + def get_image_url(self, thumbnail_key=None): + """Get image thumbnail url.""" + return self.get_image(thumbnail_key).url + + def image_tag(self): + """Admin preview tag.""" + if self.image: + return mark_safe('' % self.image.url) + else: + return None + + image_tag.short_description = _('Image') + image_tag.allow_tags = True + + class SVGImageMixin(models.Model): """SVG image model.""" diff --git a/project/settings/amazon_s3.py b/project/settings/amazon_s3.py new file mode 100644 index 00000000..73b15e28 --- /dev/null +++ b/project/settings/amazon_s3.py @@ -0,0 +1,19 @@ +"""Settings for Amazon S3""" +import os +from .base import MEDIA_LOCATION + +# AMAZON S3 +AWS_ACCESS_KEY_ID = os.getenv('AWS_ACCESS_KEY_ID') +AWS_SECRET_ACCESS_KEY = os.getenv('AWS_SECRET_ACCESS_KEY') +AWS_STORAGE_BUCKET_NAME = os.getenv('AWS_STORAGE_BUCKET_NAME') +AWS_S3_CUSTOM_DOMAIN = f'{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com' +AWS_S3_OBJECT_PARAMETERS = {'CacheControl': 'max-age=86400'} + +# Static settings +# PUBLIC_STATIC_LOCATION = 'static' +# STATIC_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/{PUBLIC_STATIC_LOCATION}/' +# STATICFILES_STORAGE = 'project.storage_backends.PublicStaticStorage' + +# Public media settings +MEDIA_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/{MEDIA_LOCATION}/' +DEFAULT_FILE_STORAGE = 'project.storage_backends.PublicMediaStorage' diff --git a/project/settings/base.py b/project/settings/base.py index 0ae088ae..2916cff0 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -91,6 +91,8 @@ EXTERNAL_APPS = [ 'rest_framework_simplejwt.token_blacklist', 'solo', 'phonenumber_field', + 'storages', + 'sorl.thumbnail', ] @@ -190,19 +192,6 @@ LOCALE_PATHS = ( os.path.abspath(os.path.join(BASE_DIR, 'locale')), ) -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/2.2/howto/static-files/ - -STATIC_ROOT = os.path.join(PUBLIC_ROOT, 'static') -STATIC_URL = '/static/' - -MEDIA_ROOT = os.path.join(PUBLIC_ROOT, 'media') -MEDIA_URL = '/media/' - -STATICFILES_DIRS = ( - os.path.join(PROJECT_ROOT, 'static'), -) - AVAILABLE_VERSIONS = { # 'future': '1.0.1', 'current': '1.0.0', @@ -262,6 +251,7 @@ SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = { 'fields': 'id, name, email', } + # SMS Settings SMS_EXPIRATION = 5 SMS_SEND_DELAY = 30 @@ -272,12 +262,14 @@ SMS_CODE_LENGTH = 6 SEND_SMS = True SMS_CODE_SHOW = False + # SMSC Settings SMS_SERVICE = 'http://smsc.ru/sys/send.php' SMS_LOGIN = 'GM2019' SMS_PASSWORD = '}#6%Qe7CYG7n' SMS_SENDER = 'GM' + # EMAIL EMAIL_USE_TLS = True EMAIL_HOST = 'smtp.gmail.com' @@ -285,6 +277,7 @@ EMAIL_HOST_USER = 'anatolyfeteleu@gmail.com' EMAIL_HOST_PASSWORD = 'nggrlnbehzksgmbt' EMAIL_PORT = 587 + # Django Rest Swagger SWAGGER_SETTINGS = { # "DEFAULT_GENERATOR_CLASS": "rest_framework.schemas.generators.BaseSchemaGenerator", @@ -307,6 +300,7 @@ REDOC_SETTINGS = { 'LAZY_RENDERING': False, } + # CELERY BROKER_URL = 'amqp://rabbitmq:5672' CELERY_RESULT_BACKEND = BROKER_URL @@ -316,6 +310,7 @@ CELERY_TASK_SERIALIZER = 'json' CELERY_RESULT_SERIALIZER = 'json' CELERY_TIMEZONE = TIME_ZONE + # Django FCM (Firebase push notifications) FCM_DJANGO_SETTINGS = { 'FCM_SERVER_KEY': ( @@ -325,6 +320,7 @@ FCM_DJANGO_SETTINGS = { ), } + # Thumbnail settings THUMBNAIL_ALIASES = { '': { @@ -345,11 +341,23 @@ THUMBNAIL_DEFAULT_OPTIONS = { 'crop': 'smart', } -# Password reset -RESETTING_TOKEN_EXPIRATION = 24 # hours +# SORL +THUMBNAIL_QUALITY = 85 +THUMBNAIL_DEBUG = False +SORL_THUMBNAIL_ALIASES = { + 'news_preview': {'geometry_string': '100x100', 'crop': 'center'}, + 'news_promo_horizontal_web': {'geometry_string': '1900x600', 'crop': 'center'}, + 'news_promo_horizontal_mobile': {'geometry_string': '375x260', 'crop': 'center'}, + 'news_tile_horizontal_web': {'geometry_string': '300x275', 'crop': 'center'}, + 'news_tile_horizontal_mobile': {'geometry_string': '343x180', 'crop': 'center'}, + 'news_tile_vertical_web': {'geometry_string': '300x380', 'crop': 'center'}, + 'news_highlight_vertical_web': {'geometry_string': '460x630', 'crop': 'center'}, + 'news_editor_web': {'geometry_string': '940x430', 'crop': 'center'}, + 'news_editor_mobile': {'geometry_string': '343x260', 'crop': 'center'}, # при загрузке через контент эдитор + 'avatar_comments_web': {'geometry_string': '116x116', 'crop': 'center'}, # через контент эдитор в мобильном браузерe +} -GEOIP_PATH = os.path.join(PROJECT_ROOT, 'geoip_db') # JWT SIMPLE_JWT = { @@ -409,17 +417,20 @@ DATA_UPLOAD_MAX_MEMORY_SIZE = 5242880 # 5MB FILE_UPLOAD_MAX_MEMORY_SIZE = 5242880 # 5MB FILE_UPLOAD_PERMISSIONS = 0o644 + # SOLO SETTINGS # todo: make a separate service (redis?) and update solo_cache SOLO_CACHE = 'default' SOLO_CACHE_PREFIX = 'solo' SOLO_CACHE_TIMEOUT = 300 + # REDIRECT URL SITE_REDIRECT_URL_UNSUBSCRIBE = '/unsubscribe/' SITE_NAME = 'Gault & Millau' + # Used in annotations for establishments. DEFAULT_ESTABLISHMENT_PUBLIC_MARK = 10 # Limit output objects (see in pagination classes). @@ -427,6 +438,19 @@ QUERY_OUTPUT_OBJECTS = 12 # Need to restrict objects to sort (3 times more then expected). LIMITING_QUERY_OBJECTS = QUERY_OUTPUT_OBJECTS * 3 + # GEO # A Spatial Reference System Identifier GEO_DEFAULT_SRID = 4326 +GEOIP_PATH = os.path.join(PROJECT_ROOT, 'geoip_db') + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/2.2/howto/static-files/ +STATIC_ROOT = os.path.join(PUBLIC_ROOT, 'static') +STATIC_URL = '/static/' + +STATICFILES_DIRS = ( + os.path.join(PROJECT_ROOT, 'static'), +) + +MEDIA_LOCATION = 'media' diff --git a/project/settings/development.py b/project/settings/development.py index 43a60935..29564575 100644 --- a/project/settings/development.py +++ b/project/settings/development.py @@ -1,9 +1,10 @@ """Development settings.""" from .base import * +from .amazon_s3 import * import sentry_sdk from sentry_sdk.integrations.django import DjangoIntegration -ALLOWED_HOSTS = ['gm.id-east.ru', '95.213.204.126'] +ALLOWED_HOSTS = ['gm.id-east.ru', '95.213.204.126', '0.0.0.0'] SEND_SMS = False SMS_CODE_SHOW = True diff --git a/project/settings/local.py b/project/settings/local.py index 503ad191..a7cb7c0f 100644 --- a/project/settings/local.py +++ b/project/settings/local.py @@ -17,6 +17,8 @@ BROKER_URL = 'amqp://rabbitmq:5672' CELERY_RESULT_BACKEND = BROKER_URL CELERY_BROKER_URL = BROKER_URL +MEDIA_ROOT = os.path.join(PUBLIC_ROOT, MEDIA_LOCATION) +MEDIA_URL = f'{SCHEMA_URI}://{DOMAIN_URI}/{MEDIA_LOCATION}/' # LOGGING LOGGING = { @@ -62,8 +64,10 @@ ELASTICSEARCH_DSL = { } } - ELASTICSEARCH_INDEX_NAMES = { # 'search_indexes.documents.news': 'local_news', 'search_indexes.documents.establishment': 'local_establishment', -} \ No newline at end of file +} + +# SORL thumbnails +THUMBNAIL_DEBUG = True diff --git a/project/settings/production.py b/project/settings/production.py index e491a1fb..f2855592 100644 --- a/project/settings/production.py +++ b/project/settings/production.py @@ -1,2 +1,3 @@ """Production settings.""" from .base import * +from .amazon_s3 import * diff --git a/project/settings/stage.py b/project/settings/stage.py index c0d6fdb1..e1443ab1 100644 --- a/project/settings/stage.py +++ b/project/settings/stage.py @@ -1,5 +1,6 @@ """Stage settings.""" from .base import * +from .amazon_s3 import * ALLOWED_HOSTS = ['gm-stage.id-east.ru', '95.213.204.126'] diff --git a/project/storage_backends.py b/project/storage_backends.py new file mode 100644 index 00000000..633337e8 --- /dev/null +++ b/project/storage_backends.py @@ -0,0 +1,12 @@ +"""Extend storage backend for adding custom parameters""" +from storages.backends.s3boto3 import S3Boto3Storage + + +class PublicMediaStorage(S3Boto3Storage): + location = 'media' + file_overwrite = False + + +class PublicStaticStorage(S3Boto3Storage): + location = 'static' + file_overwrite = False diff --git a/project/urls/__init__.py b/project/urls/__init__.py index f1a89695..ca76ff43 100644 --- a/project/urls/__init__.py +++ b/project/urls/__init__.py @@ -64,7 +64,7 @@ urlpatterns = [ ] urlpatterns = urlpatterns + \ - static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + static(settings.MEDIA_LOCATION, document_root=settings.MEDIA_ROOT) if settings.DEBUG: urlpatterns.extend(urlpatterns_doc) diff --git a/requirements/base.txt b/requirements/base.txt index 25749c4b..3d8955cd 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -32,4 +32,10 @@ djangorestframework-simplejwt==4.3.0 django-elasticsearch-dsl>=7.0.0,<8.0.0 django-elasticsearch-dsl-drf==0.20.2 -sentry-sdk==0.11.2 \ No newline at end of file +sentry-sdk==0.11.2 + +# AMAZON S3 +boto3==1.9.238 +django-storages==1.7.2 + +sorl-thumbnail==12.5.0 \ No newline at end of file From 3234a71cde753ca623461073613843342b7384d4 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 2 Oct 2019 10:34:17 +0300 Subject: [PATCH 06/87] gm-148: modified settings, added missing migration --- .../migrations/0003_auto_20191002_0729.py | 17 +++++++++++++++++ project/settings/base.py | 2 ++ project/settings/local.py | 8 +++++++- 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 apps/timetable/migrations/0003_auto_20191002_0729.py diff --git a/apps/timetable/migrations/0003_auto_20191002_0729.py b/apps/timetable/migrations/0003_auto_20191002_0729.py new file mode 100644 index 00000000..16196f74 --- /dev/null +++ b/apps/timetable/migrations/0003_auto_20191002_0729.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.4 on 2019-10-02 07:29 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('timetable', '0002_auto_20190919_1124'), + ] + + operations = [ + migrations.AlterModelOptions( + name='timetable', + options={'ordering': ['weekday'], 'verbose_name': 'Timetable', 'verbose_name_plural': 'Timetables'}, + ), + ] diff --git a/project/settings/base.py b/project/settings/base.py index 2916cff0..827b78c6 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -453,4 +453,6 @@ STATICFILES_DIRS = ( os.path.join(PROJECT_ROOT, 'static'), ) + +# MEDIA MEDIA_LOCATION = 'media' diff --git a/project/settings/local.py b/project/settings/local.py index f67b8f7e..d842894c 100644 --- a/project/settings/local.py +++ b/project/settings/local.py @@ -4,22 +4,28 @@ import sys ALLOWED_HOSTS = ['*', ] + SEND_SMS = False SMS_CODE_SHOW = True USE_CELERY = False + SCHEMA_URI = 'http' DEFAULT_SUBDOMAIN = 'www' SITE_DOMAIN_URI = 'testserver.com:8000' DOMAIN_URI = '0.0.0.0:8000' + # CELERY BROKER_URL = 'amqp://rabbitmq:5672' CELERY_RESULT_BACKEND = BROKER_URL CELERY_BROKER_URL = BROKER_URL -MEDIA_ROOT = os.path.join(PUBLIC_ROOT, MEDIA_LOCATION) + +# MEDIA MEDIA_URL = f'{SCHEMA_URI}://{DOMAIN_URI}/{MEDIA_LOCATION}/' +MEDIA_ROOT = os.path.join(PUBLIC_ROOT, MEDIA_LOCATION) + # LOGGING LOGGING = { From 402bd32eda8e1399ad065c2f210a76fe83733932 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 2 Oct 2019 14:10:23 +0300 Subject: [PATCH 07/87] 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' From e66b2f7f9f850beeca4caa104e9b266c127e53d1 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 2 Oct 2019 16:13:21 +0300 Subject: [PATCH 08/87] refactored --- apps/gallery/models.py | 4 +- .../migrations/0021_merge_20191002_1300.py | 14 +++ apps/news/models.py | 34 +++++-- apps/news/serializers.py | 92 ++++++++++++++++++- apps/news/views.py | 4 +- 5 files changed, 133 insertions(+), 15 deletions(-) create mode 100644 apps/news/migrations/0021_merge_20191002_1300.py diff --git a/apps/gallery/models.py b/apps/gallery/models.py index 81bfbb40..33d2debc 100644 --- a/apps/gallery/models.py +++ b/apps/gallery/models.py @@ -11,7 +11,7 @@ from utils.models import ProjectBaseMixin, SORLImageMixin, PlatformMixin class ImageQuerySet(models.QuerySet): """QuerySet for model Image.""" - def originals(self): + def original_images(self): """Return QuerySet with original images.""" return self.filter(parent__isnull=True) @@ -57,5 +57,5 @@ class Image(ProjectBaseMixin, SORLImageMixin, PlatformMixin): tasks.delete_image_from_remote_storage(self.id, delete_original) @property - def childs(self): + def children(self): return self.parent_image.filter(parent=self) diff --git a/apps/news/migrations/0021_merge_20191002_1300.py b/apps/news/migrations/0021_merge_20191002_1300.py new file mode 100644 index 00000000..ab0acf69 --- /dev/null +++ b/apps/news/migrations/0021_merge_20191002_1300.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.4 on 2019-10-02 13:00 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0020_remove_news_author'), + ('news', '0020_merge_20190930_1251'), + ] + + operations = [ + ] diff --git a/apps/news/models.py b/apps/news/models.py index efa0b566..9022a6a2 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -119,10 +119,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')) template = models.PositiveIntegerField(choices=TEMPLATE_CHOICES, default=NEWSPAPER) address = models.ForeignKey('location.Address', blank=True, null=True, default=None, verbose_name=_('address'), @@ -132,6 +128,8 @@ class News(BaseAttributes, TranslatedFieldsMixin): verbose_name=_('country')) tags = generic.GenericRelation(to='main.MetaDataContent') + gallery = models.ManyToManyField('gallery.Image', through='news.NewsGallery') + objects = NewsQuerySet.as_manager() class Meta: @@ -152,10 +150,28 @@ class News(BaseAttributes, TranslatedFieldsMixin): return reverse('web:news:rud', kwargs={'slug': self.slug}) @property - def should_read(self): - return self.__class__.objects.should_read(self)[:3] + def original_images(self): + return self.gallery.original_images() - @property - def same_theme(self): - return self.__class__.objects.same_theme(self)[:3] +class NewsGalleryQuerySet(models.QuerySet): + """QuerySet for model News""" + + +class NewsGallery(models.Model): + + news = models.ForeignKey(News, null=True, + related_name='news_gallery', + on_delete=models.SET_NULL, + verbose_name=_('news')) + image = 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 c473be1d..08dfce5e 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -1,6 +1,10 @@ """News app common serializers.""" +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers + from account.serializers.common import UserBaseSerializer +from gallery.models import Image +from gallery.serializers import ImageSerializer from location import models as location_models from location.serializers import CountrySimpleSerializer from main.serializers import MetaDataContentSerializer @@ -8,6 +12,53 @@ from news import models from utils.serializers import TranslatedField, ProjectModelSerializer +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 = [ + 'id', + 'title', + 'orientation_display', + 'web_url', + 'mobile_url', + ] + extra_kwargs = { + 'orientation': {'write_only': True} + } + + def get_web_url(self, obj): + """Return URL of cropped image by thumbnail.""" + return obj.get_image_url('news_promo_horizontal_web') + + 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='children', allow_null=True, many=True) + + class Meta: + model = Image + fields = [ + 'id', + 'title', + 'url', + 'crops', + ] + extra_kwargs = { + 'orientation': {'write_only': True} + } + + class NewsTypeSerializer(serializers.ModelSerializer): """News type serializer.""" @@ -28,6 +79,7 @@ class NewsBaseSerializer(ProjectModelSerializer): # related fields news_type = NewsTypeSerializer(read_only=True) tags = MetaDataContentSerializer(read_only=True, many=True) + gallery = NewsImageSerializer(source='original_images', read_only=True, many=True) class Meta: """Meta class.""" @@ -38,11 +90,10 @@ class NewsBaseSerializer(ProjectModelSerializer): 'title_translated', 'subtitle_translated', 'is_highlighted', - 'image_url', - 'preview_image_url', 'news_type', 'tags', 'slug', + 'gallery', ) @@ -123,3 +174,40 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer, 'template_display', ) + +class NewsBackOfficeGallerySerializer(serializers.ModelSerializer): + """Serializer class for model NewsGallery.""" + class Meta: + """Meta class""" + model = models.NewsGallery + fields = [ + 'id', + ] + + def get_request_kwargs(self): + """Get url kwargs from request.""" + return self.context.get('request').parser_context.get('kwargs') + + def validate(self, attrs): + """Override validate method.""" + news_pk = self.get_request_kwargs().get('pk') + image_id = self.get_request_kwargs().get('image_id') + + news_qs = models.News.objects.filter(pk=news_pk) + image_qs = Image.objects.filter(id=image_id) + + if not news_qs.exists(): + raise serializers.ValidationError({'detail': _('News not found')}) + if not image_qs.exists(): + raise serializers.ValidationError({'detail': _('Image not found')}) + + news = news_qs.first() + image = image_qs.first() + + if news.news_gallery.filter(image=image).exists(): + raise serializers.ValidationError({'detail': _('Image is already added')}) + + attrs['news'] = news + attrs['image'] = image + + return attrs diff --git a/apps/news/views.py b/apps/news/views.py index d398f8f8..f9d94742 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -114,7 +114,7 @@ class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView, class NewsBackOfficeGalleryListView(NewsBackOfficeMixinView, generics.ListAPIView): """Resource for returning gallery for news for back-office users.""" - serializer_class = ImageSerializer + serializer_class = serializers.NewsImageSerializer def get_object(self): """Override get_object method.""" @@ -128,7 +128,7 @@ class NewsBackOfficeGalleryListView(NewsBackOfficeMixinView, generics.ListAPIVie def get_queryset(self): """Override get_queryset method.""" - return self.get_object().gallery.all() + return self.get_object().gallery.original_images() class NewsBackOfficeRUDView(NewsBackOfficeMixinView, From 296a09165d4a78c4dfb13c726a5fd94b58029fe2 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 2 Oct 2019 18:09:29 +0300 Subject: [PATCH 09/87] added migration --- .../migrations/0003_auto_20191001_0647.py | 20 -------------- .../migrations/0003_auto_20191002_1456.py | 26 +++++++++++++++++++ apps/gallery/models.py | 6 +---- 3 files changed, 27 insertions(+), 25 deletions(-) delete mode 100644 apps/gallery/migrations/0003_auto_20191001_0647.py create mode 100644 apps/gallery/migrations/0003_auto_20191002_1456.py diff --git a/apps/gallery/migrations/0003_auto_20191001_0647.py b/apps/gallery/migrations/0003_auto_20191001_0647.py deleted file mode 100644 index 4defed2b..00000000 --- a/apps/gallery/migrations/0003_auto_20191001_0647.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 2.2.4 on 2019-10-01 06:47 - -from django.db import migrations -import sorl.thumbnail.fields -import utils.methods - - -class Migration(migrations.Migration): - - dependencies = [ - ('gallery', '0002_auto_20190930_0714'), - ] - - operations = [ - migrations.AlterField( - model_name='image', - name='image', - field=sorl.thumbnail.fields.ImageField(upload_to=utils.methods.image_path, verbose_name='image file'), - ), - ] diff --git a/apps/gallery/migrations/0003_auto_20191002_1456.py b/apps/gallery/migrations/0003_auto_20191002_1456.py new file mode 100644 index 00000000..4ef3d1d0 --- /dev/null +++ b/apps/gallery/migrations/0003_auto_20191002_1456.py @@ -0,0 +1,26 @@ +# Generated by Django 2.2.4 on 2019-10-02 14:56 + +from django.db import migrations, models +import django.db.models.deletion +import sorl.thumbnail.fields +import utils.methods + + +class Migration(migrations.Migration): + + dependencies = [ + ('gallery', '0002_auto_20190930_0714'), + ] + + operations = [ + migrations.AlterField( + model_name='image', + name='image', + field=sorl.thumbnail.fields.ImageField(upload_to=utils.methods.image_path, verbose_name='image file'), + ), + migrations.AlterField( + model_name='image', + name='parent', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='children', to='gallery.Image', verbose_name='parent image'), + ), + ] diff --git a/apps/gallery/models.py b/apps/gallery/models.py index 33d2debc..3f60e9af 100644 --- a/apps/gallery/models.py +++ b/apps/gallery/models.py @@ -30,7 +30,7 @@ class Image(ProjectBaseMixin, SORLImageMixin, PlatformMixin): verbose_name=_('image file')) parent = models.ForeignKey('self', blank=True, null=True, default=None, - related_name='parent_image', + related_name='children', on_delete=models.SET_DEFAULT, verbose_name=_('parent image')) orientation = models.PositiveSmallIntegerField(choices=ORIENTATIONS, @@ -55,7 +55,3 @@ class Image(ProjectBaseMixin, SORLImageMixin, PlatformMixin): tasks.delete_image_from_remote_storage.delay(self.id, delete_original) else: tasks.delete_image_from_remote_storage(self.id, delete_original) - - @property - def children(self): - return self.parent_image.filter(parent=self) From 4890e00b95a460ca958335bfc388b63e98ed41e7 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 4 Oct 2019 11:50:29 +0300 Subject: [PATCH 10/87] gm-148: refactored --- apps/account/tasks.py | 6 +- apps/authorization/tasks.py | 5 +- ...002_1456.py => 0003_auto_20191003_1228.py} | 14 ++- apps/gallery/models.py | 35 ++++---- apps/gallery/serializers.py | 1 - apps/gallery/tasks.py | 12 +-- apps/gallery/urls.py | 3 +- apps/gallery/views.py | 31 ++++--- apps/news/models.py | 10 +-- apps/news/serializers.py | 88 ++++++++++++------- apps/news/views.py | 17 ++-- apps/utils/models.py | 24 ++--- apps/utils/querysets.py | 8 +- project/settings/local.py | 5 +- 14 files changed, 146 insertions(+), 113 deletions(-) rename apps/gallery/migrations/{0003_auto_20191002_1456.py => 0003_auto_20191003_1228.py} (57%) diff --git a/apps/account/tasks.py b/apps/account/tasks.py index 03a231b3..13c6f594 100644 --- a/apps/account/tasks.py +++ b/apps/account/tasks.py @@ -18,7 +18,7 @@ def send_reset_password_email(user_id, country_code): user.send_email(subject=_('Password resetting'), message=user.reset_password_template(country_code)) except: - logger.error(f'METHOD_NAME: {send_reset_password_email.__name__}\n' + logger.error(f'TASK_NAME: {send_reset_password_email.__name__}\n' f'DETAIL: Exception occurred for reset password: ' f'{user_id}') @@ -31,7 +31,7 @@ def confirm_new_email_address(user_id, country_code): user.send_email(subject=_('Validate new email address'), message=user.confirm_email_template(country_code)) except: - logger.error(f'METHOD_NAME: {confirm_new_email_address.__name__}\n' + logger.error(f'TASK_NAME: {confirm_new_email_address.__name__}\n' f'DETAIL: Exception occurred for user: {user_id}') @@ -43,5 +43,5 @@ def change_email_address(user_id, country_code): user.send_email(subject=_('Validate new email address'), message=user.change_email_template(country_code)) except: - logger.error(f'METHOD_NAME: {change_email_address.__name__}\n' + logger.error(f'TASK_NAME: {change_email_address.__name__}\n' f'DETAIL: Exception occurred for user: {user_id}') diff --git a/apps/authorization/tasks.py b/apps/authorization/tasks.py index 9947c2a3..4df94bdc 100644 --- a/apps/authorization/tasks.py +++ b/apps/authorization/tasks.py @@ -1,7 +1,8 @@ """Authorization app celery tasks.""" import logging -from django.utils.translation import gettext_lazy as _ + from celery import shared_task +from django.utils.translation import gettext_lazy as _ from account import models as account_models @@ -17,5 +18,5 @@ def send_confirm_email(user_id, country_code): obj.send_email(subject=_('Email confirmation'), message=obj.confirm_email_template(country_code)) except: - logger.error(f'METHOD_NAME: {send_confirm_email.__name__}\n' + logger.error(f'TASK_NAME: {send_confirm_email.__name__}\n' f'DETAIL: Exception occurred for user: {user_id}') diff --git a/apps/gallery/migrations/0003_auto_20191002_1456.py b/apps/gallery/migrations/0003_auto_20191003_1228.py similarity index 57% rename from apps/gallery/migrations/0003_auto_20191002_1456.py rename to apps/gallery/migrations/0003_auto_20191003_1228.py index 4ef3d1d0..4d054a29 100644 --- a/apps/gallery/migrations/0003_auto_20191002_1456.py +++ b/apps/gallery/migrations/0003_auto_20191003_1228.py @@ -1,7 +1,6 @@ -# Generated by Django 2.2.4 on 2019-10-02 14:56 +# Generated by Django 2.2.4 on 2019-10-03 12:28 -from django.db import migrations, models -import django.db.models.deletion +from django.db import migrations import sorl.thumbnail.fields import utils.methods @@ -13,14 +12,13 @@ class Migration(migrations.Migration): ] operations = [ + migrations.RemoveField( + model_name='image', + name='parent', + ), migrations.AlterField( model_name='image', name='image', field=sorl.thumbnail.fields.ImageField(upload_to=utils.methods.image_path, verbose_name='image file'), ), - migrations.AlterField( - model_name='image', - name='parent', - field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='children', to='gallery.Image', verbose_name='parent image'), - ), ] diff --git a/apps/gallery/models.py b/apps/gallery/models.py index 3f60e9af..1388f910 100644 --- a/apps/gallery/models.py +++ b/apps/gallery/models.py @@ -1,20 +1,15 @@ from django.db import models from django.utils.translation import gettext_lazy as _ +from sorl.thumbnail import delete 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 original_images(self): - """Return QuerySet with original images.""" - return self.filter(parent__isnull=True) - class Image(ProjectBaseMixin, SORLImageMixin, PlatformMixin): """Image model.""" @@ -28,11 +23,6 @@ class Image(ProjectBaseMixin, SORLImageMixin, PlatformMixin): image = SORLImageField(upload_to=image_path, verbose_name=_('image file')) - parent = models.ForeignKey('self', - blank=True, null=True, default=None, - related_name='children', - on_delete=models.SET_DEFAULT, - verbose_name=_('parent image')) orientation = models.PositiveSmallIntegerField(choices=ORIENTATIONS, blank=True, null=True, default=None, verbose_name=_('image orientation')) @@ -49,9 +39,20 @@ class Image(ProjectBaseMixin, SORLImageMixin, PlatformMixin): """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) + 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 + delete(file_=self.image.file, delete_file=completely) + except FileNotFoundError: + pass + finally: + if completely: + # Delete an instance of image + super().delete() + + + diff --git a/apps/gallery/serializers.py b/apps/gallery/serializers.py index 08ca4a0f..e817cbd8 100644 --- a/apps/gallery/serializers.py +++ b/apps/gallery/serializers.py @@ -22,7 +22,6 @@ class ImageSerializer(serializers.ModelSerializer): 'id', 'file', 'url', - 'parent', 'orientation', 'orientation_display', 'title', diff --git a/apps/gallery/tasks.py b/apps/gallery/tasks.py index 8f3f5453..1a64d297 100644 --- a/apps/gallery/tasks.py +++ b/apps/gallery/tasks.py @@ -2,7 +2,6 @@ import logging from celery import shared_task -from sorl.thumbnail import delete from . import models @@ -11,17 +10,14 @@ logger = logging.getLogger(__name__) @shared_task -def delete_image_from_remote_storage(image_id, delete_original=True): +def delete_image(image_id: int, completely: bool = 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() + image.delete_image(completely=completely) except: - logger.error(f'METHOD_NAME: delete_image_from_remote_storage\n' + logger.error(f'TASK_NAME: delete_image\n' f'DETAIL: Exception occurred while deleting an image ' - f'from remote storage: image_id - {image_id}') + f'and related crops from remote storage: image_id - {image_id}') diff --git a/apps/gallery/urls.py b/apps/gallery/urls.py index 2faa95e4..8258092c 100644 --- a/apps/gallery/urls.py +++ b/apps/gallery/urls.py @@ -6,5 +6,6 @@ from . import views app_name = 'gallery' urlpatterns = [ - path('upload/', views.ImageBaseView.as_view(), name='upload-image'), + path('', views.ImageListCreateView.as_view(), name='list-create-image'), + path('/', views.ImageRetrieveDestroyView.as_view(), name='retrieve-destroy-image'), ] diff --git a/apps/gallery/views.py b/apps/gallery/views.py index b35df131..2b155035 100644 --- a/apps/gallery/views.py +++ b/apps/gallery/views.py @@ -1,19 +1,30 @@ -from rest_framework import generics +from django.conf import settings +from django.db.transaction import on_commit +from rest_framework import generics, status +from rest_framework.response import Response -from . import models, serializers +from . import tasks, models, serializers -class ImageBaseView(generics.CreateAPIView): - """Upload image to gallery.""" +class ImageBaseView(generics.GenericAPIView): + """Base Image view.""" model = models.Image queryset = models.Image.objects.all() serializer_class = serializers.ImageSerializer -class NewsImageListView(ImageBaseView, generics.ListAPIView): - """Return list of uploaded images for news object.""" +class ImageListCreateView(ImageBaseView, generics.ListCreateAPIView): + """List/Create Image view.""" - def get_queryset(self): - """Override get_queryset method.""" - qs = super(NewsImageListView, self).get_queryset() - return qs.filter(news_gallery__news=self.kwargs.get('news_id')) + +class ImageRetrieveDestroyView(ImageBaseView, generics.RetrieveDestroyAPIView): + """Destroy view for model Image""" + + def delete(self, request, *args, **kwargs): + """Override destroy view""" + instance = self.get_object() + if settings.USE_CELERY: + on_commit(lambda: tasks.delete_image.delay(image_id=instance.id)) + else: + on_commit(lambda: tasks.delete_image(image_id=instance.id)) + return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/apps/news/models.py b/apps/news/models.py index 9022a6a2..2e79994a 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -1,11 +1,11 @@ """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 -from random import sample as random_sample class NewsType(models.Model): @@ -149,10 +149,6 @@ 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.original_images() - class NewsGalleryQuerySet(models.QuerySet): """QuerySet for model News""" @@ -169,8 +165,6 @@ class NewsGallery(models.Model): on_delete=models.SET_NULL, verbose_name=_('gallery')) - objects = NewsGalleryQuerySet.as_manager() - class Meta: """NewsGallery meta class.""" verbose_name = _('news gallery') diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 08dfce5e..0ea5606f 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -4,7 +4,6 @@ from rest_framework import serializers from account.serializers.common import UserBaseSerializer from gallery.models import Image -from gallery.serializers import ImageSerializer from location import models as location_models from location.serializers import CountrySimpleSerializer from main.serializers import MetaDataContentSerializer @@ -12,12 +11,61 @@ from news import models from utils.serializers import TranslatedField, ProjectModelSerializer -class NewsCropImageSerializer(ImageSerializer): +class CropImageSerializer(serializers.Serializer): + """Serializer for crop images for News object.""" + preview_url = serializers.SerializerMethodField() + promo_horizontal_web_url = serializers.SerializerMethodField() + promo_horizontal_mobile_url = serializers.SerializerMethodField() + tile_horizontal_web_url = serializers.SerializerMethodField() + tile_horizontal_mobile_url = serializers.SerializerMethodField() + tile_vertical_web_url = serializers.SerializerMethodField() + highlight_vertical_web_url = serializers.SerializerMethodField() + editor_web_url = serializers.SerializerMethodField() + editor_mobile_url = serializers.SerializerMethodField() + + def get_preview_url(self, obj): + """Get crop preview.""" + return obj.instance.get_image_url('news_preview') + + def get_promo_horizontal_web_url(self, obj): + """Get crop promo_horizontal_web.""" + return obj.instance.get_image_url('news_promo_horizontal_web') + + def get_promo_horizontal_mobile_url(self, obj): + """Get crop promo_horizontal_mobile.""" + return obj.instance.get_image_url('news_promo_horizontal_mobile') + + def get_tile_horizontal_web_url(self, obj): + """Get crop tile_horizontal_web.""" + return obj.instance.get_image_url('news_tile_horizontal_web') + + def get_tile_horizontal_mobile_url(self, obj): + """Get crop tile_horizontal_mobile.""" + return obj.instance.get_image_url('news_tile_horizontal_mobile') + + def get_tile_vertical_web_url(self, obj): + """Get crop tile_vertical_web.""" + return obj.instance.get_image_url('news_tile_vertical_web') + + def get_highlight_vertical_web_url(self, obj): + """Get crop highlight_vertical_web.""" + return obj.instance.get_image_url('news_highlight_vertical_web') + + def get_editor_web_url(self, obj): + """Get crop editor_web.""" + return obj.instance.get_image_url('news_editor_web') + + def get_editor_mobile_url(self, obj): + """Get crop editor_mobile.""" + return obj.instance.get_image_url('news_editor_mobile') + + +class NewsImageSerializer(serializers.ModelSerializer): """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() + original_url = serializers.URLField(source='image.url') + auto_crop_images = CropImageSerializer(source='image', allow_null=True) class Meta: model = Image @@ -25,34 +73,8 @@ class NewsCropImageSerializer(ImageSerializer): 'id', 'title', 'orientation_display', - 'web_url', - 'mobile_url', - ] - extra_kwargs = { - 'orientation': {'write_only': True} - } - - def get_web_url(self, obj): - """Return URL of cropped image by thumbnail.""" - return obj.get_image_url('news_promo_horizontal_web') - - 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='children', allow_null=True, many=True) - - class Meta: - model = Image - fields = [ - 'id', - 'title', - 'url', - 'crops', + 'original_url', + 'auto_crop_images', ] extra_kwargs = { 'orientation': {'write_only': True} @@ -79,7 +101,7 @@ class NewsBaseSerializer(ProjectModelSerializer): # related fields news_type = NewsTypeSerializer(read_only=True) tags = MetaDataContentSerializer(read_only=True, many=True) - gallery = NewsImageSerializer(source='original_images', read_only=True, many=True) + gallery = NewsImageSerializer(read_only=True, many=True) class Meta: """Meta class.""" diff --git a/apps/news/views.py b/apps/news/views.py index f9d94742..9fee70e5 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -1,10 +1,12 @@ """News app views.""" +from django.conf import settings +from django.db.transaction import on_commit from django.shortcuts import get_object_or_404 from rest_framework import generics, permissions, status from rest_framework.response import Response +from gallery.tasks import delete_image from news import filters, models, serializers -from gallery.serializers import ImageSerializer class NewsMixinView: @@ -102,13 +104,14 @@ class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView, def destroy(self, request, *args, **kwargs): """Override destroy method.""" gallery_obj = self.get_object() - image_obj = gallery_obj.image - + if settings.USE_CELERY: + on_commit(lambda: delete_image.delay(image_id=gallery_obj.image.id, + completely=False)) + else: + on_commit(lambda: delete_image(image_id=gallery_obj.image.id, + completely=False)) # 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) @@ -128,7 +131,7 @@ class NewsBackOfficeGalleryListView(NewsBackOfficeMixinView, generics.ListAPIVie def get_queryset(self): """Override get_queryset method.""" - return self.get_object().gallery.original_images() + return self.get_object().gallery.all() class NewsBackOfficeRUDView(NewsBackOfficeMixinView, diff --git a/apps/utils/models.py b/apps/utils/models.py index 299013b7..5ff52e22 100644 --- a/apps/utils/models.py +++ b/apps/utils/models.py @@ -1,6 +1,8 @@ """Utils app models.""" import logging from os.path import exists + +from django.conf import settings from django.contrib.auth.tokens import PasswordResetTokenGenerator from django.contrib.gis.db import models from django.contrib.postgres.fields import JSONField @@ -9,12 +11,11 @@ from django.utils import timezone from django.utils.html import mark_safe from django.utils.translation import ugettext_lazy as _, get_language from easy_thumbnails.fields import ThumbnailerImageField +from sorl.thumbnail import get_thumbnail +from sorl.thumbnail.fields import ImageField as SORLImageField + from utils.methods import image_path, svg_image_path from utils.validators import svg_image_validator -from sorl.thumbnail.fields import ImageField as SORLImageField -from sorl.thumbnail import get_thumbnail -from django.conf import settings -from utils.methods import image_url_valid logger = logging.getLogger(__name__) @@ -123,7 +124,7 @@ class OAuthProjectMixin: def get_source(self): """Method to get of platform""" - return NotImplemented + return NotImplementedError class BaseAttributes(ProjectBaseMixin): @@ -193,15 +194,18 @@ class SORLImageMixin(models.Model): """Meta class.""" abstract = True - def get_image(self, thumbnail_key=None): + def get_image(self, thumbnail_key: str): """Get thumbnail image file.""" if thumbnail_key in settings.SORL_THUMBNAIL_ALIASES: - return get_thumbnail(file_=self.image, - **settings.SORL_THUMBNAIL_ALIASES[thumbnail_key]) + return get_thumbnail( + file_=self.image, + **settings.SORL_THUMBNAIL_ALIASES[thumbnail_key]) - def get_image_url(self, thumbnail_key=None): + def get_image_url(self, thumbnail_key: str): """Get image thumbnail url.""" - return self.get_image(thumbnail_key).url + crop_image = self.get_image(thumbnail_key) + if hasattr(crop_image, 'url'): + return self.get_image(thumbnail_key).url def image_tag(self): """Admin preview tag.""" diff --git a/apps/utils/querysets.py b/apps/utils/querysets.py index bf2816f2..45798fbb 100644 --- a/apps/utils/querysets.py +++ b/apps/utils/querysets.py @@ -1,8 +1,10 @@ """Utils QuerySet Mixins""" -from django.db import models -from django.db.models import Q, Sum, F from functools import reduce from operator import add + +from django.db import models +from django.db.models import Q, F + from utils.methods import get_contenttype @@ -50,7 +52,7 @@ class RelatedObjectsCountMixin(models.QuerySet): def filter_all_related_gt(self, count): """Queryset filter by all related objects count""" - exp =reduce(add, [F(f"{related_object}_count") for related_object in self._get_related_objects_names()]) + exp = reduce(add, [F(f"{related_object}_count") for related_object in self._get_related_objects_names()]) return self._annotate_related_objects_count()\ .annotate(all_related_count=exp)\ .filter(all_related_count__gt=count) diff --git a/project/settings/local.py b/project/settings/local.py index d842894c..15fbbb80 100644 --- a/project/settings/local.py +++ b/project/settings/local.py @@ -1,6 +1,7 @@ """Local settings.""" from .base import * import sys +from .amazon_s3 import * ALLOWED_HOSTS = ['*', ] @@ -23,8 +24,8 @@ CELERY_BROKER_URL = BROKER_URL # MEDIA -MEDIA_URL = f'{SCHEMA_URI}://{DOMAIN_URI}/{MEDIA_LOCATION}/' -MEDIA_ROOT = os.path.join(PUBLIC_ROOT, MEDIA_LOCATION) +# MEDIA_URL = f'{SCHEMA_URI}://{DOMAIN_URI}/{MEDIA_LOCATION}/' +# MEDIA_ROOT = os.path.join(PUBLIC_ROOT, MEDIA_LOCATION) # LOGGING From b2f900d15dcc728f766fde2f2c2528db6ff33a53 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 4 Oct 2019 13:25:42 +0300 Subject: [PATCH 11/87] gm-148: refactored --- apps/gallery/admin.py | 7 ++++++- apps/gallery/models.py | 2 +- apps/main/models.py | 1 - apps/utils/models.py | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/gallery/admin.py b/apps/gallery/admin.py index fc20b0ee..f5aa0194 100644 --- a/apps/gallery/admin.py +++ b/apps/gallery/admin.py @@ -5,4 +5,9 @@ from gallery.models import Image @admin.register(Image) class ImageModelAdmin(admin.ModelAdmin): - """Image model admin""" + """Image model admin.""" + list_display = ['id', 'title', 'image_tag', 'orientation_display'] + + def orientation_display(self, obj): + """Get image orientation name.""" + return obj.get_orientation_display() if obj.orientation else None diff --git a/apps/gallery/models.py b/apps/gallery/models.py index 1388f910..bf3870cb 100644 --- a/apps/gallery/models.py +++ b/apps/gallery/models.py @@ -37,7 +37,7 @@ class Image(ProjectBaseMixin, SORLImageMixin, PlatformMixin): def __str__(self): """String representation""" - return str(self.id) + return f'{self.title}' def delete_image(self, completely: bool = True): """ diff --git a/apps/main/models.py b/apps/main/models.py index fa6cf7d1..df04fd8a 100644 --- a/apps/main/models.py +++ b/apps/main/models.py @@ -353,7 +353,6 @@ class Carousel(models.Model): return self.content_object.establishment_type.name_translated - class Page(models.Model): """Page model.""" diff --git a/apps/utils/models.py b/apps/utils/models.py index 5ff52e22..fbfd186d 100644 --- a/apps/utils/models.py +++ b/apps/utils/models.py @@ -210,7 +210,7 @@ class SORLImageMixin(models.Model): def image_tag(self): """Admin preview tag.""" if self.image: - return mark_safe('' % self.image.url) + return mark_safe(f'') else: return None From 91790bbc396d33384ed0dc5c0cbc39a1af215ef5 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 4 Oct 2019 14:30:34 +0300 Subject: [PATCH 12/87] refactored --- apps/gallery/admin.py | 2 +- apps/search_indexes/documents/news.py | 2 -- apps/utils/models.py | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/gallery/admin.py b/apps/gallery/admin.py index f5aa0194..e325a3ed 100644 --- a/apps/gallery/admin.py +++ b/apps/gallery/admin.py @@ -6,7 +6,7 @@ from gallery.models import Image @admin.register(Image) class ImageModelAdmin(admin.ModelAdmin): """Image model admin.""" - list_display = ['id', 'title', 'image_tag', 'orientation_display'] + list_display = ['id', 'title', 'orientation_display', 'image_tag', ] def orientation_display(self, obj): """Get image orientation name.""" diff --git a/apps/search_indexes/documents/news.py b/apps/search_indexes/documents/news.py index 6e0974d8..872d955c 100644 --- a/apps/search_indexes/documents/news.py +++ b/apps/search_indexes/documents/news.py @@ -45,8 +45,6 @@ class NewsDocument(Document): 'slug', 'state', 'is_highlighted', - 'image_url', - 'preview_image_url', 'template', ) related_models = [models.NewsType] diff --git a/apps/utils/models.py b/apps/utils/models.py index fbfd186d..fb1de17c 100644 --- a/apps/utils/models.py +++ b/apps/utils/models.py @@ -210,7 +210,7 @@ class SORLImageMixin(models.Model): def image_tag(self): """Admin preview tag.""" if self.image: - return mark_safe(f'') + return mark_safe(f'') else: return None From ee93ad4a44d2bd3f13c72f97106d72c1618cb656 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 4 Oct 2019 14:31:24 +0300 Subject: [PATCH 13/87] fix settings --- project/settings/local.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/project/settings/local.py b/project/settings/local.py index 15fbbb80..8275a1a0 100644 --- a/project/settings/local.py +++ b/project/settings/local.py @@ -1,7 +1,7 @@ """Local settings.""" from .base import * import sys -from .amazon_s3 import * +# from .amazon_s3 import * ALLOWED_HOSTS = ['*', ] @@ -24,8 +24,8 @@ CELERY_BROKER_URL = BROKER_URL # MEDIA -# MEDIA_URL = f'{SCHEMA_URI}://{DOMAIN_URI}/{MEDIA_LOCATION}/' -# MEDIA_ROOT = os.path.join(PUBLIC_ROOT, MEDIA_LOCATION) +MEDIA_URL = f'{SCHEMA_URI}://{DOMAIN_URI}/{MEDIA_LOCATION}/' +MEDIA_ROOT = os.path.join(PUBLIC_ROOT, MEDIA_LOCATION) # LOGGING From 9a36b52eecc79faf95b148e7fe7a24f685cfb890 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 4 Oct 2019 15:26:29 +0300 Subject: [PATCH 14/87] fix migrations --- .../migrations/0014_auto_20190926_1156.py | 21 ------------------- apps/news/migrations/0015_newsgallery.py | 1 - apps/news/models.py | 4 ++++ .../migrations/0002_auto_20191004_1217.py | 17 +++++++++++++++ 4 files changed, 21 insertions(+), 22 deletions(-) delete mode 100644 apps/news/migrations/0014_auto_20190926_1156.py create mode 100644 apps/rating/migrations/0002_auto_20191004_1217.py diff --git a/apps/news/migrations/0014_auto_20190926_1156.py b/apps/news/migrations/0014_auto_20190926_1156.py deleted file mode 100644 index d16f0ad3..00000000 --- a/apps/news/migrations/0014_auto_20190926_1156.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 2.2.4 on 2019-09-26 11:56 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('news', '0013_auto_20190924_0806'), - ] - - operations = [ - migrations.RemoveField( - model_name='news', - name='image_url', - ), - migrations.RemoveField( - model_name='news', - name='preview_image_url', - ), - ] diff --git a/apps/news/migrations/0015_newsgallery.py b/apps/news/migrations/0015_newsgallery.py index a8422dab..8f81b4f8 100644 --- a/apps/news/migrations/0015_newsgallery.py +++ b/apps/news/migrations/0015_newsgallery.py @@ -8,7 +8,6 @@ class Migration(migrations.Migration): dependencies = [ ('gallery', '0002_auto_20190930_0714'), - ('news', '0014_auto_20190926_1156'), ] operations = [ diff --git a/apps/news/models.py b/apps/news/models.py index a87a34b4..4081a3a2 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -122,6 +122,10 @@ 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')) template = models.PositiveIntegerField(choices=TEMPLATE_CHOICES, default=NEWSPAPER) address = models.ForeignKey('location.Address', blank=True, null=True, default=None, verbose_name=_('address'), diff --git a/apps/rating/migrations/0002_auto_20191004_1217.py b/apps/rating/migrations/0002_auto_20191004_1217.py new file mode 100644 index 00000000..de37a06e --- /dev/null +++ b/apps/rating/migrations/0002_auto_20191004_1217.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.4 on 2019-10-04 12:17 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('rating', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='rating', + options={}, + ), + ] From d60d16603168de2166699b124de75ba205792a19 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 4 Oct 2019 17:01:40 +0300 Subject: [PATCH 15/87] added merge migration --- apps/rating/migrations/0003_merge_20191004_1401.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 apps/rating/migrations/0003_merge_20191004_1401.py diff --git a/apps/rating/migrations/0003_merge_20191004_1401.py b/apps/rating/migrations/0003_merge_20191004_1401.py new file mode 100644 index 00000000..f04cc893 --- /dev/null +++ b/apps/rating/migrations/0003_merge_20191004_1401.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.4 on 2019-10-04 14:01 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('rating', '0002_auto_20191004_1217'), + ('rating', '0002_auto_20191004_0928'), + ] + + operations = [ + ] From 90ab1a2ab98b8b92922b7279450d447c83def1e6 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 4 Oct 2019 18:02:51 +0300 Subject: [PATCH 16/87] Revert "fix migrations" This reverts commit 9a36b52e --- .../migrations/0014_auto_20190926_1156.py | 21 +++++++++++++++++++ apps/news/migrations/0015_newsgallery.py | 1 + apps/news/models.py | 4 ---- .../migrations/0002_auto_20191004_1217.py | 17 --------------- 4 files changed, 22 insertions(+), 21 deletions(-) create mode 100644 apps/news/migrations/0014_auto_20190926_1156.py delete mode 100644 apps/rating/migrations/0002_auto_20191004_1217.py diff --git a/apps/news/migrations/0014_auto_20190926_1156.py b/apps/news/migrations/0014_auto_20190926_1156.py new file mode 100644 index 00000000..d16f0ad3 --- /dev/null +++ b/apps/news/migrations/0014_auto_20190926_1156.py @@ -0,0 +1,21 @@ +# Generated by Django 2.2.4 on 2019-09-26 11:56 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0013_auto_20190924_0806'), + ] + + operations = [ + migrations.RemoveField( + model_name='news', + name='image_url', + ), + migrations.RemoveField( + model_name='news', + name='preview_image_url', + ), + ] diff --git a/apps/news/migrations/0015_newsgallery.py b/apps/news/migrations/0015_newsgallery.py index 8f81b4f8..a8422dab 100644 --- a/apps/news/migrations/0015_newsgallery.py +++ b/apps/news/migrations/0015_newsgallery.py @@ -8,6 +8,7 @@ class Migration(migrations.Migration): dependencies = [ ('gallery', '0002_auto_20190930_0714'), + ('news', '0014_auto_20190926_1156'), ] operations = [ diff --git a/apps/news/models.py b/apps/news/models.py index 4081a3a2..a87a34b4 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -122,10 +122,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')) template = models.PositiveIntegerField(choices=TEMPLATE_CHOICES, default=NEWSPAPER) address = models.ForeignKey('location.Address', blank=True, null=True, default=None, verbose_name=_('address'), diff --git a/apps/rating/migrations/0002_auto_20191004_1217.py b/apps/rating/migrations/0002_auto_20191004_1217.py deleted file mode 100644 index de37a06e..00000000 --- a/apps/rating/migrations/0002_auto_20191004_1217.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.2.4 on 2019-10-04 12:17 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('rating', '0001_initial'), - ] - - operations = [ - migrations.AlterModelOptions( - name='rating', - options={}, - ), - ] From 15ef2295caeebc9ebde351110a232cc8198c1b4f Mon Sep 17 00:00:00 2001 From: Anatoly Date: Mon, 7 Oct 2019 09:55:24 +0300 Subject: [PATCH 17/87] migration fix --- .../migrations/0014_auto_20190926_1156.py | 21 ------------------- apps/news/migrations/0015_newsgallery.py | 1 - apps/news/models.py | 4 ++++ .../migrations/0003_merge_20191004_1401.py | 14 ------------- 4 files changed, 4 insertions(+), 36 deletions(-) delete mode 100644 apps/news/migrations/0014_auto_20190926_1156.py delete mode 100644 apps/rating/migrations/0003_merge_20191004_1401.py diff --git a/apps/news/migrations/0014_auto_20190926_1156.py b/apps/news/migrations/0014_auto_20190926_1156.py deleted file mode 100644 index d16f0ad3..00000000 --- a/apps/news/migrations/0014_auto_20190926_1156.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 2.2.4 on 2019-09-26 11:56 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('news', '0013_auto_20190924_0806'), - ] - - operations = [ - migrations.RemoveField( - model_name='news', - name='image_url', - ), - migrations.RemoveField( - model_name='news', - name='preview_image_url', - ), - ] diff --git a/apps/news/migrations/0015_newsgallery.py b/apps/news/migrations/0015_newsgallery.py index a8422dab..8f81b4f8 100644 --- a/apps/news/migrations/0015_newsgallery.py +++ b/apps/news/migrations/0015_newsgallery.py @@ -8,7 +8,6 @@ class Migration(migrations.Migration): dependencies = [ ('gallery', '0002_auto_20190930_0714'), - ('news', '0014_auto_20190926_1156'), ] operations = [ diff --git a/apps/news/models.py b/apps/news/models.py index a87a34b4..4081a3a2 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -122,6 +122,10 @@ 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')) template = models.PositiveIntegerField(choices=TEMPLATE_CHOICES, default=NEWSPAPER) address = models.ForeignKey('location.Address', blank=True, null=True, default=None, verbose_name=_('address'), diff --git a/apps/rating/migrations/0003_merge_20191004_1401.py b/apps/rating/migrations/0003_merge_20191004_1401.py deleted file mode 100644 index f04cc893..00000000 --- a/apps/rating/migrations/0003_merge_20191004_1401.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 2.2.4 on 2019-10-04 14:01 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('rating', '0002_auto_20191004_1217'), - ('rating', '0002_auto_20191004_0928'), - ] - - operations = [ - ] From 8ac5a1970f50be997e747df3e948cf18859cf808 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Mon, 7 Oct 2019 10:51:08 +0300 Subject: [PATCH 18/87] fixed carousel --- apps/establishment/models.py | 12 ++++++------ apps/news/serializers.py | 2 ++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index fff6f45f..14debdc4 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -1,6 +1,7 @@ """Establishment models.""" from functools import reduce +import elasticsearch_dsl from django.conf import settings from django.contrib.contenttypes import fields as generic from django.contrib.gis.db.models.functions import Distance @@ -11,12 +12,11 @@ from django.db import models from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q from django.utils import timezone from django.utils.translation import gettext_lazy as _ -from elasticsearch_dsl import Q from phonenumber_field.modelfields import PhoneNumberField from collection.models import Collection -from main.models import Award, MetaDataContent from location.models import Address +from main.models import Award, MetaDataContent from review.models import Review from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin, TranslatedFieldsMixin, BaseAttributes) @@ -103,8 +103,8 @@ class EstablishmentQuerySet(models.QuerySet): """Search text via ElasticSearch.""" from search_indexes.documents import EstablishmentDocument search = EstablishmentDocument.search().filter( - Q('match', name=value) | - Q('match', **{f'description.{locale}': value}) + elasticsearch_dsl.Q('match', name=value) | + elasticsearch_dsl.Q('match', **{f'description.{locale}': value}) ).execute() ids = [result.meta.id for result in search] return self.filter(id__in=ids) @@ -375,8 +375,8 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): @property def the_most_recent_award(self): - return Award.objects.filter(Q(establishment=self) | Q(employees__establishments=self)).latest( - field_name='vintage_year') + return Award.objects.filter(Q(establishment=self) | Q(employees__establishments=self)) \ + .latest(field_name='vintage_year') class Position(BaseAttributes, TranslatedFieldsMixin): diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 0ea5606f..6c4db75d 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -116,6 +116,8 @@ class NewsBaseSerializer(ProjectModelSerializer): 'tags', 'slug', 'gallery', + 'image_url', + 'preview_image_url', ) From 93894db7aaedff07e4b60bba57948526201998db Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Wed, 9 Oct 2019 15:20:51 +0300 Subject: [PATCH 19/87] remove duplicate migration --- .../migrations/0003_auto_20191002_0729.py | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 apps/timetable/migrations/0003_auto_20191002_0729.py diff --git a/apps/timetable/migrations/0003_auto_20191002_0729.py b/apps/timetable/migrations/0003_auto_20191002_0729.py deleted file mode 100644 index 16196f74..00000000 --- a/apps/timetable/migrations/0003_auto_20191002_0729.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.2.4 on 2019-10-02 07:29 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('timetable', '0002_auto_20190919_1124'), - ] - - operations = [ - migrations.AlterModelOptions( - name='timetable', - options={'ordering': ['weekday'], 'verbose_name': 'Timetable', 'verbose_name_plural': 'Timetables'}, - ), - ] From 05d3e3ff60573f8ca5412ec6afdc3494b19ee575 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 9 Oct 2019 15:33:05 +0300 Subject: [PATCH 20/87] fix migrations --- .../migrations/0003_auto_20191002_0729.py | 17 ----------------- .../migrations/0003_auto_20191003_0943.py | 17 ----------------- 2 files changed, 34 deletions(-) delete mode 100644 apps/timetable/migrations/0003_auto_20191002_0729.py delete mode 100644 apps/timetable/migrations/0003_auto_20191003_0943.py diff --git a/apps/timetable/migrations/0003_auto_20191002_0729.py b/apps/timetable/migrations/0003_auto_20191002_0729.py deleted file mode 100644 index 16196f74..00000000 --- a/apps/timetable/migrations/0003_auto_20191002_0729.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.2.4 on 2019-10-02 07:29 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('timetable', '0002_auto_20190919_1124'), - ] - - operations = [ - migrations.AlterModelOptions( - name='timetable', - options={'ordering': ['weekday'], 'verbose_name': 'Timetable', 'verbose_name_plural': 'Timetables'}, - ), - ] diff --git a/apps/timetable/migrations/0003_auto_20191003_0943.py b/apps/timetable/migrations/0003_auto_20191003_0943.py deleted file mode 100644 index 4c5a22b4..00000000 --- a/apps/timetable/migrations/0003_auto_20191003_0943.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.2.4 on 2019-10-03 09:43 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('timetable', '0002_auto_20190919_1124'), - ] - - operations = [ - migrations.AlterModelOptions( - name='timetable', - options={'ordering': ['weekday'], 'verbose_name': 'Timetable', 'verbose_name_plural': 'Timetables'}, - ), - ] From 3f9dea30ffed35244f1370529da184ea46d9f1c0 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 9 Oct 2019 15:54:04 +0300 Subject: [PATCH 21/87] Revert "remove duplicate migration" This reverts commit 93894db7 --- .../migrations/0003_auto_20191002_0729.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 apps/timetable/migrations/0003_auto_20191002_0729.py diff --git a/apps/timetable/migrations/0003_auto_20191002_0729.py b/apps/timetable/migrations/0003_auto_20191002_0729.py new file mode 100644 index 00000000..16196f74 --- /dev/null +++ b/apps/timetable/migrations/0003_auto_20191002_0729.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.4 on 2019-10-02 07:29 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('timetable', '0002_auto_20190919_1124'), + ] + + operations = [ + migrations.AlterModelOptions( + name='timetable', + options={'ordering': ['weekday'], 'verbose_name': 'Timetable', 'verbose_name_plural': 'Timetables'}, + ), + ] From 091491e19f3a9ae21a4228c3b25989e0ff895e20 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 9 Oct 2019 15:57:35 +0300 Subject: [PATCH 22/87] small fix --- .../{0003_auto_20191002_0729.py => 0003_auto_20191003_0943.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename apps/timetable/migrations/{0003_auto_20191002_0729.py => 0003_auto_20191003_0943.py} (88%) diff --git a/apps/timetable/migrations/0003_auto_20191002_0729.py b/apps/timetable/migrations/0003_auto_20191003_0943.py similarity index 88% rename from apps/timetable/migrations/0003_auto_20191002_0729.py rename to apps/timetable/migrations/0003_auto_20191003_0943.py index 16196f74..4c5a22b4 100644 --- a/apps/timetable/migrations/0003_auto_20191002_0729.py +++ b/apps/timetable/migrations/0003_auto_20191003_0943.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.4 on 2019-10-02 07:29 +# Generated by Django 2.2.4 on 2019-10-03 09:43 from django.db import migrations From 2d375a51cff58a133764d065923bdc208ec3314c Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 9 Oct 2019 16:00:06 +0300 Subject: [PATCH 23/87] change Celery state for dev settings --- project/settings/development.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/settings/development.py b/project/settings/development.py index 59691818..6c726e25 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 = True +USE_CELERY = False SCHEMA_URI = 'http' DEFAULT_SUBDOMAIN = 'www' From 868a3c1b783a30d74ee89c6c936e91d7e046ae38 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 11 Oct 2019 16:36:34 +0300 Subject: [PATCH 24/87] added merge migration --- .../account/migrations/0011_merge_20191011_1336.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 apps/account/migrations/0011_merge_20191011_1336.py diff --git a/apps/account/migrations/0011_merge_20191011_1336.py b/apps/account/migrations/0011_merge_20191011_1336.py new file mode 100644 index 00000000..6a031068 --- /dev/null +++ b/apps/account/migrations/0011_merge_20191011_1336.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.4 on 2019-10-11 13:36 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0009_auto_20191002_0648'), + ('account', '0010_user_password_confirmed'), + ] + + operations = [ + ] From 5562a9e7c981aa825aed03c0e1e58f3bb6e7fc32 Mon Sep 17 00:00:00 2001 From: Semyon Yekhmenin Date: Tue, 15 Oct 2019 12:16:23 +0300 Subject: [PATCH 25/87] Fixed establishments/slug/, added filtering by country code --- apps/establishment/views/web.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index 8f5d2a26..0d2249b1 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -45,7 +45,9 @@ class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView serializer_class = serializers.EstablishmentDetailSerializer def get_queryset(self): - return super().get_queryset().with_extended_related() + return super().get_queryset() \ + .by_country_code(self.request.country_code) \ + .with_extended_related() class EstablishmentRecentReviewListView(EstablishmentListView): From f86b7d489fd75f7dce0cea6f67c91056aa5e52b7 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Tue, 15 Oct 2019 12:23:13 +0300 Subject: [PATCH 26/87] fix merge --- .../account/migrations/0012_merge_20191015_0912.py | 14 ++++++++++++++ apps/news/migrations/0022_merge_20191015_0912.py | 14 ++++++++++++++ apps/news/views.py | 2 -- project/urls/back.py | 2 +- 4 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 apps/account/migrations/0012_merge_20191015_0912.py create mode 100644 apps/news/migrations/0022_merge_20191015_0912.py diff --git a/apps/account/migrations/0012_merge_20191015_0912.py b/apps/account/migrations/0012_merge_20191015_0912.py new file mode 100644 index 00000000..558940ce --- /dev/null +++ b/apps/account/migrations/0012_merge_20191015_0912.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.4 on 2019-10-15 09:12 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0011_merge_20191014_1258'), + ('account', '0011_merge_20191011_1336'), + ] + + operations = [ + ] diff --git a/apps/news/migrations/0022_merge_20191015_0912.py b/apps/news/migrations/0022_merge_20191015_0912.py new file mode 100644 index 00000000..08b5a613 --- /dev/null +++ b/apps/news/migrations/0022_merge_20191015_0912.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.4 on 2019-10-15 09:12 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0021_auto_20191009_1408'), + ('news', '0021_merge_20191002_1300'), + ] + + operations = [ + ] diff --git a/apps/news/views.py b/apps/news/views.py index 601654b6..5e85b111 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -1,6 +1,4 @@ """News app views.""" -from django.shortcuts import get_object_or_404 -from rest_framework import generics, permissions from django.conf import settings from django.db.transaction import on_commit from django.shortcuts import get_object_or_404 diff --git a/project/urls/back.py b/project/urls/back.py index 7cfcd038..eb049b9c 100644 --- a/project/urls/back.py +++ b/project/urls/back.py @@ -7,7 +7,7 @@ urlpatterns = [ path('establishments/', include('establishment.urls.back')), path('location/', include('location.urls.back')), path('news/', include('news.urls.back')), - path('tags/', include(('tag.urls', 'tag'), namespace='tag')) + path('tags/', include(('tag.urls', 'tag'), namespace='tag')), path('account/', include('account.urls.back')), path('comment/', include('comment.urls.back')), ] From 9c09f000fb894566f02460300751d40b8baeea36 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 15 Oct 2019 16:32:47 +0300 Subject: [PATCH 27/87] Password changed notification email --- apps/account/models.py | 9 ++++ apps/account/serializers/common.py | 8 ++++ apps/account/serializers/web.py | 11 ++++- apps/account/tasks.py | 45 +++++++++---------- project/settings/base.py | 3 +- .../account/password_change_email.html | 7 +++ 6 files changed, 58 insertions(+), 25 deletions(-) create mode 100644 project/templates/account/password_change_email.html diff --git a/apps/account/models.py b/apps/account/models.py index 5052969e..aa49dc3b 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -202,6 +202,15 @@ class User(AbstractUser): template_name=settings.RESETTING_TOKEN_TEMPLATE, context=context) + def notify_password_changed_template(self, country_code): + """Get notification email template""" + context = {'contry_code': country_code} + context.update(self.base_template) + return render_to_string( + template_name=settings.NOTIFICATION_PASSWORD_TEMPLATE, + context=context, + ) + def confirm_email_template(self, country_code): """Get confirm email template""" context = {'token': self.confirm_email_token, diff --git a/apps/account/serializers/common.py b/apps/account/serializers/common.py index ad232eae..d68cfe56 100644 --- a/apps/account/serializers/common.py +++ b/apps/account/serializers/common.py @@ -127,6 +127,14 @@ class ChangePasswordSerializer(serializers.ModelSerializer): except serializers.ValidationError as e: raise serializers.ValidationError({'detail': e.detail}) else: + if settings.USE_CELERY: + tasks.send_password_changed_email( + user_id=self.instance.id, + country_code=self.context.get('request').country_code) + else: + tasks.send_password_changed_email( + user_id=self.instance.id, + country_code=self.context.get('request').country_code) return attrs def update(self, instance, validated_data): diff --git a/apps/account/serializers/web.py b/apps/account/serializers/web.py index 8be73afa..2f04b31f 100644 --- a/apps/account/serializers/web.py +++ b/apps/account/serializers/web.py @@ -1,8 +1,9 @@ """Serializers for account web""" +from django.conf import settings from django.contrib.auth import password_validation as password_validators from rest_framework import serializers -from account import models +from account import models, tasks from utils import exceptions as utils_exceptions from utils.methods import username_validator @@ -68,4 +69,12 @@ class PasswordResetConfirmSerializer(serializers.ModelSerializer): # Update user password from instance instance.set_password(validated_data.get('password')) instance.save() + if settings.USE_CELERY: + tasks.send_password_changed_email( + user_id=instance.id, + country_code=self.context.get('request').country_code) + else: + tasks.send_password_changed_email( + user_id=instance.id, + country_code=self.context.get('request').country_code) return instance diff --git a/apps/account/tasks.py b/apps/account/tasks.py index 03a231b3..d9fa7bb7 100644 --- a/apps/account/tasks.py +++ b/apps/account/tasks.py @@ -1,47 +1,46 @@ """Account app celery tasks.""" +import inspect import logging from celery import shared_task from django.utils.translation import gettext_lazy as _ -from . import models +from account.models import User logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.INFO) logger = logging.getLogger(__name__) +def send_email(user_id: int, subject: str, message_prop: str, country_code: str): + try: + user = User.objects.get(id=user_id) + user.send_email(subject=_(subject), + message=getattr(user, message_prop, lambda _: '')(country_code)) + except: + cur_frame = inspect.currentframe() + cal_frame = inspect.getouterframes(cur_frame, 2) + logger.error(f'METHOD_NAME: {cal_frame[1][3]}\n' + f'DETAIL: Exception occurred for user: {user_id}') + @shared_task def send_reset_password_email(user_id, country_code): """Send email to user for reset password.""" - try: - user = models.User.objects.get(id=user_id) - user.send_email(subject=_('Password resetting'), - message=user.reset_password_template(country_code)) - except: - logger.error(f'METHOD_NAME: {send_reset_password_email.__name__}\n' - f'DETAIL: Exception occurred for reset password: ' - f'{user_id}') + send_email(user_id, 'Password_resetting', 'reset_password_template', country_code) @shared_task def confirm_new_email_address(user_id, country_code): """Send email to user new email.""" - try: - user = models.User.objects.get(id=user_id) - user.send_email(subject=_('Validate new email address'), - message=user.confirm_email_template(country_code)) - except: - logger.error(f'METHOD_NAME: {confirm_new_email_address.__name__}\n' - f'DETAIL: Exception occurred for user: {user_id}') + send_email(user_id, 'Confirm new email address', 'confirm_email_template', country_code) @shared_task def change_email_address(user_id, country_code): """Send email to user new email.""" - try: - user = models.User.objects.get(id=user_id) - user.send_email(subject=_('Validate new email address'), - message=user.change_email_template(country_code)) - except: - logger.error(f'METHOD_NAME: {change_email_address.__name__}\n' - f'DETAIL: Exception occurred for user: {user_id}') + send_email(user_id, 'Validate new email address', 'change_email_template', country_code) + + +@shared_task +def send_password_changed_email(user_id, country_code): + """Send email which notifies user that his password had changed""" + send_email(user_id, 'Notify password changed', 'notify_password_changed_template', country_code) diff --git a/project/settings/base.py b/project/settings/base.py index 06f83811..8bb470a6 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -406,7 +406,8 @@ PASSWORD_RESET_TIMEOUT_DAYS = 1 RESETTING_TOKEN_TEMPLATE = 'account/password_reset_email.html' CHANGE_EMAIL_TEMPLATE = 'account/change_email.html' CONFIRM_EMAIL_TEMPLATE = 'authorization/confirm_email.html' -NEWS_EMAIL_TEMPLATE = "news/news_email.html" +NEWS_EMAIL_TEMPLATE = 'news/news_email.html' +NOTIFICATION_PASSWORD_TEMPLATE = 'account/password_change_email.html' # COOKIES diff --git a/project/templates/account/password_change_email.html b/project/templates/account/password_change_email.html new file mode 100644 index 00000000..30dd2aac --- /dev/null +++ b/project/templates/account/password_change_email.html @@ -0,0 +1,7 @@ +{% load i18n %}{% autoescape off %} +{% blocktrans %}You're receiving this email because your account's password address at {{ site_name }}.{% endblocktrans %} + +{% trans "Thanks for using our site!" %} + +{% blocktrans %}The {{ site_name }} team{% endblocktrans %} +{% endautoescape %} \ No newline at end of file From e0bce99ca952c542a8bf314f71cd4b1dac3332ca Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 15 Oct 2019 21:40:55 +0300 Subject: [PATCH 28/87] Fix issue with copies --- apps/tag/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/tag/models.py b/apps/tag/models.py index 4fa04f8e..b811796c 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -47,7 +47,7 @@ class TagCategoryQuerySet(models.QuerySet): def with_tags(self, switcher=True): """Filter by existing tags.""" - return self.filter(tags__isnull=not switcher) + return self.exclude(tags__isnull=switcher) class TagCategory(TranslatedFieldsMixin, models.Model): From 8dad3df4bf4a64332e262338689496517ae3e17e Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 14:11:07 +0300 Subject: [PATCH 29/87] Added route for mobile tags --- apps/tag/urls/mobile.py | 7 +++++++ project/urls/mobile.py | 1 + 2 files changed, 8 insertions(+) create mode 100644 apps/tag/urls/mobile.py diff --git a/apps/tag/urls/mobile.py b/apps/tag/urls/mobile.py new file mode 100644 index 00000000..561697d2 --- /dev/null +++ b/apps/tag/urls/mobile.py @@ -0,0 +1,7 @@ +from tag.urls.web import urlpatterns as common_urlpatterns + +urlpatterns = [ + +] + +urlpatterns.extend(common_urlpatterns) diff --git a/project/urls/mobile.py b/project/urls/mobile.py index 0bcbd31c..dd09dfb7 100644 --- a/project/urls/mobile.py +++ b/project/urls/mobile.py @@ -4,6 +4,7 @@ app_name = 'mobile' urlpatterns = [ path('establishments/', include('establishment.urls.mobile')), + path('tags/', include('tag.urls.mobile')), # path('account/', include('account.urls.web')), # path('advertisement/', include('advertisement.urls.web')), # path('collection/', include('collection.urls.web')), From 12a339cd1b16b0423328e407bb263d6105ede005 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 14:36:17 +0300 Subject: [PATCH 30/87] Show timetables list --- apps/timetable/serialziers.py | 6 ++++++ apps/timetable/urls/__init__.py | 0 apps/timetable/{urls.py => urls/common.py} | 0 apps/timetable/urls/mobile.py | 6 ++++++ apps/timetable/urls/web.py | 6 ++++++ apps/timetable/views.py | 3 ++- project/urls/mobile.py | 1 + project/urls/web.py | 1 + 8 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 apps/timetable/urls/__init__.py rename apps/timetable/{urls.py => urls/common.py} (100%) create mode 100644 apps/timetable/urls/mobile.py create mode 100644 apps/timetable/urls/web.py diff --git a/apps/timetable/serialziers.py b/apps/timetable/serialziers.py index 1339cc8e..06483d90 100644 --- a/apps/timetable/serialziers.py +++ b/apps/timetable/serialziers.py @@ -77,3 +77,9 @@ class ScheduleCreateSerializer(ScheduleRUDSerializer): schedule_qs.delete() establishment.schedule.add(instance) return instance + +class TimetableSerializer(serializers.ModelSerializer): + + class Meta: + model = Timetable + fields = '__all__' diff --git a/apps/timetable/urls/__init__.py b/apps/timetable/urls/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/timetable/urls.py b/apps/timetable/urls/common.py similarity index 100% rename from apps/timetable/urls.py rename to apps/timetable/urls/common.py diff --git a/apps/timetable/urls/mobile.py b/apps/timetable/urls/mobile.py new file mode 100644 index 00000000..1c6419a3 --- /dev/null +++ b/apps/timetable/urls/mobile.py @@ -0,0 +1,6 @@ +from timetable.urls.common import urlpatterns as common_urlpatterns + + +urlpatterns = [] + +urlpatterns.extend(common_urlpatterns) \ No newline at end of file diff --git a/apps/timetable/urls/web.py b/apps/timetable/urls/web.py new file mode 100644 index 00000000..1c6419a3 --- /dev/null +++ b/apps/timetable/urls/web.py @@ -0,0 +1,6 @@ +from timetable.urls.common import urlpatterns as common_urlpatterns + + +urlpatterns = [] + +urlpatterns.extend(common_urlpatterns) \ No newline at end of file diff --git a/apps/timetable/views.py b/apps/timetable/views.py index d8a13f71..226f6c5f 100644 --- a/apps/timetable/views.py +++ b/apps/timetable/views.py @@ -1,4 +1,4 @@ -from rest_framework import generics +from rest_framework import generics, permissions from timetable import serialziers, models @@ -6,3 +6,4 @@ class TimetableListView(generics.ListAPIView): """Method to get timetables""" serializer_class = serialziers.TimetableSerializer queryset = models.Timetable.objects.all() + permission_classes = (permissions.AllowAny, ) diff --git a/project/urls/mobile.py b/project/urls/mobile.py index dd09dfb7..5831a96a 100644 --- a/project/urls/mobile.py +++ b/project/urls/mobile.py @@ -5,6 +5,7 @@ app_name = 'mobile' urlpatterns = [ path('establishments/', include('establishment.urls.mobile')), path('tags/', include('tag.urls.mobile')), + path('timetables/', include('timetable.urls.mobile')), # path('account/', include('account.urls.web')), # path('advertisement/', include('advertisement.urls.web')), # path('collection/', include('collection.urls.web')), diff --git a/project/urls/web.py b/project/urls/web.py index 5bd67207..7fb7efec 100644 --- a/project/urls/web.py +++ b/project/urls/web.py @@ -34,4 +34,5 @@ urlpatterns = [ path('translation/', include('translation.urls')), path('comments/', include('comment.urls.web')), path('favorites/', include('favorites.urls')), + path('timetables/', include('timetable.urls.web')), ] From c2c9f33b571b937559db5b1ca70d59151f9522fb Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 14:44:55 +0300 Subject: [PATCH 31/87] Show timetables list (only necessary fields) --- apps/timetable/serialziers.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/timetable/serialziers.py b/apps/timetable/serialziers.py index 06483d90..ecd4127e 100644 --- a/apps/timetable/serialziers.py +++ b/apps/timetable/serialziers.py @@ -79,7 +79,13 @@ class ScheduleCreateSerializer(ScheduleRUDSerializer): return instance class TimetableSerializer(serializers.ModelSerializer): + """Serailzier for Timetable model.""" + weekday_display = serializers.CharField(source='get_weekday_display', + read_only=True) class Meta: model = Timetable - fields = '__all__' + fields = ( + 'id', + 'weekday_display', + ) From 6d7341377bb191fbce2479285253f02d765b5444 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 14:46:36 +0300 Subject: [PATCH 32/87] Show timetables list (remove pagination) --- apps/timetable/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/timetable/views.py b/apps/timetable/views.py index 226f6c5f..5129f068 100644 --- a/apps/timetable/views.py +++ b/apps/timetable/views.py @@ -6,4 +6,5 @@ class TimetableListView(generics.ListAPIView): """Method to get timetables""" serializer_class = serialziers.TimetableSerializer queryset = models.Timetable.objects.all() + pagination_class = None permission_classes = (permissions.AllowAny, ) From ed03f0f40f253ca6814866184b865d23a73c6e88 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 19:05:16 +0300 Subject: [PATCH 33/87] Search establishment by working time --- apps/establishment/models.py | 10 ++++++++++ apps/search_indexes/documents/establishment.py | 6 ++++++ apps/search_indexes/views.py | 6 ++++++ apps/timetable/models.py | 11 +++++++++++ apps/timetable/serialziers.py | 1 + 5 files changed, 34 insertions(+) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 439e99ac..f2297c6d 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -368,6 +368,16 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): def best_price_carte(self): return 200 + @property + def works_noon(self): + """ Used for indexing working by day """ + return [ret.weekday for ret in self.schedule.all() if ret.works_at_noon] + + @property + def works_afternoon(self): + """ Used for indexing working by day """ + return [ret.weekday for ret in self.schedule.all() if ret.works_at_afternoon] + @property def tags_indexing(self): return [{'id': tag.metadata.id, diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index c30d4c58..f80e78a3 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -32,6 +32,12 @@ class EstablishmentDocument(Document): }), }, multi=True) + works_afternoon = fields.ListField(fields.IntegerField( + attr='works_afternoon' + )) + works_noon = fields.ListField(fields.IntegerField( + attr='works_noon' + )) tags = fields.ObjectField( properties={ 'id': fields.IntegerField(attr='id'), diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index 50c32fc7..ed83cf5d 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -121,6 +121,12 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): 'establishment_subtypes': { 'field': 'establishment_subtypes.id' }, + 'works_noon': { + 'field': 'works_noon' + }, + 'works_afternoon': { + 'field': 'works_afternoon' + }, } geo_spatial_filter_fields = { diff --git a/apps/timetable/models.py b/apps/timetable/models.py index 53670d02..caa6e6ac 100644 --- a/apps/timetable/models.py +++ b/apps/timetable/models.py @@ -1,5 +1,6 @@ from django.db import models from django.utils.translation import gettext_lazy as _ +from datetime import time from utils.models import ProjectBaseMixin @@ -14,6 +15,8 @@ class Timetable(ProjectBaseMixin): SATURDAY = 5 SUNDAY = 6 + NOON = time(17, 0) + WEEKDAYS_CHOICES = ( (MONDAY, _('Monday')), (TUESDAY, _('Tuesday')), @@ -32,6 +35,14 @@ class Timetable(ProjectBaseMixin): opening_at = models.TimeField(verbose_name=_('Opening time'), null=True) closed_at = models.TimeField(verbose_name=_('Closed time'), null=True) + @property + def works_at_noon(self): + return bool(self.closed_at and self.closed_at <= self.NOON) + + @property + def works_at_afternoon(self): + return bool(self.closed_at and self.closed_at > self.NOON) + class Meta: """Meta class.""" verbose_name = _('Timetable') diff --git a/apps/timetable/serialziers.py b/apps/timetable/serialziers.py index ecd4127e..76a37257 100644 --- a/apps/timetable/serialziers.py +++ b/apps/timetable/serialziers.py @@ -88,4 +88,5 @@ class TimetableSerializer(serializers.ModelSerializer): fields = ( 'id', 'weekday_display', + 'works_at_noon', ) From 2f656c459de73f387dfe0ef06bca67088fcb9dfd Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 19:09:38 +0300 Subject: [PATCH 34/87] Search establishment by working time (added fields to serializer) --- apps/search_indexes/serializers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 18a1e240..be2d0e8b 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -84,6 +84,8 @@ class EstablishmentDocumentSerializer(DocumentSerializer): 'preview_image', 'address', 'tags', + 'works_noon', + 'works_afternoon', # 'collections', # 'establishment_type', # 'establishment_subtypes', From 9fc4a8703cbe046eefea0e4c13a618fcf1e053f9 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 19:13:33 +0300 Subject: [PATCH 35/87] Search establishment by working time (added lookup) --- apps/search_indexes/views.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index ed83cf5d..9d8d680b 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -122,10 +122,16 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): 'field': 'establishment_subtypes.id' }, 'works_noon': { - 'field': 'works_noon' + 'field': 'works_noon', + 'lookups': [ + constants.LOOKUP_QUERY_IN, + ], }, 'works_afternoon': { - 'field': 'works_afternoon' + 'field': 'works_afternoon', + 'lookups': [ + constants.LOOKUP_QUERY_IN, + ], }, } From 40f1cc1e222d15092f4a5c61d82a5045f554743c Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 20:56:56 +0300 Subject: [PATCH 36/87] Add lookup for categories --- apps/search_indexes/views.py | 3 +++ apps/timetable/urls/common.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index 9d8d680b..cc2b8042 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -111,6 +111,9 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): }, 'tags_category_id': { 'field': 'tags.category.id', + 'lookups': [ + constants.LOOKUP_QUERY_IN, + ], }, 'collection_type': { 'field': 'collections.collection_type' diff --git a/apps/timetable/urls/common.py b/apps/timetable/urls/common.py index ff4f4f00..95593b20 100644 --- a/apps/timetable/urls/common.py +++ b/apps/timetable/urls/common.py @@ -6,5 +6,5 @@ from timetable import views app_name = 'timetable' urlpatterns = [ - path('', views.TimetableListView.as_view(), name='list') + # path('', views.TimetableListView.as_view(), name='list') ] \ No newline at end of file From 6a85e52abcb7368658f83298c4111d738ea67fd0 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 17 Oct 2019 12:30:40 +0300 Subject: [PATCH 37/87] Rename variable --- apps/establishment/models.py | 2 +- apps/search_indexes/documents/establishment.py | 4 ++-- apps/search_indexes/serializers.py | 2 +- apps/search_indexes/views.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index f2297c6d..5d50284c 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -374,7 +374,7 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): return [ret.weekday for ret in self.schedule.all() if ret.works_at_noon] @property - def works_afternoon(self): + def works_evening(self): """ Used for indexing working by day """ return [ret.weekday for ret in self.schedule.all() if ret.works_at_afternoon] diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index f80e78a3..88aaaaa5 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -32,8 +32,8 @@ class EstablishmentDocument(Document): }), }, multi=True) - works_afternoon = fields.ListField(fields.IntegerField( - attr='works_afternoon' + works_evening = fields.ListField(fields.IntegerField( + attr='works_evening' )) works_noon = fields.ListField(fields.IntegerField( attr='works_noon' diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index be2d0e8b..a933fd9b 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -85,7 +85,7 @@ class EstablishmentDocumentSerializer(DocumentSerializer): 'address', 'tags', 'works_noon', - 'works_afternoon', + 'works_evening', # 'collections', # 'establishment_type', # 'establishment_subtypes', diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index cc2b8042..0a3afe8e 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -130,8 +130,8 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): constants.LOOKUP_QUERY_IN, ], }, - 'works_afternoon': { - 'field': 'works_afternoon', + 'works_evening': { + 'field': 'works_evening', 'lookups': [ constants.LOOKUP_QUERY_IN, ], From 0c2b657244244611d3ba4972807712b0d274742a Mon Sep 17 00:00:00 2001 From: Anatoly Date: Thu, 17 Oct 2019 13:47:59 +0300 Subject: [PATCH 38/87] small fix --- project/settings/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/settings/base.py b/project/settings/base.py index e779f4ed..82d6e016 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -211,8 +211,8 @@ REST_FRAMEWORK = { 'COERCE_DECIMAL_TO_STRING': False, 'DEFAULT_AUTHENTICATION_CLASSES': ( # JWT + 'utils.authentication.GMJWTAuthentication', 'rest_framework.authentication.SessionAuthentication', - # 'utils.authentication.GMJWTAuthentication', ), 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning', 'DEFAULT_VERSION': (AVAILABLE_VERSIONS['current'],), From 79b4b59478136359945bc3a513708011d303514c Mon Sep 17 00:00:00 2001 From: Anatoly Date: Thu, 17 Oct 2019 13:49:29 +0300 Subject: [PATCH 39/87] enabling celery for development and stage settings --- project/settings/development.py | 2 +- project/settings/stage.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/project/settings/development.py b/project/settings/development.py index 6c726e25..59691818 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' diff --git a/project/settings/stage.py b/project/settings/stage.py index e1443ab1..49a7ae0f 100644 --- a/project/settings/stage.py +++ b/project/settings/stage.py @@ -6,7 +6,7 @@ ALLOWED_HOSTS = ['gm-stage.id-east.ru', '95.213.204.126'] SEND_SMS = False SMS_CODE_SHOW = True -USE_CELERY = False +USE_CELERY = True SCHEMA_URI = 'https' DEFAULT_SUBDOMAIN = 'www' From c8c66e7d1b6af924167d11d7402c33cc95acdd7b Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 17 Oct 2019 14:32:01 +0300 Subject: [PATCH 40/87] add schedule to search results --- apps/search_indexes/documents/establishment.py | 1 + apps/search_indexes/serializers.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index 88aaaaa5..f3f9505c 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -94,6 +94,7 @@ class EstablishmentDocument(Document): 'toque_number', 'public_mark', 'slug', + 'schedule', ) def get_queryset(self): diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index a933fd9b..d4b834ca 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -4,6 +4,7 @@ from django_elasticsearch_dsl_drf.serializers import DocumentSerializer from news.serializers import NewsTypeSerializer from search_indexes.documents import EstablishmentDocument, NewsDocument from search_indexes.utils import get_translated_value +from timetable.serialziers import ScheduleRUDSerializer class TagsDocumentSerializer(serializers.Serializer): @@ -68,6 +69,7 @@ class EstablishmentDocumentSerializer(DocumentSerializer): address = AddressDocumentSerializer() tags = TagsDocumentSerializer(many=True) + schedule = ScheduleRUDSerializer(many=True, allow_null=True) class Meta: """Meta class.""" @@ -84,6 +86,7 @@ class EstablishmentDocumentSerializer(DocumentSerializer): 'preview_image', 'address', 'tags', + 'schedule', 'works_noon', 'works_evening', # 'collections', From acea297c946e6942ef926dfe4d58aa9c6785b375 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 17 Oct 2019 14:33:20 +0300 Subject: [PATCH 41/87] Revert "add schedule to search results" This reverts commit c8c66e7 --- apps/search_indexes/documents/establishment.py | 1 - apps/search_indexes/serializers.py | 3 --- 2 files changed, 4 deletions(-) diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index f3f9505c..88aaaaa5 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -94,7 +94,6 @@ class EstablishmentDocument(Document): 'toque_number', 'public_mark', 'slug', - 'schedule', ) def get_queryset(self): diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index d4b834ca..a933fd9b 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -4,7 +4,6 @@ from django_elasticsearch_dsl_drf.serializers import DocumentSerializer from news.serializers import NewsTypeSerializer from search_indexes.documents import EstablishmentDocument, NewsDocument from search_indexes.utils import get_translated_value -from timetable.serialziers import ScheduleRUDSerializer class TagsDocumentSerializer(serializers.Serializer): @@ -69,7 +68,6 @@ class EstablishmentDocumentSerializer(DocumentSerializer): address = AddressDocumentSerializer() tags = TagsDocumentSerializer(many=True) - schedule = ScheduleRUDSerializer(many=True, allow_null=True) class Meta: """Meta class.""" @@ -86,7 +84,6 @@ class EstablishmentDocumentSerializer(DocumentSerializer): 'preview_image', 'address', 'tags', - 'schedule', 'works_noon', 'works_evening', # 'collections', From c59811e0379462ae2406f9a780c0227579f093a7 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 17 Oct 2019 14:34:49 +0300 Subject: [PATCH 42/87] Revert "Revert "add schedule to search results"" This reverts commit acea297 --- apps/search_indexes/documents/establishment.py | 1 + apps/search_indexes/serializers.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index 88aaaaa5..f3f9505c 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -94,6 +94,7 @@ class EstablishmentDocument(Document): 'toque_number', 'public_mark', 'slug', + 'schedule', ) def get_queryset(self): diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index a933fd9b..d4b834ca 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -4,6 +4,7 @@ from django_elasticsearch_dsl_drf.serializers import DocumentSerializer from news.serializers import NewsTypeSerializer from search_indexes.documents import EstablishmentDocument, NewsDocument from search_indexes.utils import get_translated_value +from timetable.serialziers import ScheduleRUDSerializer class TagsDocumentSerializer(serializers.Serializer): @@ -68,6 +69,7 @@ class EstablishmentDocumentSerializer(DocumentSerializer): address = AddressDocumentSerializer() tags = TagsDocumentSerializer(many=True) + schedule = ScheduleRUDSerializer(many=True, allow_null=True) class Meta: """Meta class.""" @@ -84,6 +86,7 @@ class EstablishmentDocumentSerializer(DocumentSerializer): 'preview_image', 'address', 'tags', + 'schedule', 'works_noon', 'works_evening', # 'collections', From 97fc8dce155168929182a32739209927a9804211 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 17 Oct 2019 14:45:33 +0300 Subject: [PATCH 43/87] add schedule to search results --- apps/search_indexes/documents/establishment.py | 14 +++++++++++++- apps/search_indexes/serializers.py | 18 ++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index f3f9505c..f79b6427 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -45,6 +45,19 @@ class EstablishmentDocument(Document): properties=OBJECT_FIELD_PROPERTIES), }, multi=True) + schedule = fields.ListField(fields.ObjectField( + properties={ + 'id': fields.IntegerField(attr='id'), + 'weekday': fields.IntegerField(attr='weekday'), + 'weekday_display': fields.KeywordField(attr='weekday_display'), + 'opening_at': fields.KeywordField(attr='opening_at'), + 'closed_at': fields.KeywordField(attr='closed_at'), + 'lunch_start': fields.KeywordField(attr='lunch_start'), + 'lunch_end': fields.KeywordField(attr='lunch_end'), + 'dinner_end': fields.KeywordField(attr='dinner_end'), + 'dinner_start': fields.KeywordField(attr='dinner_start'), + } + )) address = fields.ObjectField( properties={ 'id': fields.IntegerField(), @@ -94,7 +107,6 @@ class EstablishmentDocument(Document): 'toque_number', 'public_mark', 'slug', - 'schedule', ) def get_queryset(self): diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index d4b834ca..8194504d 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -4,7 +4,6 @@ from django_elasticsearch_dsl_drf.serializers import DocumentSerializer from news.serializers import NewsTypeSerializer from search_indexes.documents import EstablishmentDocument, NewsDocument from search_indexes.utils import get_translated_value -from timetable.serialziers import ScheduleRUDSerializer class TagsDocumentSerializer(serializers.Serializer): @@ -31,6 +30,21 @@ class AddressDocumentSerializer(serializers.Serializer): geo_lat = serializers.FloatField(allow_null=True, source='coordinates.lat') +class ScheduleDocumentSerializer(serializers.Serializer): + """Schedule serializer for ES Document""" + + id = serializers.IntegerField() + weekday = serializers.IntegerField() + weekday_display = serializers.CharField() + opening_at = serializers.CharField() + closed_at = serializers.CharField() + lunch_start = serializers.CharField() + lunch_end = serializers.CharField() + dinner_end = serializers.CharField() + dinner_start = serializers.CharField() + + + class NewsDocumentSerializer(DocumentSerializer): """News document serializer.""" @@ -69,7 +83,7 @@ class EstablishmentDocumentSerializer(DocumentSerializer): address = AddressDocumentSerializer() tags = TagsDocumentSerializer(many=True) - schedule = ScheduleRUDSerializer(many=True, allow_null=True) + schedule = ScheduleDocumentSerializer(many=True, allow_null=True) class Meta: """Meta class.""" From 2c5c067b53c4afca3dfde66d7257325cb89888c8 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 17 Oct 2019 14:56:31 +0300 Subject: [PATCH 44/87] try to remove weekday_display --- apps/search_indexes/documents/establishment.py | 2 +- apps/search_indexes/serializers.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index f79b6427..69314f6c 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -49,7 +49,7 @@ class EstablishmentDocument(Document): properties={ 'id': fields.IntegerField(attr='id'), 'weekday': fields.IntegerField(attr='weekday'), - 'weekday_display': fields.KeywordField(attr='weekday_display'), + # 'weekday_display': fields.KeywordField(attr='weekday_display'), 'opening_at': fields.KeywordField(attr='opening_at'), 'closed_at': fields.KeywordField(attr='closed_at'), 'lunch_start': fields.KeywordField(attr='lunch_start'), diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 8194504d..d29c2b6b 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -35,7 +35,7 @@ class ScheduleDocumentSerializer(serializers.Serializer): id = serializers.IntegerField() weekday = serializers.IntegerField() - weekday_display = serializers.CharField() + # weekday_display = serializers.CharField() opening_at = serializers.CharField() closed_at = serializers.CharField() lunch_start = serializers.CharField() From 1998e3dc570ffbe111cd76eb8be3bb51fc7325b7 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 17 Oct 2019 14:59:35 +0300 Subject: [PATCH 45/87] Change establishment schedule ES fields type --- apps/search_indexes/documents/establishment.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index 69314f6c..1f147baa 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -49,13 +49,13 @@ class EstablishmentDocument(Document): properties={ 'id': fields.IntegerField(attr='id'), 'weekday': fields.IntegerField(attr='weekday'), - # 'weekday_display': fields.KeywordField(attr='weekday_display'), - 'opening_at': fields.KeywordField(attr='opening_at'), - 'closed_at': fields.KeywordField(attr='closed_at'), - 'lunch_start': fields.KeywordField(attr='lunch_start'), - 'lunch_end': fields.KeywordField(attr='lunch_end'), - 'dinner_end': fields.KeywordField(attr='dinner_end'), - 'dinner_start': fields.KeywordField(attr='dinner_start'), + 'weekday_display': fields.KeywordField(attr='get_weekday_display'), + 'opening_at': fields.DateField(attr='opening_at'), + 'closed_at': fields.DateField(attr='closed_at'), + 'lunch_start': fields.DateField(attr='lunch_start'), + 'lunch_end': fields.DateField(attr='lunch_end'), + 'dinner_end': fields.DateField(attr='dinner_end'), + 'dinner_start': fields.DateField(attr='dinner_start'), } )) address = fields.ObjectField( From d2e34e5d49a668b6e1886afc834bafed2f98f623 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 17 Oct 2019 15:10:26 +0300 Subject: [PATCH 46/87] Timetable search results --- apps/search_indexes/documents/establishment.py | 7 +------ apps/search_indexes/serializers.py | 8 +------- apps/timetable/models.py | 4 ++++ 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index 1f147baa..4306eed4 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -50,12 +50,7 @@ class EstablishmentDocument(Document): 'id': fields.IntegerField(attr='id'), 'weekday': fields.IntegerField(attr='weekday'), 'weekday_display': fields.KeywordField(attr='get_weekday_display'), - 'opening_at': fields.DateField(attr='opening_at'), - 'closed_at': fields.DateField(attr='closed_at'), - 'lunch_start': fields.DateField(attr='lunch_start'), - 'lunch_end': fields.DateField(attr='lunch_end'), - 'dinner_end': fields.DateField(attr='dinner_end'), - 'dinner_start': fields.DateField(attr='dinner_start'), + 'closed_at': fields.KeywordField(attr='closed_at_str'), } )) address = fields.ObjectField( diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index d29c2b6b..651205d7 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -35,14 +35,8 @@ class ScheduleDocumentSerializer(serializers.Serializer): id = serializers.IntegerField() weekday = serializers.IntegerField() - # weekday_display = serializers.CharField() - opening_at = serializers.CharField() + weekday_display = serializers.CharField() closed_at = serializers.CharField() - lunch_start = serializers.CharField() - lunch_end = serializers.CharField() - dinner_end = serializers.CharField() - dinner_start = serializers.CharField() - class NewsDocumentSerializer(DocumentSerializer): diff --git a/apps/timetable/models.py b/apps/timetable/models.py index caa6e6ac..35469c32 100644 --- a/apps/timetable/models.py +++ b/apps/timetable/models.py @@ -35,6 +35,10 @@ class Timetable(ProjectBaseMixin): opening_at = models.TimeField(verbose_name=_('Opening time'), null=True) closed_at = models.TimeField(verbose_name=_('Closed time'), null=True) + @property + def closed_at_str(self): + return str(self.closed_at) if self.closed_at else None + @property def works_at_noon(self): return bool(self.closed_at and self.closed_at <= self.NOON) From cce0913245cf559e1625903001d49b6e5ef04660 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Thu, 17 Oct 2019 18:37:48 +0300 Subject: [PATCH 47/87] change AWS_S3_CUSTOM_DOMAIN --- celerybeat-schedule | Bin 12845 -> 12845 bytes project/settings/amazon_s3.py | 5 ++++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/celerybeat-schedule b/celerybeat-schedule index 3efe528a0f0c9bb6e563876fdc4c1a02541e62f7..e1a56a152b845b38540fb26180e00295cb3cb2b1 100644 GIT binary patch delta 101 zcmZ3RvNmOb0we2WMg?(6DFz7OHGt6Pp!~?miVEtRBNTS9@Y+D7xPekan`Kqyxn<+y k%TkMqGxPJ};~SVGIDir+dd7MNMaf2m40@X*4X!W(0FSs74gdfE delta 76 zcmZ3RvNmOb0wc?0Mg?)s*9;I~Guco Date: Thu, 17 Oct 2019 23:25:39 +0300 Subject: [PATCH 48/87] add preview image url --- apps/search_indexes/documents/news.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/search_indexes/documents/news.py b/apps/search_indexes/documents/news.py index cd6fc089..dee4a1c9 100644 --- a/apps/search_indexes/documents/news.py +++ b/apps/search_indexes/documents/news.py @@ -24,6 +24,7 @@ class NewsDocument(Document): country = fields.ObjectField(properties={'id': fields.IntegerField(), 'code': fields.KeywordField()}) web_url = fields.KeywordField(attr='web_url') + preview_image_url = fields.TextField(attr='preview_image_field') tags = fields.ObjectField( properties={ 'id': fields.IntegerField(attr='id'), From 4b5b34de205cb1ac5a57c0217cc20d5bfb23df61 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 17 Oct 2019 23:28:13 +0300 Subject: [PATCH 49/87] add preview image url (fix typo) --- apps/search_indexes/documents/news.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/search_indexes/documents/news.py b/apps/search_indexes/documents/news.py index dee4a1c9..21c59e68 100644 --- a/apps/search_indexes/documents/news.py +++ b/apps/search_indexes/documents/news.py @@ -24,7 +24,7 @@ class NewsDocument(Document): country = fields.ObjectField(properties={'id': fields.IntegerField(), 'code': fields.KeywordField()}) web_url = fields.KeywordField(attr='web_url') - preview_image_url = fields.TextField(attr='preview_image_field') + preview_image_url = fields.TextField(attr='preview_image_url') tags = fields.ObjectField( properties={ 'id': fields.IntegerField(attr='id'), From b77c654c6ca71e215fcf9362c773fc3229794ea8 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 18 Oct 2019 02:38:59 +0300 Subject: [PATCH 50/87] Open now filter --- apps/establishment/models.py | 13 +++++++++++++ apps/location/models.py | 9 ++++++++- apps/search_indexes/documents/establishment.py | 1 + apps/search_indexes/serializers.py | 1 + apps/search_indexes/views.py | 9 ++++++++- project/settings/base.py | 1 + requirements/base.txt | 1 + 7 files changed, 33 insertions(+), 2 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 5d50284c..3f61dbe9 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -1,4 +1,5 @@ """Establishment models.""" +from datetime import datetime from functools import reduce import elasticsearch_dsl @@ -13,6 +14,7 @@ from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q from django.utils import timezone from django.utils.translation import gettext_lazy as _ from phonenumber_field.modelfields import PhoneNumberField +from pytz import timezone as py_tz from collection.models import Collection from location.models import Address @@ -378,6 +380,17 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): """ Used for indexing working by day """ return [ret.weekday for ret in self.schedule.all() if ret.works_at_afternoon] + # @property + def works_now(self): + """ Is establishment working now """ + now_at_est_tz = datetime.now(tz=py_tz(self.address.tz_name)) + current_week = now_at_est_tz.weekday() + schedule_for_today = self.schedule.filter(weekday=current_week).first() + if schedule_for_today is None: + return False + time_at_est_tz = now_at_est_tz.time() + return schedule_for_today.closed_at > time_at_est_tz > schedule_for_today.opening_at + @property def tags_indexing(self): return [{'id': tag.metadata.id, diff --git a/apps/location/models.py b/apps/location/models.py index 2298c28e..e05f5b26 100644 --- a/apps/location/models.py +++ b/apps/location/models.py @@ -5,8 +5,10 @@ from django.db.models.signals import post_save from django.db.transaction import on_commit from django.dispatch import receiver from django.utils.translation import gettext_lazy as _ -from utils.models import ProjectBaseMixin, SVGImageMixin, TranslatedFieldsMixin, TJSONField + +from timezonefinder import TimezoneFinder from translation.models import Language +from utils.models import ProjectBaseMixin, SVGImageMixin, TranslatedFieldsMixin, TJSONField class Country(TranslatedFieldsMixin, SVGImageMixin, ProjectBaseMixin): @@ -99,6 +101,11 @@ class Address(models.Model): def get_street_name(self): return self.street_name_1 or self.street_name_2 + @property + def tz_name(self): + tf = TimezoneFinder(in_memory=True) + return tf.certain_timezone_at(lng=self.latitude, lat=self.longitude) + @property def latitude(self): return self.coordinates.y if self.coordinates else float(0) diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index 4306eed4..dabc98bb 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -38,6 +38,7 @@ class EstablishmentDocument(Document): works_noon = fields.ListField(fields.IntegerField( attr='works_noon' )) + works_now = fields.BooleanField() tags = fields.ObjectField( properties={ 'id': fields.IntegerField(attr='id'), diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 651205d7..535974a9 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -97,6 +97,7 @@ class EstablishmentDocumentSerializer(DocumentSerializer): 'schedule', 'works_noon', 'works_evening', + 'works_now', # 'collections', # 'establishment_type', # 'establishment_subtypes', diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index 0a3afe8e..913c2d95 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -133,9 +133,16 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): 'works_evening': { 'field': 'works_evening', 'lookups': [ - constants.LOOKUP_QUERY_IN, + constants.LOOKUP_FILTER_TERM, ], }, + 'works_now': { + 'field': 'works_now', + 'lookups': [ + constants.LOOKUP_FILTER_EXISTS, + constants.LOOKUP_QUERY_IN, + ] + }, } geo_spatial_filter_fields = { diff --git a/project/settings/base.py b/project/settings/base.py index a64bedb9..95babea4 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -96,6 +96,7 @@ EXTERNAL_APPS = [ 'phonenumber_field', 'storages', 'sorl.thumbnail', + 'timezonefinder' ] diff --git a/requirements/base.txt b/requirements/base.txt index 856c70f3..dbb3b20e 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -9,6 +9,7 @@ fcm-django django-easy-select2 bootstrap-admin drf-yasg==1.16.0 +timezonefinder PySocks!=1.5.7,>=1.5.6; djangorestframework==3.9.4 From 3bc9dd73476ae121c55455f37b0a9a383def1c99 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Fri, 18 Oct 2019 02:56:49 +0300 Subject: [PATCH 51/87] Open now filter (typo fix) --- apps/search_indexes/documents/establishment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index dabc98bb..5d858321 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -38,7 +38,7 @@ class EstablishmentDocument(Document): works_noon = fields.ListField(fields.IntegerField( attr='works_noon' )) - works_now = fields.BooleanField() + works_now = fields.BooleanField(attr='works_now') tags = fields.ObjectField( properties={ 'id': fields.IntegerField(attr='id'), From f7facf826df3f01cceaed754f9486a0e17d96581 Mon Sep 17 00:00:00 2001 From: Semyon Date: Fri, 18 Oct 2019 18:37:24 +0300 Subject: [PATCH 52/87] Implemented chosen tags by adding priority field: chosen tags should have non-null priority field, other tags should have null priority field. --- apps/tag/migrations/0003_tag_priority.py | 18 ++++++++++++++++++ apps/tag/models.py | 2 ++ 2 files changed, 20 insertions(+) create mode 100644 apps/tag/migrations/0003_tag_priority.py diff --git a/apps/tag/migrations/0003_tag_priority.py b/apps/tag/migrations/0003_tag_priority.py new file mode 100644 index 00000000..5e93bcaa --- /dev/null +++ b/apps/tag/migrations/0003_tag_priority.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-10-18 15:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tag', '0002_auto_20191009_1408'), + ] + + operations = [ + migrations.AddField( + model_name='tag', + name='priority', + field=models.IntegerField(default=None, null=True, unique=True), + ), + ] diff --git a/apps/tag/models.py b/apps/tag/models.py index b811796c..0e5f9859 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -14,6 +14,8 @@ class Tag(TranslatedFieldsMixin, models.Model): category = models.ForeignKey('TagCategory', on_delete=models.PROTECT, null=True, related_name='tags', verbose_name=_('Category')) + # chosen tags should have non-null priority,other tags priority should be null + priority = models.IntegerField(unique=True, null=True, default=None) class Meta: """Meta class.""" From 71ec593ecf26db24722a06f865727bd2e1d20c76 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 21 Oct 2019 14:09:08 +0300 Subject: [PATCH 53/87] News email new layout --- project/templates/news/news_email.html | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/project/templates/news/news_email.html b/project/templates/news/news_email.html index d14bd898..6fe14060 100644 --- a/project/templates/news/news_email.html +++ b/project/templates/news/news_email.html @@ -12,7 +12,6 @@ -
@@ -25,7 +24,7 @@
-
+
{{ title }}
{% if not image_url is None %} @@ -33,11 +32,13 @@
{% endif %} -
+
{{ description | safe }}
- +
+ Go to news +
- +
- + \ No newline at end of file From cc5249674d4a3c0696f258aad5e3b07651b54c47 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 21 Oct 2019 14:39:23 +0300 Subject: [PATCH 54/87] merge migrations --- apps/tag/migrations/0004_merge_20191021_1138.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 apps/tag/migrations/0004_merge_20191021_1138.py diff --git a/apps/tag/migrations/0004_merge_20191021_1138.py b/apps/tag/migrations/0004_merge_20191021_1138.py new file mode 100644 index 00000000..6298ce4f --- /dev/null +++ b/apps/tag/migrations/0004_merge_20191021_1138.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.4 on 2019-10-21 11:38 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('tag', '0003_auto_20191018_0758'), + ('tag', '0003_tag_priority'), + ] + + operations = [ + ] From f96a1d4aa24309b97b6be5d67f711ded310fc663 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Mon, 21 Oct 2019 16:42:08 +0300 Subject: [PATCH 55/87] Revert "Implemented chosen tags by adding priority field: chosen tags should have non-null priority field, other tags should have null priority field." This reverts commit f7facf826df3f01cceaed754f9486a0e17d96581. --- apps/tag/migrations/0003_tag_priority.py | 18 ------------------ apps/tag/models.py | 2 -- 2 files changed, 20 deletions(-) delete mode 100644 apps/tag/migrations/0003_tag_priority.py diff --git a/apps/tag/migrations/0003_tag_priority.py b/apps/tag/migrations/0003_tag_priority.py deleted file mode 100644 index 5e93bcaa..00000000 --- a/apps/tag/migrations/0003_tag_priority.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.4 on 2019-10-18 15:25 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('tag', '0002_auto_20191009_1408'), - ] - - operations = [ - migrations.AddField( - model_name='tag', - name='priority', - field=models.IntegerField(default=None, null=True, unique=True), - ), - ] diff --git a/apps/tag/models.py b/apps/tag/models.py index ff4afee1..6e83ca12 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -14,8 +14,6 @@ class Tag(TranslatedFieldsMixin, models.Model): category = models.ForeignKey('TagCategory', on_delete=models.CASCADE, null=True, related_name='tags', verbose_name=_('Category')) - # chosen tags should have non-null priority,other tags priority should be null - priority = models.IntegerField(unique=True, null=True, default=None) class Meta: """Meta class.""" From 55263e4059b7e9126179d77210867a3f32dcf1c9 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Mon, 21 Oct 2019 16:51:01 +0300 Subject: [PATCH 56/87] remove merge migration --- apps/tag/migrations/0004_merge_20191021_1138.py | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 apps/tag/migrations/0004_merge_20191021_1138.py diff --git a/apps/tag/migrations/0004_merge_20191021_1138.py b/apps/tag/migrations/0004_merge_20191021_1138.py deleted file mode 100644 index 6298ce4f..00000000 --- a/apps/tag/migrations/0004_merge_20191021_1138.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 2.2.4 on 2019-10-21 11:38 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('tag', '0003_auto_20191018_0758'), - ('tag', '0003_tag_priority'), - ] - - operations = [ - ] From 4c9ea2e0e7a7187c0b0b5f36cc319f0aab9b03c2 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Tue, 22 Oct 2019 15:45:27 +0300 Subject: [PATCH 57/87] change field properties for Collection model --- .../migrations/0014_auto_20191022_1242.py | 23 +++++++++++++++++++ apps/collection/models.py | 9 ++++---- 2 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 apps/collection/migrations/0014_auto_20191022_1242.py diff --git a/apps/collection/migrations/0014_auto_20191022_1242.py b/apps/collection/migrations/0014_auto_20191022_1242.py new file mode 100644 index 00000000..d70c0cfa --- /dev/null +++ b/apps/collection/migrations/0014_auto_20191022_1242.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.4 on 2019-10-22 12:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('collection', '0013_collection_slug'), + ] + + operations = [ + migrations.AlterField( + model_name='collection', + name='end', + field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='end'), + ), + migrations.AlterField( + model_name='guide', + name='end', + field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='end'), + ), + ] diff --git a/apps/collection/models.py b/apps/collection/models.py index 25bf1ef9..e7c930c3 100644 --- a/apps/collection/models.py +++ b/apps/collection/models.py @@ -1,13 +1,11 @@ -from django.contrib.postgres.fields import JSONField from django.contrib.contenttypes.fields import ContentType - -from utils.models import TJSONField +from django.contrib.postgres.fields import JSONField from django.db import models from django.utils.translation import gettext_lazy as _ from utils.models import ProjectBaseMixin, URLImageMixin +from utils.models import TJSONField from utils.models import TranslatedFieldsMixin - from utils.querysets import RelatedObjectsCountMixin @@ -24,7 +22,8 @@ class CollectionNameMixin(models.Model): class CollectionDateMixin(models.Model): """CollectionDate mixin""" start = models.DateTimeField(_('start')) - end = models.DateTimeField(_('end')) + end = models.DateTimeField(blank=True, null=True, default=None, + verbose_name=_('end')) class Meta: """Meta class""" From 071b63465942391e02ddfcba114101a3dc80d33a Mon Sep 17 00:00:00 2001 From: Anatoly Date: Tue, 22 Oct 2019 15:58:00 +0300 Subject: [PATCH 58/87] fixed EstablishmentFilter --- apps/establishment/filters.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/apps/establishment/filters.py b/apps/establishment/filters.py index 20ece644..91670031 100644 --- a/apps/establishment/filters.py +++ b/apps/establishment/filters.py @@ -1,6 +1,7 @@ """Establishment app filters.""" from django.core.validators import EMPTY_VALUES from django_filters import rest_framework as filters + from establishment import models @@ -10,10 +11,10 @@ class EstablishmentFilter(filters.FilterSet): tag_id = filters.NumberFilter(field_name='tags__metadata__id',) award_id = filters.NumberFilter(field_name='awards__id',) search = filters.CharFilter(method='search_text') - est_type = filters.ChoiceFilter(choices=models.EstablishmentType.INDEX_NAME_TYPES, - method='by_type') - est_subtype = filters.ChoiceFilter(choices=models.EstablishmentSubType.INDEX_NAME_TYPES, - method='by_subtype') + type = filters.ChoiceFilter(choices=models.EstablishmentType.INDEX_NAME_TYPES, + method='by_type') + subtype = filters.ChoiceFilter(choices=models.EstablishmentSubType.INDEX_NAME_TYPES, + method='by_subtype') class Meta: """Meta class.""" @@ -23,8 +24,8 @@ class EstablishmentFilter(filters.FilterSet): 'tag_id', 'award_id', 'search', - 'est_type', - 'est_subtype', + 'type', + 'subtype', ) def search_text(self, queryset, name, value): @@ -34,10 +35,14 @@ class EstablishmentFilter(filters.FilterSet): return queryset def by_type(self, queryset, name, value): - return queryset.by_type(value) + if value not in EMPTY_VALUES: + return queryset.by_type(value) + return queryset def by_subtype(self, queryset, name, value): - return queryset.by_subtype(value) + if value not in EMPTY_VALUES: + return queryset.by_subtype(value) + return queryset class EstablishmentTypeTagFilter(filters.FilterSet): From 939e4a26a09bd397b7ba32d172b9781e68ea250a Mon Sep 17 00:00:00 2001 From: Semyon Yekhmenin Date: Tue, 22 Oct 2019 13:00:22 +0000 Subject: [PATCH 59/87] Feature/cities by country --- apps/location/models.py | 11 ++++++++++- apps/location/urls/mobile.py | 3 +-- apps/location/views/common.py | 16 ++++++++++------ project/urls/mobile.py | 3 ++- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/apps/location/models.py b/apps/location/models.py index 2b7aa363..c12f7ff0 100644 --- a/apps/location/models.py +++ b/apps/location/models.py @@ -53,6 +53,14 @@ class Region(models.Model): return self.name +class CityQuerySet(models.QuerySet): + """Extended queryset for City model.""" + + def by_country_code(self, code): + """Return establishments by country code""" + return self.filter(country__code=code) + + class City(models.Model): """Region model.""" @@ -68,6 +76,8 @@ class City(models.Model): is_island = models.BooleanField(_('is island'), default=False) + objects = CityQuerySet.as_manager() + class Meta: verbose_name_plural = _('cities') verbose_name = _('city') @@ -77,7 +87,6 @@ class City(models.Model): class Address(models.Model): - """Address model.""" city = models.ForeignKey(City, verbose_name=_('city'), on_delete=models.CASCADE) street_name_1 = models.CharField( diff --git a/apps/location/urls/mobile.py b/apps/location/urls/mobile.py index b808d5b6..879ded79 100644 --- a/apps/location/urls/mobile.py +++ b/apps/location/urls/mobile.py @@ -1,7 +1,6 @@ """Location app mobile urlconf.""" from location.urls.common import urlpatterns as common_urlpatterns - urlpatterns = [] -urlpatterns.extend(common_urlpatterns) \ No newline at end of file +urlpatterns.extend(common_urlpatterns) diff --git a/apps/location/views/common.py b/apps/location/views/common.py index 792fce91..b4a3f1cb 100644 --- a/apps/location/views/common.py +++ b/apps/location/views/common.py @@ -10,7 +10,7 @@ class CountryViewMixin(generics.GenericAPIView): """View Mixin for model Country""" serializer_class = serializers.CountrySerializer - permission_classes = (permissions.AllowAny, ) + permission_classes = (permissions.AllowAny,) queryset = models.Country.objects.all() @@ -56,7 +56,7 @@ class RegionRetrieveView(RegionViewMixin, generics.RetrieveAPIView): class RegionListView(RegionViewMixin, generics.ListAPIView): """List view for model Country""" - permission_classes = (permissions.AllowAny, ) + permission_classes = (permissions.AllowAny,) serializer_class = serializers.CountrySerializer @@ -83,9 +83,15 @@ class CityRetrieveView(CityViewMixin, generics.RetrieveAPIView): class CityListView(CityViewMixin, generics.ListAPIView): """List view for model City""" - permission_classes = (permissions.AllowAny, ) + permission_classes = (permissions.AllowAny,) serializer_class = serializers.CitySerializer + def get_queryset(self): + qs = super().get_queryset() + if self.request.country_code: + qs = qs.by_country_code(self.request.country_code) + return qs + class CityDestroyView(CityViewMixin, generics.DestroyAPIView): """Destroy view for model City""" @@ -110,7 +116,5 @@ class AddressRetrieveView(AddressViewMixin, generics.RetrieveAPIView): class AddressListView(AddressViewMixin, generics.ListAPIView): """List view for model Address""" - permission_classes = (permissions.AllowAny, ) + permission_classes = (permissions.AllowAny,) serializer_class = serializers.AddressDetailSerializer - - diff --git a/project/urls/mobile.py b/project/urls/mobile.py index 0bea5305..77d007dc 100644 --- a/project/urls/mobile.py +++ b/project/urls/mobile.py @@ -4,7 +4,8 @@ app_name = 'mobile' urlpatterns = [ path('establishments/', include('establishment.urls.mobile')), - path('main/', include('main.urls.mobile')) + path('main/', include('main.urls.mobile')), + path('location/', include('location.urls.mobile')) # path('account/', include('account.urls.web')), # path('advertisement/', include('advertisement.urls.web')), # path('collection/', include('collection.urls.web')), From d1c9e649b7baf5b21e2f1f27b469b5ae978356b1 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Tue, 22 Oct 2019 17:05:54 +0300 Subject: [PATCH 60/87] Remove metadatacontent --- .../migrations/0040_employee_tags.py | 19 ++++++++ apps/establishment/models.py | 11 +---- apps/establishment/views/web.py | 2 +- .../migrations/0019_auto_20191022_1359.py | 38 ++++++++++++++++ apps/main/models.py | 43 ------------------- apps/main/serializers.py | 13 ------ apps/news/models.py | 2 - 7 files changed, 60 insertions(+), 68 deletions(-) create mode 100644 apps/establishment/migrations/0040_employee_tags.py create mode 100644 apps/main/migrations/0019_auto_20191022_1359.py diff --git a/apps/establishment/migrations/0040_employee_tags.py b/apps/establishment/migrations/0040_employee_tags.py new file mode 100644 index 00000000..9f9405f5 --- /dev/null +++ b/apps/establishment/migrations/0040_employee_tags.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.4 on 2019-10-22 13:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tag', '0004_tag_priority'), + ('establishment', '0039_establishmentsubtype_index_name'), + ] + + operations = [ + migrations.AddField( + model_name='employee', + name='tags', + field=models.ManyToManyField(related_name='employees', to='tag.Tag', verbose_name='Tags'), + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 4f7a73a5..68acb68f 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -353,11 +353,8 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): verbose_name=_('Establishment slug'), editable=True) awards = generic.GenericRelation(to='main.Award', related_query_name='establishment') - # todo: remove after data merge - # tags = generic.GenericRelation(to='main.MetaDataContent') tags = models.ManyToManyField('tag.Tag', related_name='establishments', verbose_name=_('Tag')) - old_tags = generic.GenericRelation(to='main.MetaDataContent') reviews = generic.GenericRelation(to='review.Review') comments = generic.GenericRelation(to='comment.Comment') favorites = generic.GenericRelation(to='favorites.Favorites') @@ -419,11 +416,6 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): def best_price_carte(self): return 200 - @property - def tags_indexing(self): - return [{'id': tag.metadata.id, - 'label': tag.metadata.label} for tag in self.tags.all()] - @property def last_published_review(self): """Return last published review""" @@ -511,7 +503,8 @@ class Employee(BaseAttributes): establishments = models.ManyToManyField(Establishment, related_name='employees', through=EstablishmentEmployee,) awards = generic.GenericRelation(to='main.Award', related_query_name='employees') - tags = generic.GenericRelation(to='main.MetaDataContent') + tags = models.ManyToManyField('tag.Tag', related_name='employees', + verbose_name=_('Tags')) class Meta: """Meta class.""" diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index 8ee9ddf7..d3cc91cb 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -8,10 +8,10 @@ from comment import models as comment_models from establishment import filters from establishment import models, serializers from main import methods -from main.models import MetaDataContent from utils.pagination import EstablishmentPortionPagination from utils.permissions import IsCountryAdmin + class EstablishmentMixinView: """Establishment mixin.""" diff --git a/apps/main/migrations/0019_auto_20191022_1359.py b/apps/main/migrations/0019_auto_20191022_1359.py new file mode 100644 index 00000000..127ac55d --- /dev/null +++ b/apps/main/migrations/0019_auto_20191022_1359.py @@ -0,0 +1,38 @@ +# Generated by Django 2.2.4 on 2019-10-22 13:59 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0018_feature_source'), + ] + + operations = [ + migrations.RemoveField( + model_name='metadatacategory', + name='content_type', + ), + migrations.RemoveField( + model_name='metadatacategory', + name='country', + ), + migrations.RemoveField( + model_name='metadatacontent', + name='content_type', + ), + migrations.RemoveField( + model_name='metadatacontent', + name='metadata', + ), + migrations.DeleteModel( + name='MetaData', + ), + migrations.DeleteModel( + name='MetaDataCategory', + ), + migrations.DeleteModel( + name='MetaDataContent', + ), + ] diff --git a/apps/main/models.py b/apps/main/models.py index 46598257..038da470 100644 --- a/apps/main/models.py +++ b/apps/main/models.py @@ -256,49 +256,6 @@ class AwardType(models.Model): return self.name -class MetaDataCategory(models.Model): - """MetaData category model.""" - - country = models.ForeignKey( - 'location.Country', null=True, default=None, on_delete=models.CASCADE) - content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) - public = models.BooleanField() - - -class MetaData(TranslatedFieldsMixin, models.Model): - """MetaData model.""" - label = TJSONField( - _('label'), null=True, blank=True, - default=None, help_text='{"en-GB":"some text"}') - category = models.ForeignKey( - MetaDataCategory, verbose_name=_('category'), on_delete=models.CASCADE) - - class Meta: - verbose_name = _('metadata') - verbose_name_plural = _('metadata') - - def __str__(self): - label = 'None' - lang = TranslationSettings.get_solo().default_language - if self.label and lang in self.label: - label = self.label[lang] - return f'id:{self.id}-{label}' - - -class MetaDataContentQuerySet(ContentTypeQuerySetMixin): - """QuerySets for MetaDataContent model.""" - - -class MetaDataContent(models.Model): - """MetaDataContent model.""" - content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) - object_id = models.PositiveIntegerField() - content_object = generic.GenericForeignKey('content_type', 'object_id') - metadata = models.ForeignKey(MetaData, on_delete=models.CASCADE) - - objects = MetaDataContentQuerySet.as_manager() - - class Currency(models.Model): """Currency model.""" name = models.CharField(_('name'), max_length=50) diff --git a/apps/main/serializers.py b/apps/main/serializers.py index d425016d..67693d71 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -113,19 +113,6 @@ class AwardSerializer(AwardBaseSerializer): fields = AwardBaseSerializer.Meta.fields + ['award_type', ] -class MetaDataContentSerializer(serializers.ModelSerializer): - """MetaData content serializer.""" - - id = serializers.IntegerField(source='metadata.id', read_only=True) - label_translated = TranslatedField(source='metadata.label_translated') - - class Meta: - """Meta class.""" - - model = models.MetaDataContent - fields = ('id', 'label_translated') - - class CurrencySerializer(serializers.ModelSerializer): """Currency serializer""" diff --git a/apps/news/models.py b/apps/news/models.py index 0460174a..c86187d5 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -146,8 +146,6 @@ class News(BaseAttributes, TranslatedFieldsMixin): verbose_name=_('State')) is_highlighted = models.BooleanField(default=False, 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, From 3d68f1ba297b195a8864990bbac31cf32f48f4a3 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Tue, 22 Oct 2019 18:20:01 +0300 Subject: [PATCH 61/87] added imgae_url field to model Award (gm-183) --- apps/main/migrations/0019_award_image_url.py | 18 ++++++++++++++++++ apps/main/models.py | 5 +++-- apps/main/serializers.py | 3 ++- 3 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 apps/main/migrations/0019_award_image_url.py diff --git a/apps/main/migrations/0019_award_image_url.py b/apps/main/migrations/0019_award_image_url.py new file mode 100644 index 00000000..9f5a73ed --- /dev/null +++ b/apps/main/migrations/0019_award_image_url.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-10-22 14:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0018_feature_source'), + ] + + operations = [ + migrations.AddField( + model_name='award', + name='image_url', + field=models.URLField(blank=True, default=None, null=True, verbose_name='Image URL path'), + ), + ] diff --git a/apps/main/models.py b/apps/main/models.py index 038da470..1bb6ace3 100644 --- a/apps/main/models.py +++ b/apps/main/models.py @@ -15,7 +15,8 @@ from location.models import Country from main import methods from review.models import Review from utils.models import (ProjectBaseMixin, TJSONField, - TranslatedFieldsMixin, ImageMixin, PlatformMixin) + TranslatedFieldsMixin, ImageMixin, + PlatformMixin, URLImageMixin) from utils.querysets import ContentTypeQuerySetMixin @@ -226,7 +227,7 @@ class SiteFeature(ProjectBaseMixin): unique_together = ('site_settings', 'feature') -class Award(TranslatedFieldsMixin, models.Model): +class Award(TranslatedFieldsMixin, URLImageMixin, models.Model): """Award model.""" award_type = models.ForeignKey('main.AwardType', on_delete=models.CASCADE) title = TJSONField( diff --git a/apps/main/serializers.py b/apps/main/serializers.py index 67693d71..e2523fa9 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -1,9 +1,9 @@ """Main app serializers.""" from rest_framework import serializers + from advertisement.serializers.web import AdvertisementSerializer from location.serializers import CountrySerializer from main import models -from establishment.models import Establishment from utils.serializers import TranslatedField @@ -102,6 +102,7 @@ class AwardBaseSerializer(serializers.ModelSerializer): 'id', 'title_translated', 'vintage_year', + 'image_url', ] From 4a0a305e2cbedb234646b9ea7a805a1f7349a28e Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 14:11:07 +0300 Subject: [PATCH 62/87] tag category indexing name --- .../0005_tagcategory_name_indexing.py | 18 ++++++++++++++++++ apps/tag/models.py | 2 ++ apps/tag/serializers.py | 3 ++- 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 apps/tag/migrations/0005_tagcategory_name_indexing.py diff --git a/apps/tag/migrations/0005_tagcategory_name_indexing.py b/apps/tag/migrations/0005_tagcategory_name_indexing.py new file mode 100644 index 00000000..839b5747 --- /dev/null +++ b/apps/tag/migrations/0005_tagcategory_name_indexing.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-10-22 15:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tag', '0004_tag_priority'), + ] + + operations = [ + migrations.AddField( + model_name='tagcategory', + name='name_indexing', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='indexing name'), + ), + ] diff --git a/apps/tag/models.py b/apps/tag/models.py index 44eacddc..dbacbd2c 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -69,6 +69,8 @@ class TagCategory(TranslatedFieldsMixin, models.Model): on_delete=models.SET_NULL, null=True, default=None) public = models.BooleanField(default=False) + name_indexing = models.CharField(max_length=255, blank=True, null=True, + verbose_name=_('indexing name')) objects = TagCategoryQuerySet.as_manager() diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py index 6ee55c84..445042c7 100644 --- a/apps/tag/serializers.py +++ b/apps/tag/serializers.py @@ -49,7 +49,8 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer): fields = ( 'id', 'label_translated', - 'tags' + 'name_indexing', + 'tags', ) From 97823ea6012cb660495c34d998e7b881add06417 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 23 Oct 2019 11:25:04 +0300 Subject: [PATCH 63/87] change title field in model Collection to translated JSON field (gm-165) --- .../migrations/0015_auto_20191023_0715.py | 44 +++++++++++++++++++ apps/collection/models.py | 10 ++--- apps/collection/serializers/common.py | 10 ++--- .../migrations/0020_merge_20191023_0750.py | 14 ++++++ 4 files changed, 68 insertions(+), 10 deletions(-) create mode 100644 apps/collection/migrations/0015_auto_20191023_0715.py create mode 100644 apps/main/migrations/0020_merge_20191023_0750.py diff --git a/apps/collection/migrations/0015_auto_20191023_0715.py b/apps/collection/migrations/0015_auto_20191023_0715.py new file mode 100644 index 00000000..53bfdc2d --- /dev/null +++ b/apps/collection/migrations/0015_auto_20191023_0715.py @@ -0,0 +1,44 @@ +# Generated by Django 2.2.4 on 2019-10-23 07:15 + +from django.db import migrations + +import utils.models + + +def fill_title_json_from_title(apps, schema_editor): + # We can't import the Person model directly as it may be a newer + # version than this migration expects. We use the historical version. + Collection = apps.get_model('collection', 'Collection') + for collection in Collection.objects.all(): + collection.name_json = {'en-GB': collection.name} + collection.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('collection', '0014_auto_20191022_1242'), + ] + + operations = [ + migrations.AddField( + model_name='collection', + name='name_json', + field=utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='name'), + ), + migrations.RunPython(fill_title_json_from_title, migrations.RunPython.noop), + migrations.RemoveField( + model_name='collection', + name='name', + ), + migrations.RenameField( + model_name='collection', + old_name='name_json', + new_name='name', + ), + migrations.AlterField( + model_name='collection', + name='name', + field=utils.models.TJSONField(help_text='{"en-GB":"some text"}', verbose_name='name'), + ), + ] diff --git a/apps/collection/models.py b/apps/collection/models.py index e7c930c3..0a2700bd 100644 --- a/apps/collection/models.py +++ b/apps/collection/models.py @@ -43,9 +43,11 @@ class CollectionQuerySet(RelatedObjectsCountMixin): return self.filter(is_publish=True) -class Collection(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin, +class Collection(ProjectBaseMixin, CollectionDateMixin, TranslatedFieldsMixin, URLImageMixin): """Collection model.""" + STR_FIELD_NAME = 'name' + ORDINARY = 0 # Ordinary collection POP = 1 # POP collection @@ -54,6 +56,8 @@ class Collection(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin, (POP, _('Pop')), ) + name = TJSONField(verbose_name=_('name'), + help_text='{"en-GB":"some text"}') collection_type = models.PositiveSmallIntegerField(choices=COLLECTION_TYPES, default=ORDINARY, verbose_name=_('Collection type')) @@ -79,10 +83,6 @@ class Collection(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin, verbose_name = _('collection') verbose_name_plural = _('collections') - def __str__(self): - """String method.""" - return f'{self.name}' - class GuideQuerySet(models.QuerySet): """QuerySet for Guide.""" diff --git a/apps/collection/serializers/common.py b/apps/collection/serializers/common.py index 78612a55..846236d5 100644 --- a/apps/collection/serializers/common.py +++ b/apps/collection/serializers/common.py @@ -2,18 +2,19 @@ from rest_framework import serializers from collection import models from location import models as location_models +from utils.serializers import TranslatedField class CollectionBaseSerializer(serializers.ModelSerializer): """Collection base serializer""" - # RESPONSE - description_translated = serializers.CharField(read_only=True, allow_null=True) + name_translated = TranslatedField() + description_translated = TranslatedField() class Meta: model = models.Collection fields = [ 'id', - 'name', + 'name_translated', 'description_translated', 'image_url', 'slug', @@ -35,8 +36,7 @@ class CollectionSerializer(CollectionBaseSerializer): queryset=location_models.Country.objects.all(), write_only=True) - class Meta: - model = models.Collection + class Meta(CollectionBaseSerializer.Meta): fields = CollectionBaseSerializer.Meta.fields + [ 'start', 'end', diff --git a/apps/main/migrations/0020_merge_20191023_0750.py b/apps/main/migrations/0020_merge_20191023_0750.py new file mode 100644 index 00000000..eac41bfc --- /dev/null +++ b/apps/main/migrations/0020_merge_20191023_0750.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.4 on 2019-10-23 07:50 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0019_award_image_url'), + ('main', '0019_auto_20191022_1359'), + ] + + operations = [ + ] From 766b6659f1b9a02f8cb232372d6d3f57a6cb2e69 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Wed, 23 Oct 2019 11:31:45 +0300 Subject: [PATCH 64/87] Models created: Product, OnlineProduct (proxy), ProductType, ProductSubtype --- apps/product/models.py | 173 +++++++++++++++++++++++++-------------- project/settings/base.py | 2 +- 2 files changed, 111 insertions(+), 64 deletions(-) diff --git a/apps/product/models.py b/apps/product/models.py index 3c0e6ee8..41f0c7c6 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -1,63 +1,110 @@ -# from django.contrib.postgres.fields import JSONField -# from django.db import models -# from django.utils.translation import gettext_lazy as _ -# -# from utils.models import BaseAttributes -# -# -# class ProductManager(models.Manager): -# """Product manager.""" -# -# -# class ProductQuerySet(models.QuerySet): -# """Product queryset.""" -# -# -# class Product(BaseAttributes): -# """Product models.""" -# name = models.CharField(_('name'), max_length=255) -# country = models.ForeignKey('location.Country', on_delete=models.CASCADE) -# region = models.ForeignKey('location.Region', on_delete=models.CASCADE) -# # ASK: What is the "subregion" -# -# description = JSONField(_('description')) -# characteristics = JSONField(_('characteristics')) -# metadata_values = JSONField(_('metadata_values')) -# # common_relations_id -# # product_region_id -# code = models.CharField(_('code'), max_length=255) -# available = models.BooleanField(_('available')) -# -# # dealer_type -# # target_scope -# # target_type -# # rank -# # excluding_tax_unit_price -# # column_21 -# # currencies_id -# # vintage -# # producer_price -# # producer_description -# # annual_produced_quantity -# # production_method_description -# # unit_name -# # unit -# # unit_values -# # organic_source -# # certificates -# # establishments_id -# # restrictions -# # -# objects = ProductManager.from_queryset(ProductQuerySet)() -# -# class Meta: -# verbose_name = _('product') -# verbose_name_plural = _('products') -# -# -# class ProductType(models.Model): -# """ProductType model.""" -# -# class Meta: -# verbose_name_plural = _('product types') -# verbose_name = _('product type') +"""Product app models.""" +from django.db import models +from django.contrib.postgres.fields import JSONField +from django.utils.translation import gettext_lazy as _ +from utils.models import (BaseAttributes, ProjectBaseMixin, + TranslatedFieldsMixin, TJSONField) + + +class ProductType(TranslatedFieldsMixin, ProjectBaseMixin): + """ProductType model.""" + + name = TJSONField(blank=True, null=True, default=None, + verbose_name=_('Name'), help_text='{"en-GB":"some text"}') + index_name = models.CharField(max_length=50, unique=True, db_index=True, + verbose_name=_('Index name')) + use_subtypes = models.BooleanField(_('Use subtypes'), default=True) + + class Meta: + """Meta class.""" + + verbose_name = _('Product type') + verbose_name_plural = _('Product types') + + +class ProductSubType(TranslatedFieldsMixin, ProjectBaseMixin): + """ProductSubtype model.""" + + product_type = models.ForeignKey(ProductType, on_delete=models.CASCADE, + related_name='subtypes', + verbose_name=_('Product type')) + name = TJSONField(blank=True, null=True, default=None, + verbose_name=_('Name'), help_text='{"en-GB":"some text"}') + index_name = models.CharField(max_length=50, unique=True, db_index=True, + verbose_name=_('Index name')) + + class Meta: + """Meta class.""" + + verbose_name = _('Product type') + verbose_name_plural = _('Product types') + + +class ProductManager(models.Manager): + """Extended manager for Product model.""" + + +class ProductQuerySet(models.QuerySet): + """Product queryset.""" + + def common(self): + return self.filter(category=self.model.COMMON) + + def online(self): + return self.filter(category=self.model.ONLINE) + + +class Product(TranslatedFieldsMixin, BaseAttributes): + """Product models.""" + + COMMON = 0 + ONLINE = 1 + + CATEGORY_CHOICES = ( + (COMMON, _('Common')), + (ONLINE, _('Online')), + ) + + category = models.PositiveIntegerField(choices=CATEGORY_CHOICES, + default=COMMON) + name = TJSONField(_('Name'), null=True, blank=True, default=None, + help_text='{"en-GB":"some text"}') + description = TJSONField(_('Description'), null=True, blank=True, + default=None, help_text='{"en-GB":"some text"}') + characteristics = JSONField(_('Characteristics')) + country = models.ForeignKey('location.Country', on_delete=models.PROTECT, + verbose_name=_('Country')) + available = models.BooleanField(_('Available'), default=True) + type = models.ForeignKey(ProductType, on_delete=models.PROTECT, + related_name='products', verbose_name=_('Type')) + subtypes = models.ManyToManyField(ProductSubType, related_name='products', + verbose_name=_('Subtypes')) + + objects = ProductManager.from_queryset(ProductQuerySet)() + + class Meta: + """Meta class.""" + + verbose_name = _('Product') + verbose_name_plural = _('Products') + + +class OnlineProductManager(ProductManager): + """Extended manger for OnlineProduct model.""" + + def get_queryset(self): + """Overrided get_queryset method.""" + return super().get_queryset().online() + + +class OnlineProduct(Product): + """Online product.""" + + objects = OnlineProductManager.from_queryset(ProductQuerySet)() + + class Meta: + """Meta class.""" + + proxy = True + verbose_name = _('Online product') + verbose_name_plural = _('Online products') diff --git a/project/settings/base.py b/project/settings/base.py index 72fbd05f..448f6318 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -64,7 +64,7 @@ PROJECT_APPS = [ 'news.apps.NewsConfig', 'notification.apps.NotificationConfig', 'partner.apps.PartnerConfig', - 'product.apps.ProductConfig', + # 'product.apps.ProductConfig', Uncomment after refining task and create migrations 'recipe.apps.RecipeConfig', 'search_indexes.apps.SearchIndexesConfig', 'translation.apps.TranslationConfig', From 64692ae4b371b4f61c6a28d3e31b0f7732fc4b7c Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Wed, 23 Oct 2019 12:03:50 +0300 Subject: [PATCH 65/87] max length of slug is 255 --- .../news/migrations/0023_auto_20191023_0903.py | 18 ++++++++++++++++++ apps/news/models.py | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 apps/news/migrations/0023_auto_20191023_0903.py diff --git a/apps/news/migrations/0023_auto_20191023_0903.py b/apps/news/migrations/0023_auto_20191023_0903.py new file mode 100644 index 00000000..e1380b30 --- /dev/null +++ b/apps/news/migrations/0023_auto_20191023_0903.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-10-23 09:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0022_auto_20191021_1306'), + ] + + operations = [ + migrations.AlterField( + model_name='news', + name='slug', + field=models.SlugField(max_length=255, unique=True, verbose_name='News slug'), + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index c86187d5..9e2a2926 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -139,7 +139,7 @@ class News(BaseAttributes, TranslatedFieldsMixin): start = models.DateTimeField(verbose_name=_('Start')) end = models.DateTimeField(blank=True, null=True, default=None, verbose_name=_('End')) - slug = models.SlugField(unique=True, max_length=50, + slug = models.SlugField(unique=True, max_length=255, verbose_name=_('News slug')) playlist = models.IntegerField(_('playlist')) state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES, From 9c9c49d9008c5ca190de4bbd7b1c262a849d9bc4 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Wed, 23 Oct 2019 12:25:50 +0300 Subject: [PATCH 66/87] update slug fields --- .../migrations/0041_auto_20191023_0920.py | 18 ++++++++++++++++++ apps/establishment/models.py | 4 ++-- .../main/migrations/0021_auto_20191023_0924.py | 18 ++++++++++++++++++ apps/main/models.py | 2 +- 4 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 apps/establishment/migrations/0041_auto_20191023_0920.py create mode 100644 apps/main/migrations/0021_auto_20191023_0924.py diff --git a/apps/establishment/migrations/0041_auto_20191023_0920.py b/apps/establishment/migrations/0041_auto_20191023_0920.py new file mode 100644 index 00000000..dc5b2e02 --- /dev/null +++ b/apps/establishment/migrations/0041_auto_20191023_0920.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-10-23 09:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0040_employee_tags'), + ] + + operations = [ + migrations.AlterField( + model_name='establishment', + name='slug', + field=models.SlugField(max_length=255, null=True, unique=True, verbose_name='Establishment slug'), + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 68acb68f..1406e552 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -349,8 +349,8 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): verbose_name=_('Collections')) preview_image_url = models.URLField(verbose_name=_('Preview image URL path'), blank=True, null=True, default=None) - slug = models.SlugField(unique=True, max_length=50, null=True, - verbose_name=_('Establishment slug'), editable=True) + slug = models.SlugField(unique=True, max_length=255, null=True, + verbose_name=_('Establishment slug')) awards = generic.GenericRelation(to='main.Award', related_query_name='establishment') tags = models.ManyToManyField('tag.Tag', related_name='establishments', diff --git a/apps/main/migrations/0021_auto_20191023_0924.py b/apps/main/migrations/0021_auto_20191023_0924.py new file mode 100644 index 00000000..6bd6e1d6 --- /dev/null +++ b/apps/main/migrations/0021_auto_20191023_0924.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-10-23 09:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0020_merge_20191023_0750'), + ] + + operations = [ + migrations.AlterField( + model_name='feature', + name='slug', + field=models.SlugField(max_length=255, unique=True), + ), + ] diff --git a/apps/main/models.py b/apps/main/models.py index 1bb6ace3..e5d947fd 100644 --- a/apps/main/models.py +++ b/apps/main/models.py @@ -182,7 +182,7 @@ class Page(models.Model): class Feature(ProjectBaseMixin, PlatformMixin): """Feature model.""" - slug = models.CharField(max_length=255, unique=True) + slug = models.SlugField(max_length=255, unique=True) priority = models.IntegerField(unique=True, null=True, default=None) route = models.ForeignKey(Page, on_delete=models.PROTECT, null=True, default=None) site_settings = models.ManyToManyField(SiteSettings, through='SiteFeature') From 083c8a7eaa258a753397ddc5cd476e7d4aeb8a94 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Wed, 23 Oct 2019 13:00:39 +0300 Subject: [PATCH 67/87] same queryset for Establishment list and detail views --- apps/establishment/views/web.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index 1a0d5f58..0699d9d0 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -19,9 +19,12 @@ class EstablishmentMixinView: def get_queryset(self): """Overridden method 'get_queryset'.""" - return models.Establishment.objects.published() \ - .with_base_related() \ - .annotate_in_favorites(self.request.user) + qs = models.Establishment.objects.published() \ + .with_base_related() \ + .annotate_in_favorites(self.request.user) + if self.request.country_code: + qs = qs.by_country_code(self.request.country_code) + return qs class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView): @@ -30,13 +33,6 @@ class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView): filter_class = filters.EstablishmentFilter serializer_class = serializers.EstablishmentBaseSerializer - def get_queryset(self): - """Overridden method 'get_queryset'.""" - qs = super(EstablishmentListView, self).get_queryset() - if self.request.country_code: - qs = qs.by_country_code(self.request.country_code) - return qs - class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView): """Resource for getting a establishment.""" @@ -45,9 +41,7 @@ class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView serializer_class = serializers.EstablishmentDetailSerializer def get_queryset(self): - return super().get_queryset() \ - .by_country_code(self.request.country_code) \ - .with_extended_related() + return super().get_queryset().with_extended_related() class EstablishmentRecentReviewListView(EstablishmentListView): From 538ae41d850347a4597c2c2c5407d802cefe6d7b Mon Sep 17 00:00:00 2001 From: Semyon Yekhmenin Date: Wed, 23 Oct 2019 10:06:39 +0000 Subject: [PATCH 68/87] Establishments/slug/ fix --- apps/establishment/views/web.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index d3cc91cb..0699d9d0 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -19,9 +19,12 @@ class EstablishmentMixinView: def get_queryset(self): """Overridden method 'get_queryset'.""" - return models.Establishment.objects.published() \ - .with_base_related() \ - .annotate_in_favorites(self.request.user) + qs = models.Establishment.objects.published() \ + .with_base_related() \ + .annotate_in_favorites(self.request.user) + if self.request.country_code: + qs = qs.by_country_code(self.request.country_code) + return qs class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView): @@ -30,13 +33,6 @@ class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView): filter_class = filters.EstablishmentFilter serializer_class = serializers.EstablishmentBaseSerializer - def get_queryset(self): - """Overridden method 'get_queryset'.""" - qs = super(EstablishmentListView, self).get_queryset() - if self.request.country_code: - qs = qs.by_country_code(self.request.country_code) - return qs - class EstablishmentRetrieveView(EstablishmentMixinView, generics.RetrieveAPIView): """Resource for getting a establishment.""" From f46d4bd9f1173331d312f52bdddb7670bc657b5c Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 14:11:07 +0300 Subject: [PATCH 69/87] Review fix --- apps/tag/migrations/0005_tagcategory_name_indexing.py | 2 +- apps/tag/models.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/tag/migrations/0005_tagcategory_name_indexing.py b/apps/tag/migrations/0005_tagcategory_name_indexing.py index 839b5747..7fcb7f0f 100644 --- a/apps/tag/migrations/0005_tagcategory_name_indexing.py +++ b/apps/tag/migrations/0005_tagcategory_name_indexing.py @@ -12,7 +12,7 @@ class Migration(migrations.Migration): operations = [ migrations.AddField( model_name='tagcategory', - name='name_indexing', + name='index_name', field=models.CharField(blank=True, max_length=255, null=True, verbose_name='indexing name'), ), ] diff --git a/apps/tag/models.py b/apps/tag/models.py index dbacbd2c..6e7ec65e 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -69,8 +69,8 @@ class TagCategory(TranslatedFieldsMixin, models.Model): on_delete=models.SET_NULL, null=True, default=None) public = models.BooleanField(default=False) - name_indexing = models.CharField(max_length=255, blank=True, null=True, - verbose_name=_('indexing name')) + index_name = models.CharField(max_length=255, blank=True, null=True, + verbose_name=_('indexing name'), unique=True) objects = TagCategoryQuerySet.as_manager() From 81bd7b491230855d42551c7d7e15322445f8ba49 Mon Sep 17 00:00:00 2001 From: Semyon Yekhmenin Date: Wed, 23 Oct 2019 11:58:33 +0000 Subject: [PATCH 70/87] Feature/currency sign --- .../migrations/0022_auto_20191023_1113.py | 37 +++++++++++++++++++ apps/main/models.py | 29 +++++++++------ apps/main/serializers.py | 25 +++++++------ 3 files changed, 68 insertions(+), 23 deletions(-) create mode 100644 apps/main/migrations/0022_auto_20191023_1113.py diff --git a/apps/main/migrations/0022_auto_20191023_1113.py b/apps/main/migrations/0022_auto_20191023_1113.py new file mode 100644 index 00000000..0a190b23 --- /dev/null +++ b/apps/main/migrations/0022_auto_20191023_1113.py @@ -0,0 +1,37 @@ +# Generated by Django 2.2.4 on 2019-10-23 11:13 + +from django.db import migrations, models +import django.db.models.deletion +import utils.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0021_auto_20191023_0924'), + ] + + operations = [ + migrations.AddField( + model_name='currency', + name='sign', + field=models.CharField(default='?', max_length=1, verbose_name='sign'), + preserve_default=False, + ), + migrations.AddField( + model_name='currency', + name='slug', + field=models.SlugField(default='?', max_length=255, unique=True), + preserve_default=False, + ), + migrations.AddField( + model_name='sitesettings', + name='currency', + field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='main.Currency'), + ), + migrations.AlterField( + model_name='currency', + name='name', + field=utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='name'), + ), + ] diff --git a/apps/main/models.py b/apps/main/models.py index e5d947fd..bbdd6386 100644 --- a/apps/main/models.py +++ b/apps/main/models.py @@ -106,6 +106,22 @@ from utils.querysets import ContentTypeQuerySetMixin # +class Currency(models.Model, TranslatedFieldsMixin): + """Currency model.""" + name = TJSONField( + _('name'), null=True, blank=True, + default=None, help_text='{"en-GB":"some text"}') + sign = models.CharField(_('sign'), max_length=1) + slug = models.SlugField(max_length=255, unique=True) + + class Meta: + verbose_name = _('currency') + verbose_name_plural = _('currencies') + + def __str__(self): + return f'{self.name}' + + class SiteSettingsQuerySet(models.QuerySet): """Extended queryset for SiteSettings model.""" @@ -135,6 +151,7 @@ class SiteSettings(ProjectBaseMixin): verbose_name=_('Config')) ad_config = models.TextField(blank=True, null=True, default=None, verbose_name=_('AD config')) + currency = models.ForeignKey(Currency, on_delete=models.PROTECT, null=True, default=None) objects = SiteSettingsQuerySet.as_manager() @@ -257,18 +274,6 @@ class AwardType(models.Model): return self.name -class Currency(models.Model): - """Currency model.""" - name = models.CharField(_('name'), max_length=50) - - class Meta: - verbose_name = _('currency') - verbose_name_plural = _('currencies') - - def __str__(self): - return f'{self.name}' - - class CarouselQuerySet(models.QuerySet): """Carousel QuerySet.""" diff --git a/apps/main/serializers.py b/apps/main/serializers.py index e2523fa9..0e65741e 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -40,11 +40,24 @@ class SiteFeatureSerializer(serializers.ModelSerializer): ) +class CurrencySerializer(serializers.ModelSerializer): + """Currency serializer""" + + class Meta: + model = models.Currency + fields = [ + 'id', + 'name_translated', + 'sign' + ] + + class SiteSettingsSerializer(serializers.ModelSerializer): """Site settings serializer.""" published_features = SiteFeatureSerializer(source='published_sitefeatures', many=True, allow_null=True) + currency = CurrencySerializer() # todo: remove this country_code = serializers.CharField(source='subdomain', read_only=True) @@ -63,6 +76,7 @@ class SiteSettingsSerializer(serializers.ModelSerializer): 'config', 'ad_config', 'published_features', + 'currency' ) @@ -114,17 +128,6 @@ class AwardSerializer(AwardBaseSerializer): fields = AwardBaseSerializer.Meta.fields + ['award_type', ] -class CurrencySerializer(serializers.ModelSerializer): - """Currency serializer""" - - class Meta: - model = models.Currency - fields = [ - 'id', - 'name' - ] - - class CarouselListSerializer(serializers.ModelSerializer): """Serializer for retrieving list of carousel items.""" model_name = serializers.CharField() From 6a9f11e98960892d3e9a0d1eb6792a0760b3452f Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 23 Oct 2019 15:31:04 +0300 Subject: [PATCH 71/87] merge from develop --- .../migrations/0014_merge_20191023_0959.py | 14 ++++++++ .../migrations/0023_merge_20191023_1000.py | 14 ++++++++ .../migrations/0024_newsgallery_is_main.py | 18 ++++++++++ apps/news/models.py | 16 ++++++--- apps/news/serializers.py | 33 +++++++++++++++++-- apps/news/views.py | 2 +- .../migrations/0003_merge_20191004_1401.py | 14 -------- .../migrations/0003_auto_20191002_0729.py | 17 ---------- project/settings/base.py | 15 ++++++++- project/settings/local.py | 8 ++--- 10 files changed, 108 insertions(+), 43 deletions(-) create mode 100644 apps/account/migrations/0014_merge_20191023_0959.py create mode 100644 apps/news/migrations/0023_merge_20191023_1000.py create mode 100644 apps/news/migrations/0024_newsgallery_is_main.py delete mode 100644 apps/rating/migrations/0003_merge_20191004_1401.py delete mode 100644 apps/timetable/migrations/0003_auto_20191002_0729.py diff --git a/apps/account/migrations/0014_merge_20191023_0959.py b/apps/account/migrations/0014_merge_20191023_0959.py new file mode 100644 index 00000000..07ab850e --- /dev/null +++ b/apps/account/migrations/0014_merge_20191023_0959.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.4 on 2019-10-23 09:59 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0009_auto_20191002_0648'), + ('account', '0013_auto_20191016_0810'), + ] + + operations = [ + ] diff --git a/apps/news/migrations/0023_merge_20191023_1000.py b/apps/news/migrations/0023_merge_20191023_1000.py new file mode 100644 index 00000000..75dfd1b2 --- /dev/null +++ b/apps/news/migrations/0023_merge_20191023_1000.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.4 on 2019-10-23 10:00 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0021_merge_20191002_1300'), + ('news', '0022_auto_20191021_1306'), + ] + + operations = [ + ] diff --git a/apps/news/migrations/0024_newsgallery_is_main.py b/apps/news/migrations/0024_newsgallery_is_main.py new file mode 100644 index 00000000..aa7fffd9 --- /dev/null +++ b/apps/news/migrations/0024_newsgallery_is_main.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-10-23 10:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0023_merge_20191023_1000'), + ] + + operations = [ + migrations.AddField( + model_name='newsgallery', + name='is_main', + field=models.BooleanField(default=False, verbose_name='Is the main image'), + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index 643a0143..246aadf0 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -146,10 +146,6 @@ class News(BaseAttributes, TranslatedFieldsMixin): verbose_name=_('State')) is_highlighted = models.BooleanField(default=False, verbose_name=_('Is highlighted')) - 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')) template = models.PositiveIntegerField(choices=TEMPLATE_CHOICES, default=NEWSPAPER) address = models.ForeignKey('location.Address', blank=True, null=True, default=None, verbose_name=_('address'), @@ -197,10 +193,18 @@ class News(BaseAttributes, TranslatedFieldsMixin): def same_theme(self): return self.__class__.objects.same_theme(self)[:3] + @property + def main_image(self): + return self.news_gallery.main_images().first().image + class NewsGalleryQuerySet(models.QuerySet): """QuerySet for model News""" + def main_images(self): + """Return objects with flag is_main is True""" + return self.filter(is_main=True) + class NewsGallery(models.Model): @@ -212,6 +216,10 @@ class NewsGallery(models.Model): related_name='news_gallery', on_delete=models.SET_NULL, verbose_name=_('gallery')) + is_main = models.BooleanField(default=False, + verbose_name=_('Is the main image')) + + objects = NewsGalleryQuerySet.as_manager() class Meta: """NewsGallery meta class.""" diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 658e4203..12c76e67 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -134,7 +134,6 @@ class NewsBaseSerializer(ProjectModelSerializer): # related fields news_type = NewsTypeSerializer(read_only=True) - gallery = NewsImageSerializer(read_only=True, many=True) tags = TagBaseSerializer(read_only=True, many=True) class Meta: @@ -149,7 +148,34 @@ class NewsBaseSerializer(ProjectModelSerializer): 'news_type', 'tags', 'slug', - 'gallery', + ) + + +class NewsListSerializer(NewsBaseSerializer): + """List serializer for News model.""" + + # read only fields + title_translated = TranslatedField() + subtitle_translated = TranslatedField() + + # related fields + news_type = NewsTypeSerializer(read_only=True) + tags = TagBaseSerializer(read_only=True, many=True) + image = NewsImageSerializer(source='main_image', allow_null=True) + + class Meta: + """Meta class.""" + + model = models.News + fields = ( + 'id', + 'title_translated', + 'subtitle_translated', + 'is_highlighted', + 'news_type', + 'tags', + 'slug', + 'image', ) @@ -161,6 +187,7 @@ class NewsDetailSerializer(NewsBaseSerializer): author = UserBaseSerializer(source='created_by', read_only=True) state_display = serializers.CharField(source='get_state_display', read_only=True) + gallery = NewsImageSerializer(read_only=True, many=True) class Meta(NewsBaseSerializer.Meta): """Meta class.""" @@ -175,6 +202,7 @@ class NewsDetailSerializer(NewsBaseSerializer): 'state_display', 'author', 'country', + 'gallery', ) @@ -242,6 +270,7 @@ class NewsBackOfficeGallerySerializer(serializers.ModelSerializer): model = models.NewsGallery fields = [ 'id', + 'is_main', ] def get_request_kwargs(self): diff --git a/apps/news/views.py b/apps/news/views.py index ba5719b0..c2a71eca 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -30,7 +30,7 @@ class NewsMixinView: class NewsListView(NewsMixinView, generics.ListAPIView): """News list view.""" - + serializer_class = serializers.NewsListSerializer filter_class = filters.NewsListFilterSet diff --git a/apps/rating/migrations/0003_merge_20191004_1401.py b/apps/rating/migrations/0003_merge_20191004_1401.py deleted file mode 100644 index f04cc893..00000000 --- a/apps/rating/migrations/0003_merge_20191004_1401.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 2.2.4 on 2019-10-04 14:01 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('rating', '0002_auto_20191004_1217'), - ('rating', '0002_auto_20191004_0928'), - ] - - operations = [ - ] diff --git a/apps/timetable/migrations/0003_auto_20191002_0729.py b/apps/timetable/migrations/0003_auto_20191002_0729.py deleted file mode 100644 index 16196f74..00000000 --- a/apps/timetable/migrations/0003_auto_20191002_0729.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.2.4 on 2019-10-02 07:29 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('timetable', '0002_auto_20190919_1124'), - ] - - operations = [ - migrations.AlterModelOptions( - name='timetable', - options={'ordering': ['weekday'], 'verbose_name': 'Timetable', 'verbose_name_plural': 'Timetables'}, - ), - ] diff --git a/project/settings/base.py b/project/settings/base.py index 0e078f9f..cd0a63b0 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -369,7 +369,6 @@ SORL_THUMBNAIL_ALIASES = { 'avatar_comments_web': {'geometry_string': '116x116', 'crop': 'center'}, # через контент эдитор в мобильном браузерe } -GEOIP_PATH = os.path.join(PROJECT_ROOT, 'geoip_db') # JWT SIMPLE_JWT = { @@ -455,3 +454,17 @@ LIMITING_QUERY_OBJECTS = QUERY_OUTPUT_OBJECTS * 3 # GEO # A Spatial Reference System Identifier GEO_DEFAULT_SRID = 4326 +GEOIP_PATH = os.path.join(PROJECT_ROOT, 'geoip_db') + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/2.2/howto/static-files/ +STATIC_ROOT = os.path.join(PUBLIC_ROOT, 'static') +STATIC_URL = '/static/' + +STATICFILES_DIRS = ( + os.path.join(PROJECT_ROOT, 'static'), +) + + +# MEDIA +MEDIA_LOCATION = 'media' \ No newline at end of file diff --git a/project/settings/local.py b/project/settings/local.py index 4e219bfb..e87b99f6 100644 --- a/project/settings/local.py +++ b/project/settings/local.py @@ -31,6 +31,10 @@ MEDIA_URL = f'{SCHEMA_URI}://{DOMAIN_URI}/{MEDIA_LOCATION}/' MEDIA_ROOT = os.path.join(PUBLIC_ROOT, MEDIA_LOCATION) +# SORL thumbnails +THUMBNAIL_DEBUG = True + + # LOGGING LOGGING = { 'version': 1, @@ -84,7 +88,3 @@ ELASTICSEARCH_INDEX_NAMES = { TESTING = sys.argv[1:2] == ['test'] if TESTING: ELASTICSEARCH_INDEX_NAMES = {} - - -# SORL thumbnails -THUMBNAIL_DEBUG = True From d8c99062ca480fe2040ed97025fb0bd09efd1041 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Wed, 23 Oct 2019 16:28:42 +0300 Subject: [PATCH 72/87] fix singals --- apps/search_indexes/signals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/search_indexes/signals.py b/apps/search_indexes/signals.py index 77660a2c..3da50fb8 100644 --- a/apps/search_indexes/signals.py +++ b/apps/search_indexes/signals.py @@ -38,7 +38,7 @@ def update_document(sender, **kwargs): for establishment in establishments: registry.update(establishment) if model_name == 'establishmentsubtype': - if instance(instance, establishment_models.EstablishmentSubType): + if isinstance(instance, establishment_models.EstablishmentSubType): establishments = Establishment.objects.filter( establishment_subtypes=instance) for establishment in establishments: From bd3a809d8517b22f2427a76e49d155e30277c5fe Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 23 Oct 2019 16:47:57 +0300 Subject: [PATCH 73/87] fix migrations --- .../0039_establishmentsubtype_index_name.py | 2 +- .../migrations/0022_auto_20191023_1113.py | 24 +++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/apps/establishment/migrations/0039_establishmentsubtype_index_name.py b/apps/establishment/migrations/0039_establishmentsubtype_index_name.py index a29b1ae0..cf35010a 100644 --- a/apps/establishment/migrations/0039_establishmentsubtype_index_name.py +++ b/apps/establishment/migrations/0039_establishmentsubtype_index_name.py @@ -8,7 +8,7 @@ def fill_establishment_subtype(apps, schema_editor): # version than this migration expects. We use the historical version. EstablishmentSubType = apps.get_model('establishment', 'EstablishmentSubType') for n, et in enumerate(EstablishmentSubType.objects.all()): - et.index_name = f'Type {n}' + et.index_name = 'Type %s' % n et.save() diff --git a/apps/main/migrations/0022_auto_20191023_1113.py b/apps/main/migrations/0022_auto_20191023_1113.py index 0a190b23..6ed60051 100644 --- a/apps/main/migrations/0022_auto_20191023_1113.py +++ b/apps/main/migrations/0022_auto_20191023_1113.py @@ -5,6 +5,16 @@ import django.db.models.deletion import utils.models +def fill_currency_name(apps, schema_editor): + # We can't import the Person model directly as it may be a newer + # version than this migration expects. We use the historical version. + Currency = apps.get_model('main', 'Currency') + for currency in Currency.objects.all(): + currency.name_json = {'en-GB': currency.name} + currency.save() + + + class Migration(migrations.Migration): dependencies = [ @@ -29,9 +39,19 @@ class Migration(migrations.Migration): name='currency', field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='main.Currency'), ), - migrations.AlterField( + migrations.AddField( model_name='currency', - name='name', + name='name_json', field=utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='name'), ), + migrations.RunPython(fill_currency_name, migrations.RunPython.noop), + migrations.RemoveField( + model_name='currency', + name='name', + ), + migrations.RenameField( + model_name='currency', + old_name='name_json', + new_name='name', + ), ] From b1169675a8c9915d7a2aadfb621780e7ca8e93fa Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Wed, 23 Oct 2019 17:03:24 +0300 Subject: [PATCH 74/87] fix CurrencySerializer --- apps/main/serializers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/main/serializers.py b/apps/main/serializers.py index 0e65741e..8975af14 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -4,7 +4,7 @@ from rest_framework import serializers from advertisement.serializers.web import AdvertisementSerializer from location.serializers import CountrySerializer from main import models -from utils.serializers import TranslatedField +from utils.serializers import ProjectModelSerializer class FeatureSerializer(serializers.ModelSerializer): @@ -40,7 +40,7 @@ class SiteFeatureSerializer(serializers.ModelSerializer): ) -class CurrencySerializer(serializers.ModelSerializer): +class CurrencySerializer(ProjectModelSerializer): """Currency serializer""" class Meta: From 7e93aafc3f8d8d7eb77fb6a3a1363567a1246b25 Mon Sep 17 00:00:00 2001 From: "a.feteleu" Date: Wed, 23 Oct 2019 17:05:37 +0300 Subject: [PATCH 75/87] added merge migrations --- .../migrations/0015_merge_20191023_1317.py | 14 ++++++++++++++ apps/news/migrations/0025_merge_20191023_1317.py | 15 +++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 apps/account/migrations/0015_merge_20191023_1317.py create mode 100644 apps/news/migrations/0025_merge_20191023_1317.py diff --git a/apps/account/migrations/0015_merge_20191023_1317.py b/apps/account/migrations/0015_merge_20191023_1317.py new file mode 100644 index 00000000..f014afb2 --- /dev/null +++ b/apps/account/migrations/0015_merge_20191023_1317.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.4 on 2019-10-23 13:17 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0014_merge_20191023_0959'), + ('account', '0012_merge_20191015_0912'), + ] + + operations = [ + ] diff --git a/apps/news/migrations/0025_merge_20191023_1317.py b/apps/news/migrations/0025_merge_20191023_1317.py new file mode 100644 index 00000000..96cb3980 --- /dev/null +++ b/apps/news/migrations/0025_merge_20191023_1317.py @@ -0,0 +1,15 @@ +# Generated by Django 2.2.4 on 2019-10-23 13:17 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0024_newsgallery_is_main'), + ('news', '0023_auto_20191023_0903'), + ('news', '0022_merge_20191015_0912'), + ] + + operations = [ + ] From 125c438f199359a556f726dba903aad053b3f789 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 14:11:07 +0300 Subject: [PATCH 76/87] Review fix #2 --- apps/tag/migrations/0005_tagcategory_name_indexing.py | 2 +- apps/tag/models.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/tag/migrations/0005_tagcategory_name_indexing.py b/apps/tag/migrations/0005_tagcategory_name_indexing.py index 7fcb7f0f..2547481a 100644 --- a/apps/tag/migrations/0005_tagcategory_name_indexing.py +++ b/apps/tag/migrations/0005_tagcategory_name_indexing.py @@ -13,6 +13,6 @@ class Migration(migrations.Migration): migrations.AddField( model_name='tagcategory', name='index_name', - field=models.CharField(blank=True, max_length=255, null=True, verbose_name='indexing name'), + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='indexing name', unique=True), ), ] diff --git a/apps/tag/models.py b/apps/tag/models.py index 6e7ec65e..1ee594ad 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -70,7 +70,7 @@ class TagCategory(TranslatedFieldsMixin, models.Model): default=None) public = models.BooleanField(default=False) index_name = models.CharField(max_length=255, blank=True, null=True, - verbose_name=_('indexing name'), unique=True) + verbose_name=_('indexing name'), unique=True) objects = TagCategoryQuerySet.as_manager() From 40357c20b6403e0efed74b0ccd329372101f08e4 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 14:11:07 +0300 Subject: [PATCH 77/87] Changes from gm-148 --- apps/tag/migrations/0004_merge_20191021_1138.py | 14 ++++++++++++++ apps/timetable/serialziers.py | 1 + project/templates/news/news_email.html | 12 ++++++------ 3 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 apps/tag/migrations/0004_merge_20191021_1138.py diff --git a/apps/tag/migrations/0004_merge_20191021_1138.py b/apps/tag/migrations/0004_merge_20191021_1138.py new file mode 100644 index 00000000..6298ce4f --- /dev/null +++ b/apps/tag/migrations/0004_merge_20191021_1138.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.4 on 2019-10-21 11:38 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('tag', '0003_auto_20191018_0758'), + ('tag', '0003_tag_priority'), + ] + + operations = [ + ] diff --git a/apps/timetable/serialziers.py b/apps/timetable/serialziers.py index 76a37257..babe33c1 100644 --- a/apps/timetable/serialziers.py +++ b/apps/timetable/serialziers.py @@ -78,6 +78,7 @@ class ScheduleCreateSerializer(ScheduleRUDSerializer): establishment.schedule.add(instance) return instance + class TimetableSerializer(serializers.ModelSerializer): """Serailzier for Timetable model.""" weekday_display = serializers.CharField(source='get_weekday_display', diff --git a/project/templates/news/news_email.html b/project/templates/news/news_email.html index 6fe14060..0227b9de 100644 --- a/project/templates/news/news_email.html +++ b/project/templates/news/news_email.html @@ -7,7 +7,7 @@ {{ title }} - +
@@ -24,19 +24,19 @@ -
+
{{ title }}
{% if not image_url is None %}
- +
{% endif %} -
+
{{ description | safe }}
- -
+ +
Go to news
From 47da9929cb5ef6c12881ac81fa9ef5a4d5ab0a2e Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 14:11:07 +0300 Subject: [PATCH 78/87] Some fixes --- apps/search_indexes/documents/news.py | 1 - apps/tag/migrations/0004_merge_20191021_1138.py | 14 -------------- 2 files changed, 15 deletions(-) delete mode 100644 apps/tag/migrations/0004_merge_20191021_1138.py diff --git a/apps/search_indexes/documents/news.py b/apps/search_indexes/documents/news.py index 21c59e68..cd6fc089 100644 --- a/apps/search_indexes/documents/news.py +++ b/apps/search_indexes/documents/news.py @@ -24,7 +24,6 @@ class NewsDocument(Document): country = fields.ObjectField(properties={'id': fields.IntegerField(), 'code': fields.KeywordField()}) web_url = fields.KeywordField(attr='web_url') - preview_image_url = fields.TextField(attr='preview_image_url') tags = fields.ObjectField( properties={ 'id': fields.IntegerField(attr='id'), diff --git a/apps/tag/migrations/0004_merge_20191021_1138.py b/apps/tag/migrations/0004_merge_20191021_1138.py deleted file mode 100644 index 6298ce4f..00000000 --- a/apps/tag/migrations/0004_merge_20191021_1138.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 2.2.4 on 2019-10-21 11:38 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('tag', '0003_auto_20191018_0758'), - ('tag', '0003_tag_priority'), - ] - - operations = [ - ] From d5609e4cb89f724e60aae4b98d485de4e9004fc2 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 14:11:07 +0300 Subject: [PATCH 79/87] Works now filter first part --- .../migrations/0040_establishment_tz.py | 33 +++++++++++++++++++ apps/establishment/models.py | 9 ++--- apps/establishment/serializers/back.py | 1 + apps/location/models.py | 6 ---- apps/search_indexes/views.py | 5 ++- project/settings/base.py | 1 + requirements/base.txt | 1 + 7 files changed, 43 insertions(+), 13 deletions(-) create mode 100644 apps/establishment/migrations/0040_establishment_tz.py diff --git a/apps/establishment/migrations/0040_establishment_tz.py b/apps/establishment/migrations/0040_establishment_tz.py new file mode 100644 index 00000000..d5a383d8 --- /dev/null +++ b/apps/establishment/migrations/0040_establishment_tz.py @@ -0,0 +1,33 @@ +# Generated by Django 2.2.4 on 2019-10-21 17:33 + +import timezone_field.fields +from django.db import migrations +from django.conf import settings +from timezonefinder import TimezoneFinder +from establishment.models import Establishment + + +def fill_timezones(apps, schema_editor): + tf = TimezoneFinder(in_memory=True) + for establishment in Establishment.objects.prefetch_related('address').all(): + if establishment.address and establishment.address.latitude and establishment.address.longitude: + establishment.tz = tf.certain_timezone_at(lng=establishment.address.longitude, + lat=establishment.address.latitude) + establishment.save() + + +class Migration(migrations.Migration): + + + dependencies = [ + ('establishment', '0039_establishmentsubtype_index_name'), + ] + + operations = [ + migrations.AddField( + model_name='establishment', + name='tz', + field=timezone_field.fields.TimeZoneField(default=settings.TIME_ZONE), + ), + migrations.RunPython(fill_timezones, migrations.RunPython.noop), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index c01d8613..7de88d91 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -14,7 +14,6 @@ from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q from django.utils import timezone from django.utils.translation import gettext_lazy as _ from phonenumber_field.modelfields import PhoneNumberField -from pytz import timezone as py_tz from collection.models import Collection from location.models import Address @@ -22,6 +21,7 @@ from main.models import Award from review.models import Review from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin, TranslatedFieldsMixin, BaseAttributes) +from timezone_field import TimeZoneField # todo: establishment type&subtypes check @@ -354,6 +354,7 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): blank=True, null=True, default=None) slug = models.SlugField(unique=True, max_length=255, null=True, verbose_name=_('Establishment slug')) + tz = TimeZoneField(default=settings.TIME_ZONE) awards = generic.GenericRelation(to='main.Award', related_query_name='establishment') tags = models.ManyToManyField('tag.Tag', related_name='establishments', @@ -429,13 +430,13 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): """ Used for indexing working by day """ return [ret.weekday for ret in self.schedule.all() if ret.works_at_afternoon] - # @property + @property def works_now(self): """ Is establishment working now """ - now_at_est_tz = datetime.now(tz=py_tz(self.address.tz_name)) + now_at_est_tz = datetime.now(tz=self.tz) current_week = now_at_est_tz.weekday() schedule_for_today = self.schedule.filter(weekday=current_week).first() - if schedule_for_today is None: + if schedule_for_today is None or schedule_for_today.closed_at is None or schedule_for_today.opening_at is None: return False time_at_est_tz = now_at_est_tz.time() return schedule_for_today.closed_at > time_at_est_tz > schedule_for_today.opening_at diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index 59725710..714dd62d 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -41,6 +41,7 @@ class EstablishmentListCreateSerializer(EstablishmentBaseSerializer): 'is_publish', 'guestonline_id', 'lastable_id', + 'tz', ] diff --git a/apps/location/models.py b/apps/location/models.py index 3591d4df..da645de6 100644 --- a/apps/location/models.py +++ b/apps/location/models.py @@ -6,7 +6,6 @@ from django.db.transaction import on_commit from django.dispatch import receiver from django.utils.translation import gettext_lazy as _ -from timezonefinder import TimezoneFinder from translation.models import Language from utils.models import ProjectBaseMixin, SVGImageMixin, TranslatedFieldsMixin, TJSONField @@ -114,11 +113,6 @@ class Address(models.Model): def get_street_name(self): return self.street_name_1 or self.street_name_2 - @property - def tz_name(self): - tf = TimezoneFinder(in_memory=True) - return tf.certain_timezone_at(lng=self.latitude, lat=self.longitude) - @property def latitude(self): return self.coordinates.y if self.coordinates else float(0) diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index 913c2d95..25205bac 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -133,14 +133,13 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): 'works_evening': { 'field': 'works_evening', 'lookups': [ - constants.LOOKUP_FILTER_TERM, + constants.LOOKUP_QUERY_IN, ], }, 'works_now': { 'field': 'works_now', 'lookups': [ - constants.LOOKUP_FILTER_EXISTS, - constants.LOOKUP_QUERY_IN, + constants.LOOKUP_FILTER_TERM, ] }, } diff --git a/project/settings/base.py b/project/settings/base.py index bec8fc2b..02df804e 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -95,6 +95,7 @@ EXTERNAL_APPS = [ 'rest_framework_simplejwt.token_blacklist', 'solo', 'phonenumber_field', + 'timezone_field', 'storages', 'sorl.thumbnail', 'timezonefinder' diff --git a/requirements/base.txt b/requirements/base.txt index dbb3b20e..c95055de 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -18,6 +18,7 @@ django-filter==2.1.0 djangorestframework-xml geoip2==2.9.0 django-phonenumber-field[phonenumbers]==2.1.0 +django-timezone-field==3.1 # auth socials django-rest-framework-social-oauth2==1.1.0 From ce5987de9528da4d891a9c4870b7c61eb632a551 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 14:11:07 +0300 Subject: [PATCH 80/87] Attach establishment timestamps as command --- apps/establishment/management/__init__.py | 0 .../management/commands/__init__.py | 0 .../commands/attach_establishments_tz.py | 23 +++++++++++++++++++ .../migrations/0040_establishment_tz.py | 12 ---------- 4 files changed, 23 insertions(+), 12 deletions(-) create mode 100644 apps/establishment/management/__init__.py create mode 100644 apps/establishment/management/commands/__init__.py create mode 100644 apps/establishment/management/commands/attach_establishments_tz.py diff --git a/apps/establishment/management/__init__.py b/apps/establishment/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/establishment/management/commands/__init__.py b/apps/establishment/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/establishment/management/commands/attach_establishments_tz.py b/apps/establishment/management/commands/attach_establishments_tz.py new file mode 100644 index 00000000..58be04cb --- /dev/null +++ b/apps/establishment/management/commands/attach_establishments_tz.py @@ -0,0 +1,23 @@ +from django.core.management.base import BaseCommand +from pytz import timezone as py_tz +from timezonefinder import TimezoneFinder +from establishment.models import Establishment + + +class Command(BaseCommand): + help = 'Attach correct timestamps according to coordinates to existing establishments' + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.tf = TimezoneFinder(in_memory=True) + + def handle(self, *args, **options): + for establishment in Establishment.objects.prefetch_related('address').all(): + if establishment.address and establishment.address.latitude and establishment.address.longitude: + establishment.tz = py_tz(self.tf.certain_timezone_at(lng=establishment.address.longitude, + lat=establishment.address.latitude)) + establishment.save() + self.stdout.write(self.style.SUCCESS(f'Attached timezone {establishment.tz} to {establishment}')) + else: + self.stdout.write(self.style.WARNING(f'Establishment {establishment} has no coordinates' + f'passing...')) diff --git a/apps/establishment/migrations/0040_establishment_tz.py b/apps/establishment/migrations/0040_establishment_tz.py index d5a383d8..75f54c06 100644 --- a/apps/establishment/migrations/0040_establishment_tz.py +++ b/apps/establishment/migrations/0040_establishment_tz.py @@ -3,17 +3,6 @@ import timezone_field.fields from django.db import migrations from django.conf import settings -from timezonefinder import TimezoneFinder -from establishment.models import Establishment - - -def fill_timezones(apps, schema_editor): - tf = TimezoneFinder(in_memory=True) - for establishment in Establishment.objects.prefetch_related('address').all(): - if establishment.address and establishment.address.latitude and establishment.address.longitude: - establishment.tz = tf.certain_timezone_at(lng=establishment.address.longitude, - lat=establishment.address.latitude) - establishment.save() class Migration(migrations.Migration): @@ -29,5 +18,4 @@ class Migration(migrations.Migration): name='tz', field=timezone_field.fields.TimeZoneField(default=settings.TIME_ZONE), ), - migrations.RunPython(fill_timezones, migrations.RunPython.noop), ] From 711d536c98cf8a96473681101b3f80c44ab3b99d Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 14:11:07 +0300 Subject: [PATCH 81/87] Update migration --- .../{0040_establishment_tz.py => 0041_establishment_tz.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename apps/establishment/migrations/{0040_establishment_tz.py => 0041_establishment_tz.py} (86%) diff --git a/apps/establishment/migrations/0040_establishment_tz.py b/apps/establishment/migrations/0041_establishment_tz.py similarity index 86% rename from apps/establishment/migrations/0040_establishment_tz.py rename to apps/establishment/migrations/0041_establishment_tz.py index 75f54c06..a06bbad9 100644 --- a/apps/establishment/migrations/0040_establishment_tz.py +++ b/apps/establishment/migrations/0041_establishment_tz.py @@ -9,7 +9,7 @@ class Migration(migrations.Migration): dependencies = [ - ('establishment', '0039_establishmentsubtype_index_name'), + ('establishment', '0040_employee_tags'), ] operations = [ From 07ac06f405f0a4473cd2a00b5d15e9d88dff60ea Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 14:11:07 +0300 Subject: [PATCH 82/87] Update migration --- .../{0041_establishment_tz.py => 0042_establishment_tz.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename apps/establishment/migrations/{0041_establishment_tz.py => 0042_establishment_tz.py} (88%) diff --git a/apps/establishment/migrations/0041_establishment_tz.py b/apps/establishment/migrations/0042_establishment_tz.py similarity index 88% rename from apps/establishment/migrations/0041_establishment_tz.py rename to apps/establishment/migrations/0042_establishment_tz.py index a06bbad9..e804242f 100644 --- a/apps/establishment/migrations/0041_establishment_tz.py +++ b/apps/establishment/migrations/0042_establishment_tz.py @@ -9,7 +9,7 @@ class Migration(migrations.Migration): dependencies = [ - ('establishment', '0040_employee_tags'), + ('establishment', '0041_auto_20191023_0920'), ] operations = [ From 1e3c80ad0cda529a5f5c0575224308f03833c38e Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 23 Oct 2019 17:40:42 +0300 Subject: [PATCH 83/87] small fix --- apps/gallery/models.py | 2 +- apps/main/models.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/gallery/models.py b/apps/gallery/models.py index bf3870cb..baed48fc 100644 --- a/apps/gallery/models.py +++ b/apps/gallery/models.py @@ -37,7 +37,7 @@ class Image(ProjectBaseMixin, SORLImageMixin, PlatformMixin): def __str__(self): """String representation""" - return f'{self.title}' + return f'{self.id}' def delete_image(self, completely: bool = True): """ diff --git a/apps/main/models.py b/apps/main/models.py index 3a54bedf..6af4e4d8 100644 --- a/apps/main/models.py +++ b/apps/main/models.py @@ -106,7 +106,7 @@ from utils.querysets import ContentTypeQuerySetMixin # -class Currency(models.Model, TranslatedFieldsMixin): +class Currency(TranslatedFieldsMixin, models.Model): """Currency model.""" name = TJSONField( _('name'), null=True, blank=True, From a8001c44b13de1f8b466cc74ab8b88b6378618e7 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 23 Oct 2019 17:50:08 +0300 Subject: [PATCH 84/87] added validation rule to NewsBackOfficeGallerySerializer --- apps/news/serializers.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 417db72e..6814965a 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -282,6 +282,7 @@ class NewsBackOfficeGallerySerializer(serializers.ModelSerializer): """Override validate method.""" news_pk = self.get_request_kwargs().get('pk') image_id = self.get_request_kwargs().get('image_id') + is_main = attrs.get('is_main') news_qs = models.News.objects.filter(pk=news_pk) image_qs = Image.objects.filter(id=image_id) @@ -297,6 +298,9 @@ class NewsBackOfficeGallerySerializer(serializers.ModelSerializer): if news.news_gallery.filter(image=image).exists(): raise serializers.ValidationError({'detail': _('Image is already added')}) + if is_main and news.news_gallery.main_images().exists(): + raise serializers.ValidationError({'detail': _('Main image is already added')}) + attrs['news'] = news attrs['image'] = image From b2341194bc23684f70b402d2479ed1e181394d79 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 14:11:07 +0300 Subject: [PATCH 85/87] fix serializer --- apps/tag/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py index 445042c7..790c8926 100644 --- a/apps/tag/serializers.py +++ b/apps/tag/serializers.py @@ -49,7 +49,7 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer): fields = ( 'id', 'label_translated', - 'name_indexing', + 'index_name', 'tags', ) From 890d1004e14231914522b8d3c917d98e155b077c Mon Sep 17 00:00:00 2001 From: Semyon Yekhmenin Date: Thu, 24 Oct 2019 07:44:47 +0000 Subject: [PATCH 86/87] Added view for chosen tags --- apps/tag/models.py | 10 ++++++++++ apps/tag/urls/web.py | 1 + apps/tag/views.py | 13 ++++++++++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/apps/tag/models.py b/apps/tag/models.py index f1fd4c37..26079849 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -5,6 +5,14 @@ from configuration.models import TranslationSettings from utils.models import TJSONField, TranslatedFieldsMixin +class TagQuerySet(models.QuerySet): + def filter_chosen(self): + return self.exclude(priority__isnull=True) + + def order_by_priority(self): + return self.order_by('priority') + + class Tag(TranslatedFieldsMixin, models.Model): """Tag model.""" @@ -16,6 +24,8 @@ class Tag(TranslatedFieldsMixin, models.Model): verbose_name=_('Category')) priority = models.IntegerField(unique=True, null=True, default=None) + objects = TagQuerySet.as_manager() + class Meta: """Meta class.""" diff --git a/apps/tag/urls/web.py b/apps/tag/urls/web.py index c99253eb..ec63931e 100644 --- a/apps/tag/urls/web.py +++ b/apps/tag/urls/web.py @@ -7,6 +7,7 @@ app_name = 'tag' router = SimpleRouter() router.register(r'categories', views.TagCategoryViewSet) +router.register(r'chosen_tags', views.ChosenTagsView, basename='Tag') urlpatterns = [ diff --git a/apps/tag/views.py b/apps/tag/views.py index 2a0ff0f5..6d98f39f 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -1,11 +1,22 @@ """Tag views.""" -from rest_framework import viewsets, mixins, status +from rest_framework import viewsets, mixins, status, generics from rest_framework.decorators import action from rest_framework.response import Response from tag import filters, models, serializers from rest_framework import permissions +class ChosenTagsView(generics.ListAPIView, viewsets.GenericViewSet): + pagination_class = None + permission_classes = (permissions.AllowAny,) + serializer_class = serializers.TagBaseSerializer + + def get_queryset(self): + return models.Tag.objects\ + .filter_chosen() \ + .order_by_priority() + + # User`s views & viewsets class TagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): """ViewSet for TagCategory model.""" From 2756529c7f95cd3df1f22be07a4d49415c201d0b Mon Sep 17 00:00:00 2001 From: "a.feteleu" Date: Thu, 24 Oct 2019 11:34:00 +0300 Subject: [PATCH 87/87] fix migrations in userroles --- .../migrations/0016_auto_20191024_0830.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 apps/account/migrations/0016_auto_20191024_0830.py diff --git a/apps/account/migrations/0016_auto_20191024_0830.py b/apps/account/migrations/0016_auto_20191024_0830.py new file mode 100644 index 00000000..63373499 --- /dev/null +++ b/apps/account/migrations/0016_auto_20191024_0830.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2.4 on 2019-10-24 08:30 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0015_merge_20191023_1317'), + ] + + operations = [ + migrations.AlterField( + model_name='role', + name='role', + field=models.PositiveIntegerField(choices=[(1, 'Standard user'), (2, 'Comments moderator'), (3, 'Country admin'), (4, 'Content page manager'), (5, 'Establishment manager'), (6, 'Reviewer manager'), (7, 'Restaurant reviewer')], verbose_name='Role'), + ), + migrations.AlterField( + model_name='userrole', + name='establishment', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='establishment.Establishment', verbose_name='Establishment'), + ), + ]