From d2075a1bf8e555f55272d2b3e5a381f1271a204b Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Fri, 20 Sep 2019 12:13:26 +0300 Subject: [PATCH 001/223] 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 002/223] 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 003/223] 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 004/223] 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 005/223] 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 006/223] 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 007/223] 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 008/223] 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 009/223] 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 010/223] 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 011/223] 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 012/223] 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 013/223] 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 014/223] 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 015/223] 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 016/223] 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 017/223] 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 018/223] 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 d321866dc21b45eb2205930cd4a01098f7f3e132 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Tue, 8 Oct 2019 15:38:35 +0300 Subject: [PATCH 019/223] gm-192: in progress --- apps/account/views/web.py | 3 +- apps/establishment/admin.py | 22 +++-- ...ishmenttag_establishmenttypetagcategory.py | 39 ++++++++ apps/establishment/models.py | 62 +++++++++++- apps/establishment/serializers/back.py | 17 ++++ apps/establishment/serializers/common.py | 64 ++++++++----- apps/establishment/urls/back.py | 6 ++ apps/establishment/urls/web.py | 2 +- apps/establishment/views/back.py | 94 ++++++++++++++++++- apps/establishment/views/web.py | 36 +------ apps/main/admin.py | 16 ---- apps/main/models.py | 1 - .../search_indexes/documents/establishment.py | 23 ++--- apps/tag/__init__.py | 0 apps/tag/admin.py | 12 +++ apps/tag/apps.py | 7 ++ apps/tag/migrations/0001_initial.py | 44 +++++++++ apps/tag/migrations/__init__.py | 0 apps/tag/models.py | 51 ++++++++++ apps/tag/serializers.py | 44 +++++++++ apps/tag/tests.py | 3 + apps/tag/urls.py | 10 ++ apps/tag/views.py | 18 ++++ project/settings/base.py | 1 + project/urls/back.py | 6 +- 25 files changed, 477 insertions(+), 104 deletions(-) create mode 100644 apps/establishment/migrations/0032_establishmenttag_establishmenttypetagcategory.py create mode 100644 apps/tag/__init__.py create mode 100644 apps/tag/admin.py create mode 100644 apps/tag/apps.py create mode 100644 apps/tag/migrations/0001_initial.py create mode 100644 apps/tag/migrations/__init__.py create mode 100644 apps/tag/models.py create mode 100644 apps/tag/serializers.py create mode 100644 apps/tag/tests.py create mode 100644 apps/tag/urls.py create mode 100644 apps/tag/views.py diff --git a/apps/account/views/web.py b/apps/account/views/web.py index 897c955e..9f2ebcfd 100644 --- a/apps/account/views/web.py +++ b/apps/account/views/web.py @@ -40,8 +40,7 @@ class PasswordResetConfirmView(JWTGenericViewMixin): queryset = models.User.objects.active() def get_object(self): - """Override get_object method - """ + """Override get_object method""" queryset = self.filter_queryset(self.get_queryset()) uidb64 = self.kwargs.get('uidb64') diff --git a/apps/establishment/admin.py b/apps/establishment/admin.py index f95dd5c8..a40475b0 100644 --- a/apps/establishment/admin.py +++ b/apps/establishment/admin.py @@ -5,7 +5,7 @@ from django.utils.translation import gettext_lazy as _ from comment.models import Comment from establishment import models -from main.models import Award, MetaDataContent +from main.models import Award from review import models as review_models @@ -24,11 +24,6 @@ class AwardInline(GenericTabularInline): extra = 0 -class MetaDataContentInline(GenericTabularInline): - model = MetaDataContent - extra = 0 - - class ContactPhoneInline(admin.TabularInline): """Contact phone inline admin.""" model = models.ContactPhone @@ -56,8 +51,7 @@ class EstablishmentAdmin(admin.ModelAdmin): """Establishment admin.""" list_display = ['id', '__str__', 'image_tag', ] inlines = [ - AwardInline, MetaDataContentInline, - ContactPhoneInline, ContactEmailInline, + AwardInline, ContactPhoneInline, ContactEmailInline, ReviewInline, CommentInline] @@ -84,4 +78,14 @@ class MenuAdmin(admin.ModelAdmin): """Get user's short name.""" return obj.category_translated - category_translated.short_description = _('category') \ No newline at end of file + category_translated.short_description = _('category') + + +@admin.register(models.EstablishmentTypeTagCategory) +class EstablishmentTypeTagCategory(admin.ModelAdmin): + """EstablishmentTypeTagCategory admin.""" + + +@admin.register(models.EstablishmentTag) +class EstablishmentTag(admin.ModelAdmin): + """EstablishmentTag admin.""" diff --git a/apps/establishment/migrations/0032_establishmenttag_establishmenttypetagcategory.py b/apps/establishment/migrations/0032_establishmenttag_establishmenttypetagcategory.py new file mode 100644 index 00000000..6efaa57a --- /dev/null +++ b/apps/establishment/migrations/0032_establishmenttag_establishmenttypetagcategory.py @@ -0,0 +1,39 @@ +# Generated by Django 2.2.4 on 2019-10-08 07:47 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('tag', '0001_initial'), + ('establishment', '0031_establishment_slug'), + ] + + operations = [ + migrations.CreateModel( + name='EstablishmentTypeTagCategory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('establishment_type', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tag_categories', to='establishment.EstablishmentType', verbose_name='establishment type')), + ('tag_category', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='est_type_tag_categories', to='tag.TagCategory', verbose_name='tag category')), + ], + options={ + 'verbose_name': 'establishment type tag categories', + 'verbose_name_plural': 'establishment type tag categories', + }, + ), + migrations.CreateModel( + name='EstablishmentTag', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('establishment', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tags', to='establishment.Establishment', verbose_name='establishment')), + ('tag', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tags', to='tag.Tag', verbose_name='tag')), + ], + options={ + 'verbose_name': 'establishment tag', + 'verbose_name_plural': 'establishment tags', + }, + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 5c2a0ff0..9ce4eebc 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -76,11 +76,13 @@ class EstablishmentQuerySet(models.QuerySet): def with_base_related(self): """Return qs with related objects.""" - return self.select_related('address').prefetch_related( - models.Prefetch('tags', - MetaDataContent.objects.select_related( - 'metadata__category')) - ) + return self.select_related('address') + # todo: fix this + # return self.select_related('address').prefetch_related( + # models.Prefetch('tags', + # MetaDataContent.objects.select_related( + # 'metadata__category')) + # ) def with_extended_related(self): return self.select_related('establishment_type').\ @@ -546,3 +548,53 @@ class SocialNetwork(models.Model): def __str__(self): return self.title + + +class EstablishmentTagQuerySet(models.QuerySet): + """Establishment tag QuerySet.""" + + def by_country_code(self, code): + """Return establishment tags by establishment country code.""" + return self.filter(establishment__address__city__country__code=code) + + +class EstablishmentTag(models.Model): + """Establishment tag model.""" + tag = models.ForeignKey('tag.Tag', + on_delete=models.SET_NULL, null=True, + related_name='tags', + verbose_name=_('tag')) + establishment = models.ForeignKey('establishment.Establishment', + on_delete=models.SET_NULL, null=True, + related_name='tags', + verbose_name=_('establishment')) + objects = EstablishmentTagQuerySet.as_manager() + + class Meta: + verbose_name = _('establishment tag') + verbose_name_plural = _('establishment tags') + + +class EstablishmentTypeTagCategoryQuerySet(models.QuerySet): + """EstablishmentTypeTagCategory QuerySet.""" + + def by_country_code(self, code): + """Return establishment tags by country code""" + return self.filter(tag_category__country__code=code) + + +class EstablishmentTypeTagCategory(models.Model): + """Tag categories based on establishment type.""" + establishment_type = models.ForeignKey(EstablishmentType, + on_delete=models.SET_NULL, null=True, + related_name='tag_categories', + verbose_name=_('establishment type')) + tag_category = models.ForeignKey('tag.TagCategory', + on_delete=models.SET_NULL, null=True, + related_name='est_type_tag_categories', + verbose_name=_('tag category')) + objects = EstablishmentTypeTagCategoryQuerySet.as_manager() + + class Meta: + verbose_name = _('establishment type tag categories') + verbose_name_plural = _('establishment type tag categories') diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index d0c70b2f..b15ff51f 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -1,4 +1,5 @@ from rest_framework import serializers +from tag.serializers import TagBaseSerializer from establishment import models from establishment.serializers import ( EstablishmentBaseSerializer, PlateSerializer, ContactEmailsSerializer, @@ -6,6 +7,7 @@ from establishment.serializers import ( EstablishmentTypeSerializer) from utils.decorators import with_base_attributes +from utils.serializers import TranslatedField from main.models import Currency @@ -74,6 +76,21 @@ class EstablishmentRUDSerializer(EstablishmentBaseSerializer): ] +class EstablishmentTagCategoryListSerializer(serializers.ModelSerializer): + """Serializer for intermediate model EstablishmentTypeTagCategories.""" + label_translated = TranslatedField(source='tag_category.label_translated') + tags = TagBaseSerializer(source='tag_category.tags', many=True) + + class Meta: + """Meta class.""" + model = models.EstablishmentTypeTagCategory + fields = [ + 'id', + 'label_translated', + 'tags', + ] + + class SocialNetworkSerializers(serializers.ModelSerializer): """Social network serializers.""" class Meta: diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index f09c8200..6c12360b 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -6,8 +6,8 @@ from comment.serializers import common as comment_serializers from establishment import models from favorites.models import Favorites from location.serializers import AddressBaseSerializer -from main.models import MetaDataContent -from main.serializers import MetaDataContentSerializer, AwardSerializer, CurrencySerializer +from main.serializers import AwardSerializer, CurrencySerializer +from tag import models as tag_models from review import models as review_models from timetable.serialziers import ScheduleRUDSerializer from utils import exceptions as utils_exceptions @@ -89,25 +89,32 @@ class MenuRUDSerializers(ProjectModelSerializer): class EstablishmentTypeSerializer(serializers.ModelSerializer): """Serializer for EstablishmentType model.""" - name_translated = serializers.CharField(allow_null=True) + name_translated = TranslatedField() class Meta: """Meta class.""" model = models.EstablishmentType - fields = ('id', 'name_translated') + fields = ('id', 'name', 'name_translated') + extra_kwargs = { + 'name': {'write_only': True} + } class EstablishmentSubTypeSerializer(serializers.ModelSerializer): """Serializer for EstablishmentSubType models.""" - name_translated = serializers.CharField(allow_null=True) + name_translated = TranslatedField() class Meta: """Meta class.""" model = models.EstablishmentSubType - fields = ('id', 'name_translated') + fields = ('id', 'name', 'name_translated', 'establishment_type') + extra_kwargs = { + 'name': {'write_only': True}, + 'establishment_type': {'write_only': True} + } class ReviewSerializer(serializers.ModelSerializer): @@ -138,14 +145,42 @@ class EstablishmentEmployeeSerializer(serializers.ModelSerializer): fields = ('id', 'name', 'position_translated', 'awards', 'priority') +class EstablishmentCategoryTagListSerializer(serializers.ModelSerializer): + """Serializer for establishment category tags.""" + label_translated = TranslatedField() + + class Meta: + """Meta class.""" + model = tag_models.TagCategory + fields = [ + 'id', + 'label_translated', + ] + + +class EstablishmentTagListSerializer(serializers.ModelSerializer): + """Serializer for establishment tags.""" + label_translated = TranslatedField(source='tag.label_translated') + category = EstablishmentCategoryTagListSerializer(source='tag.category') + + class Meta: + """Meta class.""" + model = tag_models.Tag + fields = [ + 'id', + 'label_translated', + 'category', + ] + + class EstablishmentBaseSerializer(ProjectModelSerializer): """Base serializer for Establishment model.""" preview_image = serializers.URLField(source='preview_image_url') slug = serializers.SlugField(allow_blank=False, required=True, max_length=50) address = AddressBaseSerializer() - tags = MetaDataContentSerializer(many=True) in_favorites = serializers.BooleanField(allow_null=True) + tags = EstablishmentTagListSerializer(many=True) class Meta: """Meta class.""" @@ -305,18 +340,3 @@ class EstablishmentFavoritesCreateSerializer(serializers.ModelSerializer): 'content_object': validated_data.pop('establishment') }) return super().create(validated_data) - - -class EstablishmentTagListSerializer(serializers.ModelSerializer): - """List establishment tag serializer.""" - id = serializers.IntegerField(source='metadata.id') - label_translated = serializers.CharField( - source='metadata.label_translated', read_only=True, allow_null=True) - - class Meta: - """Meta class.""" - model = MetaDataContent - fields = [ - 'id', - 'label_translated', - ] diff --git a/apps/establishment/urls/back.py b/apps/establishment/urls/back.py index dca5fb55..b04a6843 100644 --- a/apps/establishment/urls/back.py +++ b/apps/establishment/urls/back.py @@ -14,6 +14,8 @@ urlpatterns = [ name='schedule-rud'), path('/schedule/', views.EstablishmentScheduleCreateView.as_view(), name='schedule-create'), + path('/tags/categories/', views.EstablishmentTagCategoryListView.as_view(), + name='tag-category-list'), path('menus/', views.MenuListCreateView.as_view(), name='menu-list'), path('menus//', views.MenuRUDView.as_view(), name='menu-rud'), path('plates/', views.PlateListCreateView.as_view(), name='plates'), @@ -26,4 +28,8 @@ urlpatterns = [ path('emails//', views.EmailRUDView.as_view(), name='emails-rud'), path('employees/', views.EmployeeListCreateView.as_view(), name='employees'), path('employees//', views.EmployeeRUDView.as_view(), name='employees-rud'), + path('types/', views.EstablishmentTypeListCreateView.as_view(), name='type-list'), + path('types//', views.EstablishmentTypeRUDView.as_view(), name='type-rud'), + path('subtypes/', views.EstablishmentSubtypeListCreateView.as_view(), name='subtype-list'), + path('subtypes//', views.EstablishmentSubtypeRUDView.as_view(), name='subtype-rud'), ] \ No newline at end of file diff --git a/apps/establishment/urls/web.py b/apps/establishment/urls/web.py index b732d171..b4d1942d 100644 --- a/apps/establishment/urls/web.py +++ b/apps/establishment/urls/web.py @@ -4,4 +4,4 @@ from establishment.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/establishment/views/back.py b/apps/establishment/views/back.py index 5cba8255..64887fb3 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -1,9 +1,9 @@ """Establishment app views.""" - +from django.shortcuts import get_object_or_404 from rest_framework import generics -from establishment import models -from establishment import serializers +from establishment import models, serializers +from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer class EstablishmentMixinViews: @@ -25,6 +25,62 @@ class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView): serializer_class = serializers.EstablishmentRUDSerializer +class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView): + """Establishment schedule RUD view""" + serializer_class = ScheduleRUDSerializer + + def get_object(self): + """ + Returns the object the view is displaying. + """ + establishment_pk = self.kwargs['pk'] + schedule_id = self.kwargs['schedule_id'] + + establishment = get_object_or_404(klass=models.Establishment.objects.all(), + pk=establishment_pk) + schedule = get_object_or_404(klass=establishment.schedule, + id=schedule_id) + + # May raise a permission denied + self.check_object_permissions(self.request, establishment) + self.check_object_permissions(self.request, schedule) + + return schedule + + +class EstablishmentScheduleCreateView(generics.CreateAPIView): + """Establishment schedule Create view""" + serializer_class = ScheduleCreateSerializer + + +class EstablishmentTagCategoryListView(EstablishmentMixinViews, generics.ListAPIView): + """View for establishment tag categories.""" + serializer_class = serializers.EstablishmentTagCategoryListSerializer + pagination_class = None + + def get_object(self): + """ + Returns the object the view is displaying. + """ + queryset = super(EstablishmentTagCategoryListView, self).get_queryset() + + # Perform the lookup filtering. + lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field + + filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]} + obj = get_object_or_404(queryset, **filter_kwargs) + + # May raise a permission denied + self.check_object_permissions(self.request, obj) + + return obj + + def get_queryset(self): + """Overridden get_queryset method.""" + establishment = self.get_object() + return establishment.establishment_type.tag_categories.all() + + class MenuListCreateView(generics.ListCreateAPIView): """Menu list create view.""" serializer_class = serializers.MenuSerializers @@ -100,3 +156,35 @@ class EmployeeRUDView(generics.RetrieveUpdateDestroyAPIView): """Social RUD view.""" serializer_class = serializers.EmployeeBackSerializers queryset = models.Employee.objects.all() + + +class EstablishmentTypeListCreateView(generics.ListCreateAPIView): + """Establishment type list/create view.""" + serializer_class = serializers.EstablishmentTypeSerializer + queryset = models.EstablishmentType.objects.all() + pagination_class = None + + +class EstablishmentTypeRUDView(generics.RetrieveUpdateDestroyAPIView): + """Establishment type retrieve/update/destroy view.""" + serializer_class = serializers.EstablishmentTypeSerializer + queryset = models.EstablishmentType.objects.all() + + +class EstablishmentSubtypeListCreateView(generics.ListCreateAPIView): + """Establishment subtype list/create view.""" + serializer_class = serializers.EstablishmentSubTypeSerializer + queryset = models.EstablishmentSubType.objects.all() + pagination_class = None + + +class EstablishmentSubtypeRUDView(generics.RetrieveUpdateDestroyAPIView): + """Establishment subtype retrieve/update/destroy view.""" + serializer_class = serializers.EstablishmentSubTypeSerializer + queryset = models.EstablishmentSubType.objects.all() + + +class EstablishmentTagListCreateView(generics.ListCreateAPIView): + """Establishment tag list/create view.""" + serializer_class = serializers.EstablishmentTagListSerializer + queryset = models.EstablishmentTag.objects.all() diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index 8f5d2a26..d65ede11 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -9,7 +9,6 @@ from establishment import filters from establishment import models, serializers from main import methods from main.models import MetaDataContent -from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer from utils.pagination import EstablishmentPortionPagination @@ -19,9 +18,10 @@ class EstablishmentMixinView: permission_classes = (permissions.AllowAny,) def get_queryset(self): - """Overrided method 'get_queryset'.""" - return models.Establishment.objects.published().with_base_related().\ - annotate_in_favorites(self.request.user) + """Overridden method 'get_queryset'.""" + return models.Establishment.objects.published() \ + .with_base_related() \ + .annotate_in_favorites(self.request.user) class EstablishmentListView(EstablishmentMixinView, generics.ListAPIView): @@ -187,31 +187,3 @@ class EstablishmentTagListView(generics.ListAPIView): return MetaDataContent.objects.by_content_type(app_label='establishment', model='establishment')\ .distinct('metadata__label') - - -class EstablishmentScheduleRUDView(generics.RetrieveUpdateDestroyAPIView): - """Establishment schedule RUD view""" - serializer_class = ScheduleRUDSerializer - - def get_object(self): - """ - Returns the object the view is displaying. - """ - establishment_pk = self.kwargs['pk'] - schedule_id = self.kwargs['schedule_id'] - - establishment = get_object_or_404(klass=models.Establishment.objects.all(), - pk=establishment_pk) - schedule = get_object_or_404(klass=establishment.schedule, - id=schedule_id) - - # May raise a permission denied - self.check_object_permissions(self.request, establishment) - self.check_object_permissions(self.request, schedule) - - return schedule - - -class EstablishmentScheduleCreateView(generics.CreateAPIView): - """Establishment schedule Create view""" - serializer_class = ScheduleCreateSerializer diff --git a/apps/main/admin.py b/apps/main/admin.py index bdbfe46e..f14a3470 100644 --- a/apps/main/admin.py +++ b/apps/main/admin.py @@ -25,22 +25,6 @@ class AwardAdmin(admin.ModelAdmin): # list_display_links = ['id', '__str__'] -@admin.register(models.MetaData) -class MetaDataAdmin(admin.ModelAdmin): - """MetaData admin.""" - - -@admin.register(models.MetaDataCategory) -class MetaDataCategoryAdmin(admin.ModelAdmin): - """MetaData admin.""" - list_display = ['id', 'country', 'content_type'] - - -@admin.register(models.MetaDataContent) -class MetaDataContentAdmin(admin.ModelAdmin): - """MetaDataContent admin""" - - @admin.register(models.Currency) class CurrencContentAdmin(admin.ModelAdmin): """CurrencContent admin""" 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/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index 2d43154e..3a06d4ed 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -30,17 +30,18 @@ class EstablishmentDocument(Document): properties=OBJECT_FIELD_PROPERTIES) }, multi=True) - tags = fields.ObjectField( - properties={ - 'id': fields.IntegerField(attr='metadata.id'), - 'label': fields.ObjectField(attr='metadata.label_indexing', - properties=OBJECT_FIELD_PROPERTIES), - 'category': fields.ObjectField(attr='metadata.category', - properties={ - 'id': fields.IntegerField(), - }) - }, - multi=True) + # todo: need to fix + # tags = fields.ObjectField( + # properties={ + # 'id': fields.IntegerField(attr='metadata.id'), + # 'label': fields.ObjectField(attr='metadata.label_indexing', + # properties=OBJECT_FIELD_PROPERTIES), + # 'category': fields.ObjectField(attr='metadata.category', + # properties={ + # 'id': fields.IntegerField(), + # }) + # }, + # multi=True) address = fields.ObjectField( properties={ 'id': fields.IntegerField(), diff --git a/apps/tag/__init__.py b/apps/tag/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/tag/admin.py b/apps/tag/admin.py new file mode 100644 index 00000000..ea7f9394 --- /dev/null +++ b/apps/tag/admin.py @@ -0,0 +1,12 @@ +from django.contrib import admin +from .models import Tag, TagCategory + + +@admin.register(Tag) +class TagAdmin(admin.ModelAdmin): + """Admin model for model Tag.""" + + +@admin.register(TagCategory) +class TagCategoryAdmin(admin.ModelAdmin): + """Admin model for model TagCategory.""" diff --git a/apps/tag/apps.py b/apps/tag/apps.py new file mode 100644 index 00000000..a1cce249 --- /dev/null +++ b/apps/tag/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class TagConfig(AppConfig): + name = 'tag' + verbose_name = _('tag') diff --git a/apps/tag/migrations/0001_initial.py b/apps/tag/migrations/0001_initial.py new file mode 100644 index 00000000..1c146570 --- /dev/null +++ b/apps/tag/migrations/0001_initial.py @@ -0,0 +1,44 @@ +# Generated by Django 2.2.4 on 2019-10-08 07:47 + +from django.db import migrations, models +import django.db.models.deletion +import utils.models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('location', '0010_auto_20190904_0711'), + ] + + operations = [ + migrations.CreateModel( + name='TagCategory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('label', utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='label')), + ('public', models.BooleanField(default=False)), + ('country', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='location.Country')), + ], + options={ + 'verbose_name': 'tag category', + 'verbose_name_plural': 'tag categories', + }, + bases=(utils.models.TranslatedFieldsMixin, models.Model), + ), + migrations.CreateModel( + name='Tag', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('label', utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='label')), + ('category', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tags', to='tag.TagCategory', verbose_name='category')), + ], + options={ + 'verbose_name': 'tag', + 'verbose_name_plural': 'tags', + }, + bases=(utils.models.TranslatedFieldsMixin, models.Model), + ), + ] diff --git a/apps/tag/migrations/__init__.py b/apps/tag/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/tag/models.py b/apps/tag/models.py new file mode 100644 index 00000000..c269d64e --- /dev/null +++ b/apps/tag/models.py @@ -0,0 +1,51 @@ +"""Tag app models.""" +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from configuration.models import TranslationSettings +from utils.models import TJSONField, TranslatedFieldsMixin + + +class Tag(TranslatedFieldsMixin, models.Model): + """Tag model.""" + label = TJSONField( + _('label'), null=True, blank=True, + default=None, help_text='{"en-GB":"some text"}') + category = models.ForeignKey('TagCategory', + on_delete=models.SET_NULL, null=True, + related_name='tags', + verbose_name='category') + + class Meta: + verbose_name = _('tag') + verbose_name_plural = _('tags') + + 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 TagCategory(TranslatedFieldsMixin, models.Model): + """Tag base category model.""" + label = TJSONField( + _('label'), null=True, blank=True, + default=None, help_text='{"en-GB":"some text"}') + country = models.ForeignKey('location.Country', + on_delete=models.SET_NULL, null=True, + default=None) + + public = models.BooleanField(default=False) + + class Meta: + verbose_name = _('tag category') + verbose_name_plural = _('tag categories') + + 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}' diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py new file mode 100644 index 00000000..cf783910 --- /dev/null +++ b/apps/tag/serializers.py @@ -0,0 +1,44 @@ +"""Tag serializers.""" +from rest_framework import serializers +from . import models +from utils.serializers import TranslatedField + + +class TagBaseSerializer(serializers.ModelSerializer): + """Serializer for model Tag.""" + label_translated = TranslatedField() + + class Meta: + model = models.Tag + fields = [ + 'id', + 'label', + 'label_translated', + 'category' + ] + extra_kwargs = { + 'label': {'write_only': True}, + 'category': {'write_only': True} + } + + +class TagCategoryBaseSerializer(serializers.ModelSerializer): + """Serializer for model TagCategory.""" + label_translated = TranslatedField() + country_translated = TranslatedField(source='country.name_translated') + + class Meta: + """Meta class.""" + model = models.TagCategory + fields = [ + 'id', + 'label', + 'label_translated', + 'country', + 'country_translated', + 'public', + ] + extra_kwargs = { + 'label': {'write_only': True}, + 'country': {'write_only': True}, + } diff --git a/apps/tag/tests.py b/apps/tag/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/apps/tag/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/tag/urls.py b/apps/tag/urls.py new file mode 100644 index 00000000..0d0b86d2 --- /dev/null +++ b/apps/tag/urls.py @@ -0,0 +1,10 @@ +"""Urlconf for app tag.""" +from django.urls import path +from . import views + +app_name = 'tag' + +urlpatterns = [ + path('', views.TagListCreateView.as_view(), name='list-create'), + path('category/', views.TagCategoryListCreateView.as_view(), name='category-list-create'), +] diff --git a/apps/tag/views.py b/apps/tag/views.py new file mode 100644 index 00000000..ab944ba7 --- /dev/null +++ b/apps/tag/views.py @@ -0,0 +1,18 @@ +"""Tag views.""" +from rest_framework import generics + +from . import serializers, models + + +class TagListCreateView(generics.ListCreateAPIView): + """List/create tag view.""" + queryset = models.Tag.objects.all() + serializer_class = serializers.TagBaseSerializer + pagination_class = None + + +class TagCategoryListCreateView(generics.ListCreateAPIView): + """List/create tag category view.""" + queryset = models.TagCategory.objects.all() + serializer_class = serializers.TagCategoryBaseSerializer + pagination_class = None diff --git a/project/settings/base.py b/project/settings/base.py index cec6a4d1..8f02f52d 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -72,6 +72,7 @@ PROJECT_APPS = [ 'comment.apps.CommentConfig', 'favorites.apps.FavoritesConfig', 'rating.apps.RatingConfig', + 'tag.apps.TagConfig', ] EXTERNAL_APPS = [ diff --git a/project/urls/back.py b/project/urls/back.py index 7b4146eb..2a9657db 100644 --- a/project/urls/back.py +++ b/project/urls/back.py @@ -7,5 +7,7 @@ urlpatterns = [ namespace='gallery')), path('establishments/', include('establishment.urls.back')), path('location/', include('location.urls.back')), - path('news/', include('news.urls.back')) -] \ No newline at end of file + path('news/', include('news.urls.back')), + path('tags/', include(('tag.urls', 'tag'), + namespace='tag')) +] From 69d02e7a07eb573d03f6dea0210379677ac2067a Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 9 Oct 2019 10:05:19 +0300 Subject: [PATCH 020/223] fix auth --- apps/utils/authentication.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/apps/utils/authentication.py b/apps/utils/authentication.py index 044d6d75..e8375ffe 100644 --- a/apps/utils/authentication.py +++ b/apps/utils/authentication.py @@ -23,14 +23,24 @@ class GMJWTAuthentication(JWTAuthentication): """ def authenticate(self, request): - token = get_token_from_cookies(request) - if token is None: + try: + token = get_token_from_cookies(request) + # Return non-authorized user if token not in cookies + assert token + + raw_token = self.get_raw_token(token) + # Return non-authorized user if cant get raw token + assert raw_token + + validated_token = self.get_validated_token(raw_token) + user = self.get_user(validated_token) + + # Check record in DB + token_is_valid = user.access_tokens.valid() \ + .by_jti(jti=validated_token.payload.get('jti')) + assert token_is_valid.exists() + except: + # Return non-authorized user if token is invalid or raised an error when run checks. return None - - raw_token = self.get_raw_token(token) - if raw_token is None: - return None - - validated_token = self.get_validated_token(raw_token) - - return self.get_user(validated_token), None + else: + return user, None From ca5f588466602381dc5a8bf0e0a8ff85f523c567 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 9 Oct 2019 12:53:35 +0300 Subject: [PATCH 021/223] fix elastic document, fix prefetch and select related --- apps/authorization/tasks.py | 4 +-- ...ishmenttag_establishmenttypetagcategory.py | 28 +++++++--------- .../migrations/0033_auto_20191009_0715.py | 30 +++++++++++++++++ apps/establishment/models.py | 22 ++++++------- apps/establishment/serializers/back.py | 1 + .../search_indexes/documents/establishment.py | 32 +++++++++++-------- apps/tag/migrations/0001_initial.py | 2 +- project/settings/base.py | 6 ++-- 8 files changed, 78 insertions(+), 47 deletions(-) create mode 100644 apps/establishment/migrations/0033_auto_20191009_0715.py diff --git a/apps/authorization/tasks.py b/apps/authorization/tasks.py index c97fbbae..cb186142 100644 --- a/apps/authorization/tasks.py +++ b/apps/authorization/tasks.py @@ -1,10 +1,10 @@ """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 -from smtplib import SMTPException logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.INFO) logger = logging.getLogger(__name__) diff --git a/apps/establishment/migrations/0032_establishmenttag_establishmenttypetagcategory.py b/apps/establishment/migrations/0032_establishmenttag_establishmenttypetagcategory.py index 6efaa57a..ec9966d8 100644 --- a/apps/establishment/migrations/0032_establishmenttag_establishmenttypetagcategory.py +++ b/apps/establishment/migrations/0032_establishmenttag_establishmenttypetagcategory.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.4 on 2019-10-08 07:47 +# Generated by Django 2.2.4 on 2019-10-09 07:15 from django.db import migrations, models import django.db.models.deletion @@ -7,33 +7,29 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('tag', '0001_initial'), ('establishment', '0031_establishment_slug'), ] operations = [ - migrations.CreateModel( - name='EstablishmentTypeTagCategory', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('establishment_type', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tag_categories', to='establishment.EstablishmentType', verbose_name='establishment type')), - ('tag_category', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='est_type_tag_categories', to='tag.TagCategory', verbose_name='tag category')), - ], - options={ - 'verbose_name': 'establishment type tag categories', - 'verbose_name_plural': 'establishment type tag categories', - }, - ), migrations.CreateModel( name='EstablishmentTag', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('establishment', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tags', to='establishment.Establishment', verbose_name='establishment')), - ('tag', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tags', to='tag.Tag', verbose_name='tag')), ], options={ 'verbose_name': 'establishment tag', 'verbose_name_plural': 'establishment tags', }, ), + migrations.CreateModel( + name='EstablishmentTypeTagCategory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('establishment_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tag_categories', to='establishment.EstablishmentType', verbose_name='establishment type')), + ], + options={ + 'verbose_name': 'establishment type tag categories', + 'verbose_name_plural': 'establishment type tag categories', + }, + ), ] diff --git a/apps/establishment/migrations/0033_auto_20191009_0715.py b/apps/establishment/migrations/0033_auto_20191009_0715.py new file mode 100644 index 00000000..5df367d6 --- /dev/null +++ b/apps/establishment/migrations/0033_auto_20191009_0715.py @@ -0,0 +1,30 @@ +# Generated by Django 2.2.4 on 2019-10-09 07:15 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('tag', '0001_initial'), + ('establishment', '0032_establishmenttag_establishmenttypetagcategory'), + ] + + operations = [ + migrations.AddField( + model_name='establishmenttypetagcategory', + name='tag_category', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='est_type_tag_categories', to='tag.TagCategory', verbose_name='tag category'), + ), + migrations.AddField( + model_name='establishmenttag', + name='establishment', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tags', to='establishment.Establishment', verbose_name='establishment'), + ), + migrations.AddField( + model_name='establishmenttag', + name='tag', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tags', to='tag.Tag', verbose_name='tag'), + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 9ce4eebc..30ca88bd 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -15,6 +15,7 @@ from elasticsearch_dsl import Q from phonenumber_field.modelfields import PhoneNumberField from collection.models import Collection +from tag.models import Tag, TagCategory from main.models import Award, MetaDataContent from location.models import Address from review.models import Review @@ -76,13 +77,12 @@ class EstablishmentQuerySet(models.QuerySet): def with_base_related(self): """Return qs with related objects.""" - return self.select_related('address') - # todo: fix this - # return self.select_related('address').prefetch_related( - # models.Prefetch('tags', - # MetaDataContent.objects.select_related( - # 'metadata__category')) - # ) + return self.select_related('address', 'establishment_type').prefetch_related( + models.Prefetch('tags', + EstablishmentTag.objects.select_related('tag')), + models.Prefetch('establishment_type__tag_categories', + EstablishmentTypeTagCategory.objects.select_related('tag_category')), + ) def with_extended_related(self): return self.select_related('establishment_type').\ @@ -561,11 +561,11 @@ class EstablishmentTagQuerySet(models.QuerySet): class EstablishmentTag(models.Model): """Establishment tag model.""" tag = models.ForeignKey('tag.Tag', - on_delete=models.SET_NULL, null=True, + on_delete=models.CASCADE, related_name='tags', verbose_name=_('tag')) establishment = models.ForeignKey('establishment.Establishment', - on_delete=models.SET_NULL, null=True, + on_delete=models.CASCADE, related_name='tags', verbose_name=_('establishment')) objects = EstablishmentTagQuerySet.as_manager() @@ -586,11 +586,11 @@ class EstablishmentTypeTagCategoryQuerySet(models.QuerySet): class EstablishmentTypeTagCategory(models.Model): """Tag categories based on establishment type.""" establishment_type = models.ForeignKey(EstablishmentType, - on_delete=models.SET_NULL, null=True, + on_delete=models.CASCADE, related_name='tag_categories', verbose_name=_('establishment type')) tag_category = models.ForeignKey('tag.TagCategory', - on_delete=models.SET_NULL, null=True, + on_delete=models.CASCADE, related_name='est_type_tag_categories', verbose_name=_('tag category')) objects = EstablishmentTypeTagCategoryQuerySet.as_manager() diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index b15ff51f..766806ad 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -78,6 +78,7 @@ class EstablishmentRUDSerializer(EstablishmentBaseSerializer): class EstablishmentTagCategoryListSerializer(serializers.ModelSerializer): """Serializer for intermediate model EstablishmentTypeTagCategories.""" + id = serializers.IntegerField(source='tag_category.id') label_translated = TranslatedField(source='tag_category.label_translated') tags = TagBaseSerializer(source='tag_category.tags', many=True) diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index 3a06d4ed..e2147fbc 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -21,27 +21,31 @@ class EstablishmentDocument(Document): properties={ 'id': fields.IntegerField(), 'name': fields.ObjectField(attr='name_indexing', - properties=OBJECT_FIELD_PROPERTIES) + properties=OBJECT_FIELD_PROPERTIES), + 'tag_categories': fields.ObjectField(properties={ + 'tag_category': fields.ObjectField( + properties={ + 'id': fields.IntegerField() + } + ) + }), }) establishment_subtypes = fields.ObjectField( properties={ 'id': fields.IntegerField(), 'name': fields.ObjectField(attr='name_indexing', - properties=OBJECT_FIELD_PROPERTIES) + properties={ + 'id': fields.IntegerField(), + }), + }, + multi=True) + tags = fields.ObjectField( + properties={ + 'tag': fields.ObjectField(properties={ + 'id': fields.IntegerField(), + }), }, multi=True) - # todo: need to fix - # tags = fields.ObjectField( - # properties={ - # 'id': fields.IntegerField(attr='metadata.id'), - # 'label': fields.ObjectField(attr='metadata.label_indexing', - # properties=OBJECT_FIELD_PROPERTIES), - # 'category': fields.ObjectField(attr='metadata.category', - # properties={ - # 'id': fields.IntegerField(), - # }) - # }, - # multi=True) address = fields.ObjectField( properties={ 'id': fields.IntegerField(), diff --git a/apps/tag/migrations/0001_initial.py b/apps/tag/migrations/0001_initial.py index 1c146570..543eb035 100644 --- a/apps/tag/migrations/0001_initial.py +++ b/apps/tag/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.4 on 2019-10-08 07:47 +# Generated by Django 2.2.4 on 2019-10-09 07:15 from django.db import migrations, models import django.db.models.deletion diff --git a/project/settings/base.py b/project/settings/base.py index 8f02f52d..f04788d1 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -282,9 +282,9 @@ SMS_SENDER = 'GM' # EMAIL EMAIL_USE_TLS = True -EMAIL_HOST = 'smtp.yandex.ru' -EMAIL_HOST_USER = 't3st.t3stov.t3stovich@yandex.ru' -EMAIL_HOST_PASSWORD = 'ylhernyutkfbylgk' +EMAIL_HOST = 'smtp.mandrillapp.com' +EMAIL_HOST_USER = 'bbody@gaultmillau.fr' +EMAIL_HOST_PASSWORD = 'FQghjXmS1FmKmlZEpSg6TA' EMAIL_PORT = 587 # Django Rest Swagger From 2191a505a59b6f6202dfcb8bcb03786885e45826 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 9 Oct 2019 14:11:53 +0300 Subject: [PATCH 022/223] refactored EstablishmentBase serializer --- apps/establishment/serializers/back.py | 18 ----------- apps/establishment/serializers/common.py | 38 +++++++++++++++++------- apps/establishment/urls/common.py | 1 - apps/establishment/views/back.py | 9 +++--- apps/establishment/views/web.py | 13 -------- 5 files changed, 33 insertions(+), 46 deletions(-) diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index 766806ad..d0c70b2f 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -1,5 +1,4 @@ from rest_framework import serializers -from tag.serializers import TagBaseSerializer from establishment import models from establishment.serializers import ( EstablishmentBaseSerializer, PlateSerializer, ContactEmailsSerializer, @@ -7,7 +6,6 @@ from establishment.serializers import ( EstablishmentTypeSerializer) from utils.decorators import with_base_attributes -from utils.serializers import TranslatedField from main.models import Currency @@ -76,22 +74,6 @@ class EstablishmentRUDSerializer(EstablishmentBaseSerializer): ] -class EstablishmentTagCategoryListSerializer(serializers.ModelSerializer): - """Serializer for intermediate model EstablishmentTypeTagCategories.""" - id = serializers.IntegerField(source='tag_category.id') - label_translated = TranslatedField(source='tag_category.label_translated') - tags = TagBaseSerializer(source='tag_category.tags', many=True) - - class Meta: - """Meta class.""" - model = models.EstablishmentTypeTagCategory - fields = [ - 'id', - 'label_translated', - 'tags', - ] - - class SocialNetworkSerializers(serializers.ModelSerializer): """Social network serializers.""" class Meta: diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 6c12360b..2cd4e429 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -1,17 +1,20 @@ """Establishment serializers.""" from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers + from comment import models as comment_models from comment.serializers import common as comment_serializers from establishment import models from favorites.models import Favorites from location.serializers import AddressBaseSerializer from main.serializers import AwardSerializer, CurrencySerializer -from tag import models as tag_models from review import models as review_models +from tag import models as tag_models +from tag.serializers import TagBaseSerializer from timetable.serialziers import ScheduleRUDSerializer from utils import exceptions as utils_exceptions -from utils.serializers import TranslatedField, ProjectModelSerializer +from utils.serializers import ProjectModelSerializer +from utils.serializers import TranslatedField class ContactPhonesSerializer(serializers.ModelSerializer): @@ -86,6 +89,22 @@ class MenuRUDSerializers(ProjectModelSerializer): ] +class EstablishmentTagCategoryListSerializer(serializers.ModelSerializer): + """Serializer for intermediate model EstablishmentTypeTagCategories.""" + id = serializers.IntegerField(source='tag_category.id') + label_translated = TranslatedField(source='tag_category.label_translated') + tags = TagBaseSerializer(source='tag_category.tags', many=True) + + class Meta: + """Meta class.""" + model = models.EstablishmentTypeTagCategory + fields = [ + 'id', + 'label_translated', + 'tags', + ] + + class EstablishmentTypeSerializer(serializers.ModelSerializer): """Serializer for EstablishmentType model.""" @@ -158,18 +177,17 @@ class EstablishmentCategoryTagListSerializer(serializers.ModelSerializer): ] -class EstablishmentTagListSerializer(serializers.ModelSerializer): - """Serializer for establishment tags.""" - label_translated = TranslatedField(source='tag.label_translated') - category = EstablishmentCategoryTagListSerializer(source='tag.category') +class EstablishmentTagSerializer(serializers.ModelSerializer): + """Serializer for intermediate model EstablishmentTag.""" + id = serializers.IntegerField(source='tag.id') + label_translated = serializers.CharField(source='tag.label_translated') class Meta: """Meta class.""" - model = tag_models.Tag + model = models.EstablishmentTag fields = [ 'id', - 'label_translated', - 'category', + 'label_translated' ] @@ -180,7 +198,7 @@ class EstablishmentBaseSerializer(ProjectModelSerializer): slug = serializers.SlugField(allow_blank=False, required=True, max_length=50) address = AddressBaseSerializer() in_favorites = serializers.BooleanField(allow_null=True) - tags = EstablishmentTagListSerializer(many=True) + tags = EstablishmentTagSerializer(many=True) class Meta: """Meta class.""" diff --git a/apps/establishment/urls/common.py b/apps/establishment/urls/common.py index 1e9225d6..5d7df146 100644 --- a/apps/establishment/urls/common.py +++ b/apps/establishment/urls/common.py @@ -7,7 +7,6 @@ app_name = 'establishment' urlpatterns = [ path('', views.EstablishmentListView.as_view(), name='list'), - path('tags/', views.EstablishmentTagListView.as_view(), name='tags'), path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(), name='recent-reviews'), path('slug//', views.EstablishmentRetrieveView.as_view(), name='detail'), diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index 64887fb3..32ee2805 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -184,7 +184,8 @@ class EstablishmentSubtypeRUDView(generics.RetrieveUpdateDestroyAPIView): queryset = models.EstablishmentSubType.objects.all() -class EstablishmentTagListCreateView(generics.ListCreateAPIView): - """Establishment tag list/create view.""" - serializer_class = serializers.EstablishmentTagListSerializer - queryset = models.EstablishmentTag.objects.all() +# todo: next task +# class EstablishmentTagListCreateView(generics.CreateAPIView): +# """Establishment tag list/create view.""" +# serializer_class = serializers.EstablishmentTagCategoryListCreateSerializer +# queryset = models.EstablishmentTag.objects.all() diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index d65ede11..b4ef4f13 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -174,16 +174,3 @@ class EstablishmentNearestRetrieveView(EstablishmentListView, generics.ListAPIVi return qs.by_distance_from_point(**{k: v for k, v in filter_kwargs.items() if v is not None}) return qs - - -class EstablishmentTagListView(generics.ListAPIView): - """List view for establishment tags.""" - serializer_class = serializers.EstablishmentTagListSerializer - permission_classes = (permissions.AllowAny,) - pagination_class = None - - def get_queryset(self): - """Override get_queryset method""" - return MetaDataContent.objects.by_content_type(app_label='establishment', - model='establishment')\ - .distinct('metadata__label') From 31f8b5abd12ad2a6b195cc1c1ed21a97eb7168e7 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 9 Oct 2019 15:19:46 +0300 Subject: [PATCH 023/223] refactored base settings --- project/settings/base.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/project/settings/base.py b/project/settings/base.py index cec6a4d1..2a6f4aea 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -275,15 +275,15 @@ SMS_CODE_SHOW = False # SMSC Settings SMS_SERVICE = 'http://smsc.ru/sys/send.php' -SMS_LOGIN = 'GM2019' -SMS_PASSWORD = '}#6%Qe7CYG7n' +SMS_LOGIN = os.environ.get('SMS_LOGIN') +SMS_PASSWORD = os.environ.get('SMS_PASSWORD') SMS_SENDER = 'GM' # EMAIL EMAIL_USE_TLS = True -EMAIL_HOST = 'smtp.yandex.ru' -EMAIL_HOST_USER = 't3st.t3stov.t3stovich@yandex.ru' -EMAIL_HOST_PASSWORD = 'ylhernyutkfbylgk' +EMAIL_HOST = 'smtp.mandrillapp.com' +EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER') +EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD') EMAIL_PORT = 587 # Django Rest Swagger From 93894db7aaedff07e4b60bba57948526201998db Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Wed, 9 Oct 2019 15:20:51 +0300 Subject: [PATCH 024/223] 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 025/223] 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 026/223] 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 027/223] 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 028/223] 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 074a5ec5ea7be3450ec89b79c7b278c8a6a09464 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Wed, 9 Oct 2019 18:25:40 +0300 Subject: [PATCH 029/223] refactor news tags --- .../migrations/0021_auto_20191009_1408.py | 24 ++++++++++ apps/news/models.py | 7 +-- apps/news/serializers.py | 4 +- apps/news/urls/web.py | 2 + apps/news/views.py | 16 +++++++ .../tag/migrations/0002_auto_20191009_1408.py | 27 +++++++++++ apps/tag/models.py | 47 ++++++++++++------- apps/tag/serializers.py | 21 +++++++-- apps/tag/views.py | 5 +- project/urls/back.py | 6 +-- project/urls/web.py | 3 +- 11 files changed, 131 insertions(+), 31 deletions(-) create mode 100644 apps/news/migrations/0021_auto_20191009_1408.py create mode 100644 apps/tag/migrations/0002_auto_20191009_1408.py diff --git a/apps/news/migrations/0021_auto_20191009_1408.py b/apps/news/migrations/0021_auto_20191009_1408.py new file mode 100644 index 00000000..81a4d7fa --- /dev/null +++ b/apps/news/migrations/0021_auto_20191009_1408.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2.4 on 2019-10-09 14:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tag', '0002_auto_20191009_1408'), + ('news', '0020_remove_news_author'), + ] + + operations = [ + migrations.AddField( + model_name='news', + name='tags', + field=models.ManyToManyField(related_name='news', to='tag.Tag', verbose_name='Tags'), + ), + migrations.AddField( + model_name='newstype', + name='tag_categories', + field=models.ManyToManyField(related_name='news_types', to='tag.TagCategory'), + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index 6e91b912..8a6a89f4 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -12,6 +12,8 @@ class NewsType(models.Model): """NewsType model.""" name = models.CharField(_('name'), max_length=250) + tag_categories = models.ManyToManyField('tag.TagCategory', + related_name='news_types') class Meta: """Meta class.""" @@ -133,8 +135,8 @@ class News(BaseAttributes, TranslatedFieldsMixin): country = models.ForeignKey('location.Country', blank=True, null=True, on_delete=models.SET_NULL, verbose_name=_('country')) - tags = generic.GenericRelation(to='main.MetaDataContent') - + tags = models.ManyToManyField('tag.Tag', related_name='news', + verbose_name=_('Tags')) ratings = generic.GenericRelation(Rating) objects = NewsQuerySet.as_manager() @@ -163,4 +165,3 @@ class News(BaseAttributes, TranslatedFieldsMixin): @property def same_theme(self): return self.__class__.objects.same_theme(self)[:3] - diff --git a/apps/news/serializers.py b/apps/news/serializers.py index c473be1d..6f0b73b6 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -3,8 +3,8 @@ from rest_framework import serializers from account.serializers.common import UserBaseSerializer from location import models as location_models from location.serializers import CountrySimpleSerializer -from main.serializers import MetaDataContentSerializer from news import models +from tag.serializers import TagBaseSerializer from utils.serializers import TranslatedField, ProjectModelSerializer @@ -27,7 +27,7 @@ class NewsBaseSerializer(ProjectModelSerializer): # related fields news_type = NewsTypeSerializer(read_only=True) - tags = MetaDataContentSerializer(read_only=True, many=True) + tags = TagBaseSerializer(read_only=True, many=True) class Meta: """Meta class.""" diff --git a/apps/news/urls/web.py b/apps/news/urls/web.py index 80fcf072..0671f5f6 100644 --- a/apps/news/urls/web.py +++ b/apps/news/urls/web.py @@ -7,5 +7,7 @@ app_name = 'news' urlpatterns = [ path('', views.NewsListView.as_view(), name='list'), path('types/', views.NewsTypeListView.as_view(), name='type'), + path('types//tags/', views.NewsTypeTagsView.as_view(), + name='type-tags'), path('slug//', views.NewsDetailView.as_view(), name='rud'), ] diff --git a/apps/news/views.py b/apps/news/views.py index 61a57251..1a845a7b 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -1,7 +1,10 @@ """News app views.""" +from django.shortcuts import get_object_or_404 from rest_framework import generics, permissions from news import filters, models, serializers from rating.tasks import add_rating +from tag.serializers import TagCategoryDetailSerializer + class NewsMixinView: """News mixin.""" @@ -34,6 +37,7 @@ class NewsDetailView(NewsMixinView, generics.RetrieveAPIView): """Override get_queryset method.""" return super().get_queryset().with_extended_related() + class NewsTypeListView(generics.ListAPIView): """NewsType list view.""" @@ -43,6 +47,18 @@ class NewsTypeListView(generics.ListAPIView): serializer_class = serializers.NewsTypeSerializer +class NewsTypeTagsView(generics.ListAPIView): + """Resource to get a list of tags for a news type.""" + + pagination_class = None + permission_classes = (permissions.AllowAny, ) + serializer_class = TagCategoryDetailSerializer + + def get_queryset(self): + news_type = get_object_or_404(models.NewsType, pk=self.kwargs.get('pk')) + return news_type.tag_categories.with_related() + + class NewsBackOfficeMixinView: """News back office mixin view.""" diff --git a/apps/tag/migrations/0002_auto_20191009_1408.py b/apps/tag/migrations/0002_auto_20191009_1408.py new file mode 100644 index 00000000..472d9596 --- /dev/null +++ b/apps/tag/migrations/0002_auto_20191009_1408.py @@ -0,0 +1,27 @@ +# Generated by Django 2.2.4 on 2019-10-09 14:08 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('tag', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='tag', + options={'verbose_name': 'Tag', 'verbose_name_plural': 'Tags'}, + ), + migrations.AlterModelOptions( + name='tagcategory', + options={'verbose_name': 'Tag category', 'verbose_name_plural': 'Tag categories'}, + ), + migrations.AlterField( + model_name='tag', + name='category', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='tags', to='tag.TagCategory', verbose_name='Category'), + ), + ] diff --git a/apps/tag/models.py b/apps/tag/models.py index c269d64e..b552bbcb 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -1,24 +1,25 @@ """Tag app models.""" from django.db import models from django.utils.translation import gettext_lazy as _ - from configuration.models import TranslationSettings from utils.models import TJSONField, TranslatedFieldsMixin class Tag(TranslatedFieldsMixin, models.Model): """Tag model.""" - label = TJSONField( - _('label'), null=True, blank=True, - default=None, help_text='{"en-GB":"some text"}') - category = models.ForeignKey('TagCategory', - on_delete=models.SET_NULL, null=True, - related_name='tags', - verbose_name='category') + + label = TJSONField(blank=True, null=True, default=None, + verbose_name=_('label'), + help_text='{"en-GB":"some text"}') + category = models.ForeignKey('TagCategory', on_delete=models.PROTECT, + null=True, related_name='tags', + verbose_name=_('Category')) class Meta: - verbose_name = _('tag') - verbose_name_plural = _('tags') + """Meta class.""" + + verbose_name = _('Tag') + verbose_name_plural = _('Tags') def __str__(self): label = 'None' @@ -28,20 +29,34 @@ class Tag(TranslatedFieldsMixin, models.Model): return f'id:{self.id}-{label}' +class TagCategoryQuerySet(models.QuerySet): + """Extended queryset for TagCategory model.""" + + def by_news_type(self, news_type): + return self.filter(news_types=news_type) + + def with_related(self): + return self.select_related('country').prefetch_related('tags') + + class TagCategory(TranslatedFieldsMixin, models.Model): """Tag base category model.""" - label = TJSONField( - _('label'), null=True, blank=True, - default=None, help_text='{"en-GB":"some text"}') + + label = TJSONField(blank=True, null=True, default=None, + verbose_name=_('label'), + help_text='{"en-GB":"some text"}') country = models.ForeignKey('location.Country', on_delete=models.SET_NULL, null=True, default=None) - public = models.BooleanField(default=False) + objects = TagCategoryQuerySet.as_manager() + class Meta: - verbose_name = _('tag category') - verbose_name_plural = _('tag categories') + """Meta class.""" + + verbose_name = _('Tag category') + verbose_name_plural = _('Tag categories') def __str__(self): label = 'None' diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py index cf783910..ecb1b68d 100644 --- a/apps/tag/serializers.py +++ b/apps/tag/serializers.py @@ -1,14 +1,17 @@ """Tag serializers.""" from rest_framework import serializers -from . import models +from tag import models from utils.serializers import TranslatedField class TagBaseSerializer(serializers.ModelSerializer): """Serializer for model Tag.""" + label_translated = TranslatedField() class Meta: + """Meta class.""" + model = models.Tag fields = [ 'id', @@ -24,21 +27,33 @@ class TagBaseSerializer(serializers.ModelSerializer): class TagCategoryBaseSerializer(serializers.ModelSerializer): """Serializer for model TagCategory.""" + label_translated = TranslatedField() country_translated = TranslatedField(source='country.name_translated') class Meta: """Meta class.""" + model = models.TagCategory - fields = [ + fields = ( 'id', 'label', 'label_translated', 'country', 'country_translated', 'public', - ] + ) extra_kwargs = { 'label': {'write_only': True}, 'country': {'write_only': True}, } + + +class TagCategoryDetailSerializer(TagCategoryBaseSerializer): + + tags = TagBaseSerializer(many=True) + + class Meta(TagCategoryBaseSerializer.Meta): + """Meta class.""" + + fields = TagCategoryBaseSerializer.Meta.fields + ('tags', ) diff --git a/apps/tag/views.py b/apps/tag/views.py index ab944ba7..5bf4d045 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -1,11 +1,11 @@ """Tag views.""" from rest_framework import generics - -from . import serializers, models +from tag import serializers, models class TagListCreateView(generics.ListCreateAPIView): """List/create tag view.""" + queryset = models.Tag.objects.all() serializer_class = serializers.TagBaseSerializer pagination_class = None @@ -13,6 +13,7 @@ class TagListCreateView(generics.ListCreateAPIView): class TagCategoryListCreateView(generics.ListCreateAPIView): """List/create tag category view.""" + queryset = models.TagCategory.objects.all() serializer_class = serializers.TagCategoryBaseSerializer pagination_class = None diff --git a/project/urls/back.py b/project/urls/back.py index 2a9657db..77e573d0 100644 --- a/project/urls/back.py +++ b/project/urls/back.py @@ -3,11 +3,9 @@ from django.urls import path, include app_name = 'back' urlpatterns = [ - path('gallery/', include(('gallery.urls', 'gallery'), - namespace='gallery')), + path('gallery/', include(('gallery.urls', 'gallery'), namespace='gallery')), 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')) ] diff --git a/project/urls/web.py b/project/urls/web.py index 5bf538f3..a4e68317 100644 --- a/project/urls/web.py +++ b/project/urls/web.py @@ -23,7 +23,8 @@ urlpatterns = [ path('collections/', include('collection.urls.web')), path('establishments/', include('establishment.urls.web')), path('news/', include('news.urls.web')), - path('notifications/', include(('notification.urls.web', "notification"), namespace='notification')), + path('notifications/', include(('notification.urls.web', "notification"), + namespace='notification')), path('partner/', include('partner.urls.web')), path('location/', include('location.urls.web')), path('main/', include('main.urls')), From f83b735eb321306dfa6a710e3f820bb12d14dcab Mon Sep 17 00:00:00 2001 From: Anatoly Date: Thu, 10 Oct 2019 15:10:43 +0300 Subject: [PATCH 030/223] changed files --- .../migrations/0034_merge_20191009_1457.py | 14 ++++++ apps/establishment/models.py | 27 ++++++++++++ apps/establishment/serializers/back.py | 5 +-- apps/establishment/serializers/common.py | 44 +++++++++++++++++-- apps/establishment/urls/back.py | 4 ++ apps/establishment/views/back.py | 30 ++++++++++++- 6 files changed, 116 insertions(+), 8 deletions(-) create mode 100644 apps/establishment/migrations/0034_merge_20191009_1457.py diff --git a/apps/establishment/migrations/0034_merge_20191009_1457.py b/apps/establishment/migrations/0034_merge_20191009_1457.py new file mode 100644 index 00000000..945860f7 --- /dev/null +++ b/apps/establishment/migrations/0034_merge_20191009_1457.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.4 on 2019-10-09 14:57 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0033_auto_20191009_0715'), + ('establishment', '0033_auto_20191003_0943_squashed_0034_auto_20191003_1036'), + ] + + operations = [ + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 74348f5d..c248ca5d 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -479,6 +479,7 @@ class ContactEmail(models.Model): def __str__(self): return f'{self.email}' + # # class Wine(TranslatedFieldsMixin, models.Model): # """Wine model.""" @@ -586,6 +587,11 @@ class EstablishmentTypeTagCategoryQuerySet(models.QuerySet): """Return establishment tags by country code""" return self.filter(tag_category__country__code=code) + def with_base_related(self): + """Return with related relations.""" + # return self.select_related('tags', 'establishment_type') + return self.select_related('establishment_type', 'tag_category') + class EstablishmentTypeTagCategory(models.Model): """Tag categories based on establishment type.""" @@ -602,3 +608,24 @@ class EstablishmentTypeTagCategory(models.Model): class Meta: verbose_name = _('establishment type tag categories') verbose_name_plural = _('establishment type tag categories') + + +# class EstablishmentSubTypeTagCategoryQuerySet(models.QuerySet): +# """QuerySet for tag categories based on establishment subtype.""" +# +# +# class EstablishmentSubTypeTagCategory(models.Model): +# """Tag categories based on establishment subtype.""" +# establishment_subtype = models.ForeignKey(EstablishmentSubType, +# on_delete=models.CASCADE, +# related_name='tag_categories', +# verbose_name=_('establishment subtype')) +# tag_category = models.ForeignKey('tag.TagCategory', +# on_delete=models.CASCADE, +# related_name='est_subtype_tag_categories', +# verbose_name=_('tag category')) +# objects = EstablishmentSubTypeTagCategoryQuerySet.as_manager() +# +# class Meta: +# verbose_name = _('establishment subtype tag categories') +# verbose_name_plural = _('establishment subtype tag categories') diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index 8bd09e85..33d2e812 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -1,13 +1,12 @@ from rest_framework import serializers + from establishment import models from establishment.serializers import ( EstablishmentBaseSerializer, PlateSerializer, ContactEmailsSerializer, ContactPhonesSerializer, SocialNetworkRelatedSerializers, EstablishmentTypeSerializer) - -from utils.decorators import with_base_attributes - from main.models import Currency +from utils.decorators import with_base_attributes class EstablishmentListCreateSerializer(EstablishmentBaseSerializer): diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 2cd4e429..ef21e4fb 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -89,11 +89,11 @@ class MenuRUDSerializers(ProjectModelSerializer): ] -class EstablishmentTagCategoryListSerializer(serializers.ModelSerializer): +class EstablishmentTypeTagCategoryBaseSerializer(serializers.ModelSerializer): """Serializer for intermediate model EstablishmentTypeTagCategories.""" - id = serializers.IntegerField(source='tag_category.id') + id = serializers.IntegerField(source='tag_category.id', read_only=True) label_translated = TranslatedField(source='tag_category.label_translated') - tags = TagBaseSerializer(source='tag_category.tags', many=True) + tags = TagBaseSerializer(source='tag_category.tags', many=True, read_only=True) class Meta: """Meta class.""" @@ -102,7 +102,21 @@ class EstablishmentTagCategoryListSerializer(serializers.ModelSerializer): 'id', 'label_translated', 'tags', + 'establishment_type', + 'tag_category', ] + extra_kwargs = { + 'establishment_type': {'write_only': True}, + 'tag_category': {'write_only': True}, + } + + def validate(self, attrs): + """Override validate method.""" + if models.EstablishmentTypeTagCategory.objects.filter( + establishment_type=attrs.get('establishment_type'), + tag_category=attrs.get('tag_category')).exists(): + raise serializers.ValidationError(detail={'detail': _('Objects is already attached.')}) + return attrs class EstablishmentTypeSerializer(serializers.ModelSerializer): @@ -136,6 +150,30 @@ class EstablishmentSubTypeSerializer(serializers.ModelSerializer): } +# class EstablishmentSubTypeTagCategoryBaseSerializer(serializers.ModelSerializer): +# """Serializer for intermediate model EstablishmentSubTypeTagCategories.""" +# +# class Meta: +# """Meta class.""" +# model = models.EstablishmentSubTypeTagCategory +# fields = [ +# 'establishment_subtype', +# 'tag_category', +# ] +# extra_kwargs = { +# 'establishment_subtype': {'write_only': True}, +# 'tag_category': {'write_only': True}, +# } +# +# def validate(self, attrs): +# """Override validate method.""" +# if models.EstablishmentSubTypeTagCategory.objects.filter( +# establishment_type=attrs.get('establishment_subtype'), +# tag_category=attrs.get('tag_category')).exists(): +# raise serializers.ValidationError(detail={'detail': _('Objects is already attached.')}) +# return attrs + + class ReviewSerializer(serializers.ModelSerializer): """Serializer for model Review.""" text_translated = serializers.CharField(read_only=True) diff --git a/apps/establishment/urls/back.py b/apps/establishment/urls/back.py index b04a6843..ad10ea1a 100644 --- a/apps/establishment/urls/back.py +++ b/apps/establishment/urls/back.py @@ -30,6 +30,10 @@ urlpatterns = [ path('employees//', views.EmployeeRUDView.as_view(), name='employees-rud'), path('types/', views.EstablishmentTypeListCreateView.as_view(), name='type-list'), path('types//', views.EstablishmentTypeRUDView.as_view(), name='type-rud'), + path('types/attach-tag-category/', views.EstablishmentTypeAttachTagCategoryView.as_view(), + name='type-attach-tag-category'), path('subtypes/', views.EstablishmentSubtypeListCreateView.as_view(), name='subtype-list'), path('subtypes//', views.EstablishmentSubtypeRUDView.as_view(), name='subtype-rud'), + # path('subtypes/attach-tag-category/', views.EstablishmentSubTypeAttachTagCategoryView.as_view(), + # name='subtype-attach-tag-category'), ] \ No newline at end of file diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index 32ee2805..7d0949be 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -1,8 +1,9 @@ """Establishment app views.""" from django.shortcuts import get_object_or_404 -from rest_framework import generics +from rest_framework import generics, status from establishment import models, serializers +from rest_framework.response import Response from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer @@ -55,7 +56,7 @@ class EstablishmentScheduleCreateView(generics.CreateAPIView): class EstablishmentTagCategoryListView(EstablishmentMixinViews, generics.ListAPIView): """View for establishment tag categories.""" - serializer_class = serializers.EstablishmentTagCategoryListSerializer + serializer_class = serializers.EstablishmentTypeTagCategoryBaseSerializer pagination_class = None def get_object(self): @@ -81,6 +82,31 @@ class EstablishmentTagCategoryListView(EstablishmentMixinViews, generics.ListAPI return establishment.establishment_type.tag_categories.all() +class EstablishmentTypeAttachTagCategoryView(generics.CreateAPIView): + """Attach tag category to establishment type.""" + serializer_class = serializers.EstablishmentTypeTagCategoryBaseSerializer + + def get_queryset(self): + """Override get_queryset method.""" + return models.EstablishmentTypeTagCategory.objects.with_base_related() + + def post(self, request, *args, **kwargs): + """Overridden post-method.""" + super(EstablishmentTypeAttachTagCategoryView, self).post(request) + return Response(status=status.HTTP_200_OK) + + +# class EstablishmentSubTypeAttachTagCategoryView(generics.CreateAPIView): +# """Attach tag category to establishment subtype.""" +# queryset = models.EstablishmentSubTypeTagCategory.objects.all() +# serializer_class = serializers.EstablishmentSubTypeTagCategoryBaseSerializer +# +# def post(self, request, *args, **kwargs): +# """Overridden post-method.""" +# super(EstablishmentSubTypeAttachTagCategoryView, self).post(request) +# return Response(status=status.HTTP_200_OK) + + class MenuListCreateView(generics.ListCreateAPIView): """Menu list create view.""" serializer_class = serializers.MenuSerializers From ddb3eec6790cad55810233d948c1ac0db0e80236 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 11 Oct 2019 15:41:31 +0300 Subject: [PATCH 031/223] finished endpoint to get tags by establishment type and subtype --- apps/establishment/admin.py | 9 +- .../0035_establishmentsubtypetagcategory.py | 27 +++ apps/establishment/models.py | 43 ++--- apps/establishment/serializers/back.py | 6 +- apps/establishment/serializers/common.py | 170 ++++++++++-------- apps/establishment/urls/back.py | 6 +- apps/establishment/views/back.py | 45 +++-- apps/establishment/views/web.py | 2 +- 8 files changed, 185 insertions(+), 123 deletions(-) create mode 100644 apps/establishment/migrations/0035_establishmentsubtypetagcategory.py diff --git a/apps/establishment/admin.py b/apps/establishment/admin.py index a40475b0..1f200f41 100644 --- a/apps/establishment/admin.py +++ b/apps/establishment/admin.py @@ -82,10 +82,15 @@ class MenuAdmin(admin.ModelAdmin): @admin.register(models.EstablishmentTypeTagCategory) -class EstablishmentTypeTagCategory(admin.ModelAdmin): +class EstablishmentTypeTagCategoryAdmin(admin.ModelAdmin): + """EstablishmentTypeTagCategory admin.""" + + +@admin.register(models.EstablishmentSubTypeTagCategory) +class EstablishmentSubTypeTagCategoryAdmin(admin.ModelAdmin): """EstablishmentTypeTagCategory admin.""" @admin.register(models.EstablishmentTag) -class EstablishmentTag(admin.ModelAdmin): +class EstablishmentTagAdmin(admin.ModelAdmin): """EstablishmentTag admin.""" diff --git a/apps/establishment/migrations/0035_establishmentsubtypetagcategory.py b/apps/establishment/migrations/0035_establishmentsubtypetagcategory.py new file mode 100644 index 00000000..6a85fca7 --- /dev/null +++ b/apps/establishment/migrations/0035_establishmentsubtypetagcategory.py @@ -0,0 +1,27 @@ +# Generated by Django 2.2.4 on 2019-10-11 10:47 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('tag', '0002_auto_20191009_1408'), + ('establishment', '0034_merge_20191009_1457'), + ] + + operations = [ + migrations.CreateModel( + name='EstablishmentSubTypeTagCategory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('establishment_subtype', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tag_categories', to='establishment.EstablishmentSubType', verbose_name='establishment subtype')), + ('tag_category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='est_subtype_tag_categories', to='tag.TagCategory', verbose_name='tag category')), + ], + options={ + 'verbose_name': 'establishment subtype tag categories', + 'verbose_name_plural': 'establishment subtype tag categories', + }, + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index c248ca5d..39b750fa 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -589,7 +589,6 @@ class EstablishmentTypeTagCategoryQuerySet(models.QuerySet): def with_base_related(self): """Return with related relations.""" - # return self.select_related('tags', 'establishment_type') return self.select_related('establishment_type', 'tag_category') @@ -610,22 +609,26 @@ class EstablishmentTypeTagCategory(models.Model): verbose_name_plural = _('establishment type tag categories') -# class EstablishmentSubTypeTagCategoryQuerySet(models.QuerySet): -# """QuerySet for tag categories based on establishment subtype.""" -# -# -# class EstablishmentSubTypeTagCategory(models.Model): -# """Tag categories based on establishment subtype.""" -# establishment_subtype = models.ForeignKey(EstablishmentSubType, -# on_delete=models.CASCADE, -# related_name='tag_categories', -# verbose_name=_('establishment subtype')) -# tag_category = models.ForeignKey('tag.TagCategory', -# on_delete=models.CASCADE, -# related_name='est_subtype_tag_categories', -# verbose_name=_('tag category')) -# objects = EstablishmentSubTypeTagCategoryQuerySet.as_manager() -# -# class Meta: -# verbose_name = _('establishment subtype tag categories') -# verbose_name_plural = _('establishment subtype tag categories') +class EstablishmentSubTypeTagCategoryQuerySet(models.QuerySet): + """QuerySet for tag categories based on establishment subtype.""" + + def with_base_related(self): + """Return queryset with base related.""" + return self.select_related('establishment_subtype', 'tag_category') + + +class EstablishmentSubTypeTagCategory(models.Model): + """Tag categories based on establishment subtype.""" + establishment_subtype = models.ForeignKey(EstablishmentSubType, + on_delete=models.CASCADE, + related_name='tag_categories', + verbose_name=_('establishment subtype')) + tag_category = models.ForeignKey('tag.TagCategory', + on_delete=models.CASCADE, + related_name='est_subtype_tag_categories', + verbose_name=_('tag category')) + objects = EstablishmentSubTypeTagCategoryQuerySet.as_manager() + + class Meta: + verbose_name = _('establishment subtype tag categories') + verbose_name_plural = _('establishment subtype tag categories') diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index 33d2e812..e164b44a 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -4,7 +4,7 @@ from establishment import models from establishment.serializers import ( EstablishmentBaseSerializer, PlateSerializer, ContactEmailsSerializer, ContactPhonesSerializer, SocialNetworkRelatedSerializers, - EstablishmentTypeSerializer) + EstablishmentTypeBaseSerializer) from main.models import Currency from utils.decorators import with_base_attributes @@ -20,7 +20,7 @@ class EstablishmentListCreateSerializer(EstablishmentBaseSerializer): emails = ContactEmailsSerializer(read_only=True, many=True, ) socials = SocialNetworkRelatedSerializers(read_only=True, many=True, ) slug = serializers.SlugField(required=True, allow_blank=False, max_length=50) - type = EstablishmentTypeSerializer(source='establishment_type', read_only=True) + type = EstablishmentTypeBaseSerializer(source='establishment_type', read_only=True) class Meta: model = models.Establishment @@ -54,7 +54,7 @@ class EstablishmentRUDSerializer(EstablishmentBaseSerializer): phones = ContactPhonesSerializer(read_only=False, many=True, ) emails = ContactEmailsSerializer(read_only=False, many=True, ) socials = SocialNetworkRelatedSerializers(read_only=False, many=True, ) - type = EstablishmentTypeSerializer(source='establishment_type') + type = EstablishmentTypeBaseSerializer(source='establishment_type') class Meta: model = models.Establishment diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index ef21e4fb..eec51466 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -89,6 +89,57 @@ class MenuRUDSerializers(ProjectModelSerializer): ] +class ReviewSerializer(serializers.ModelSerializer): + """Serializer for model Review.""" + text_translated = serializers.CharField(read_only=True) + + class Meta: + """Meta class.""" + model = review_models.Review + fields = ( + 'text_translated', + ) + + +class EstablishmentTypeBaseSerializer(serializers.ModelSerializer): + """Serializer for EstablishmentType model.""" + name_translated = TranslatedField() + + class Meta: + """Meta class.""" + model = models.EstablishmentType + fields = [ + 'id', + 'name', + 'name_translated', + 'use_subtypes' + ] + extra_kwargs = { + 'name': {'write_only': True}, + 'use_subtypes': {'write_only': True}, + } + + +class EstablishmentSubTypeBaseSerializer(serializers.ModelSerializer): + """Serializer for EstablishmentSubType models.""" + + name_translated = TranslatedField() + + class Meta: + """Meta class.""" + model = models.EstablishmentSubType + fields = [ + 'id', + 'name', + 'name_translated', + 'establishment_type' + ] + extra_kwargs = { + 'name': {'write_only': True}, + 'establishment_type': {'write_only': True} + } + + class EstablishmentTypeTagCategoryBaseSerializer(serializers.ModelSerializer): """Serializer for intermediate model EstablishmentTypeTagCategories.""" id = serializers.IntegerField(source='tag_category.id', read_only=True) @@ -119,71 +170,62 @@ class EstablishmentTypeTagCategoryBaseSerializer(serializers.ModelSerializer): return attrs -class EstablishmentTypeSerializer(serializers.ModelSerializer): - """Serializer for EstablishmentType model.""" - - name_translated = TranslatedField() +class EstablishmentSubTypeTagCategoryBaseSerializer(serializers.ModelSerializer): + """Serializer for intermediate model EstablishmentSubTypeTagCategories.""" + id = serializers.IntegerField(source='tag_category.id', read_only=True) + label_translated = TranslatedField(source='tag_category.label_translated') + tags = TagBaseSerializer(source='tag_category.tags', many=True, read_only=True) class Meta: """Meta class.""" - - model = models.EstablishmentType - fields = ('id', 'name', 'name_translated') + model = models.EstablishmentSubTypeTagCategory + fields = [ + 'id', + 'label_translated', + 'tags', + 'establishment_subtype', + 'tag_category', + ] extra_kwargs = { - 'name': {'write_only': True} + 'establishment_subtype': {'write_only': True}, + 'tag_category': {'write_only': True}, } + def validate(self, attrs): + """Override validate method.""" + if models.EstablishmentTypeTagCategory.objects.filter( + establishment_type=attrs.get('establishment_type'), + tag_category=attrs.get('tag_category')).exists(): + raise serializers.ValidationError(detail={'detail': _('Objects is already attached.')}) + return attrs -class EstablishmentSubTypeSerializer(serializers.ModelSerializer): - """Serializer for EstablishmentSubType models.""" - name_translated = TranslatedField() +class EstablishmentSubTypeSerializer(EstablishmentSubTypeBaseSerializer): + """Extended serializer for EstablishmentSubType model with tags.""" + tag_categories = EstablishmentSubTypeTagCategoryBaseSerializer(many=True, read_only=True) - class Meta: + class Meta(EstablishmentSubTypeBaseSerializer.Meta): + """Meta class""" + fields = [ + 'id', + 'name_translated', + 'tag_categories' + ] + + +class EstablishmentTagsByType(EstablishmentTypeBaseSerializer): + """Tags by establishment type""" + tag_categories = EstablishmentTypeTagCategoryBaseSerializer(many=True) + subtypes = EstablishmentSubTypeSerializer(many=True, source='establishmentsubtype_set') + + class Meta(EstablishmentTypeBaseSerializer.Meta): """Meta class.""" - - model = models.EstablishmentSubType - fields = ('id', 'name', 'name_translated', 'establishment_type') - extra_kwargs = { - 'name': {'write_only': True}, - 'establishment_type': {'write_only': True} - } - - -# class EstablishmentSubTypeTagCategoryBaseSerializer(serializers.ModelSerializer): -# """Serializer for intermediate model EstablishmentSubTypeTagCategories.""" -# -# class Meta: -# """Meta class.""" -# model = models.EstablishmentSubTypeTagCategory -# fields = [ -# 'establishment_subtype', -# 'tag_category', -# ] -# extra_kwargs = { -# 'establishment_subtype': {'write_only': True}, -# 'tag_category': {'write_only': True}, -# } -# -# def validate(self, attrs): -# """Override validate method.""" -# if models.EstablishmentSubTypeTagCategory.objects.filter( -# establishment_type=attrs.get('establishment_subtype'), -# tag_category=attrs.get('tag_category')).exists(): -# raise serializers.ValidationError(detail={'detail': _('Objects is already attached.')}) -# return attrs - - -class ReviewSerializer(serializers.ModelSerializer): - """Serializer for model Review.""" - text_translated = serializers.CharField(read_only=True) - - class Meta: - """Meta class.""" - model = review_models.Review - fields = ( - 'text_translated', - ) + fields = [ + 'id', + 'name_translated', + 'tag_categories', + 'subtypes', + ] class EstablishmentEmployeeSerializer(serializers.ModelSerializer): @@ -202,19 +244,6 @@ class EstablishmentEmployeeSerializer(serializers.ModelSerializer): fields = ('id', 'name', 'position_translated', 'awards', 'priority') -class EstablishmentCategoryTagListSerializer(serializers.ModelSerializer): - """Serializer for establishment category tags.""" - label_translated = TranslatedField() - - class Meta: - """Meta class.""" - model = tag_models.TagCategory - fields = [ - 'id', - 'label_translated', - ] - - class EstablishmentTagSerializer(serializers.ModelSerializer): """Serializer for intermediate model EstablishmentTag.""" id = serializers.IntegerField(source='tag.id') @@ -262,8 +291,8 @@ class EstablishmentDetailSerializer(EstablishmentBaseSerializer): description_translated = TranslatedField() image = serializers.URLField(source='image_url') - type = EstablishmentTypeSerializer(source='establishment_type', read_only=True) - subtypes = EstablishmentSubTypeSerializer(many=True, source='establishment_subtypes') + type = EstablishmentTypeBaseSerializer(source='establishment_type', read_only=True) + subtypes = EstablishmentSubTypeBaseSerializer(many=True, source='establishment_subtypes') awards = AwardSerializer(many=True) schedule = ScheduleRUDSerializer(many=True, allow_null=True) phones = ContactPhonesSerializer(read_only=True, many=True) @@ -396,3 +425,4 @@ class EstablishmentFavoritesCreateSerializer(serializers.ModelSerializer): 'content_object': validated_data.pop('establishment') }) return super().create(validated_data) + diff --git a/apps/establishment/urls/back.py b/apps/establishment/urls/back.py index ad10ea1a..e0489783 100644 --- a/apps/establishment/urls/back.py +++ b/apps/establishment/urls/back.py @@ -14,7 +14,7 @@ urlpatterns = [ name='schedule-rud'), path('/schedule/', views.EstablishmentScheduleCreateView.as_view(), name='schedule-create'), - path('/tags/categories/', views.EstablishmentTagCategoryListView.as_view(), + path('/tags/', views.EstablishmentTagCategoryListView.as_view(), name='tag-category-list'), path('menus/', views.MenuListCreateView.as_view(), name='menu-list'), path('menus//', views.MenuRUDView.as_view(), name='menu-rud'), @@ -34,6 +34,6 @@ urlpatterns = [ name='type-attach-tag-category'), path('subtypes/', views.EstablishmentSubtypeListCreateView.as_view(), name='subtype-list'), path('subtypes//', views.EstablishmentSubtypeRUDView.as_view(), name='subtype-rud'), - # path('subtypes/attach-tag-category/', views.EstablishmentSubTypeAttachTagCategoryView.as_view(), - # name='subtype-attach-tag-category'), + path('subtypes/attach-tag-category/', views.EstablishmentSubTypeAttachTagCategoryView.as_view(), + name='subtype-attach-tag-category'), ] \ No newline at end of file diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index 7d0949be..84855982 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -54,9 +54,9 @@ class EstablishmentScheduleCreateView(generics.CreateAPIView): serializer_class = ScheduleCreateSerializer -class EstablishmentTagCategoryListView(EstablishmentMixinViews, generics.ListAPIView): +class EstablishmentTagCategoryListView(EstablishmentMixinViews, generics.RetrieveAPIView): """View for establishment tag categories.""" - serializer_class = serializers.EstablishmentTypeTagCategoryBaseSerializer + serializer_class = serializers.EstablishmentTagsByType pagination_class = None def get_object(self): @@ -74,12 +74,7 @@ class EstablishmentTagCategoryListView(EstablishmentMixinViews, generics.ListAPI # May raise a permission denied self.check_object_permissions(self.request, obj) - return obj - - def get_queryset(self): - """Overridden get_queryset method.""" - establishment = self.get_object() - return establishment.establishment_type.tag_categories.all() + return obj.establishment_type class EstablishmentTypeAttachTagCategoryView(generics.CreateAPIView): @@ -96,17 +91,6 @@ class EstablishmentTypeAttachTagCategoryView(generics.CreateAPIView): return Response(status=status.HTTP_200_OK) -# class EstablishmentSubTypeAttachTagCategoryView(generics.CreateAPIView): -# """Attach tag category to establishment subtype.""" -# queryset = models.EstablishmentSubTypeTagCategory.objects.all() -# serializer_class = serializers.EstablishmentSubTypeTagCategoryBaseSerializer -# -# def post(self, request, *args, **kwargs): -# """Overridden post-method.""" -# super(EstablishmentSubTypeAttachTagCategoryView, self).post(request) -# return Response(status=status.HTTP_200_OK) - - class MenuListCreateView(generics.ListCreateAPIView): """Menu list create view.""" serializer_class = serializers.MenuSerializers @@ -186,31 +170,44 @@ class EmployeeRUDView(generics.RetrieveUpdateDestroyAPIView): class EstablishmentTypeListCreateView(generics.ListCreateAPIView): """Establishment type list/create view.""" - serializer_class = serializers.EstablishmentTypeSerializer + serializer_class = serializers.EstablishmentTypeBaseSerializer queryset = models.EstablishmentType.objects.all() pagination_class = None class EstablishmentTypeRUDView(generics.RetrieveUpdateDestroyAPIView): """Establishment type retrieve/update/destroy view.""" - serializer_class = serializers.EstablishmentTypeSerializer + serializer_class = serializers.EstablishmentTypeBaseSerializer queryset = models.EstablishmentType.objects.all() class EstablishmentSubtypeListCreateView(generics.ListCreateAPIView): """Establishment subtype list/create view.""" - serializer_class = serializers.EstablishmentSubTypeSerializer + serializer_class = serializers.EstablishmentSubTypeBaseSerializer queryset = models.EstablishmentSubType.objects.all() pagination_class = None class EstablishmentSubtypeRUDView(generics.RetrieveUpdateDestroyAPIView): """Establishment subtype retrieve/update/destroy view.""" - serializer_class = serializers.EstablishmentSubTypeSerializer + serializer_class = serializers.EstablishmentSubTypeBaseSerializer queryset = models.EstablishmentSubType.objects.all() -# todo: next task +class EstablishmentSubTypeAttachTagCategoryView(generics.CreateAPIView): + """Attach tag category to establishment subtype.""" + serializer_class = serializers.EstablishmentSubTypeTagCategoryBaseSerializer + + def get_queryset(self): + """Override get_queryset method.""" + return models.EstablishmentSubTypeTagCategory.objects.with_base_related() + + def post(self, request, *args, **kwargs): + """Overridden post-method.""" + super(EstablishmentSubTypeAttachTagCategoryView, self).post(request) + return Response(status=status.HTTP_200_OK) + + # class EstablishmentTagListCreateView(generics.CreateAPIView): # """Establishment tag list/create view.""" # serializer_class = serializers.EstablishmentTagCategoryListCreateSerializer diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index b4ef4f13..f164ec73 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -84,7 +84,7 @@ class EstablishmentTypeListView(generics.ListAPIView): """Resource for getting a list of establishment types.""" permission_classes = (permissions.AllowAny,) - serializer_class = serializers.EstablishmentTypeSerializer + serializer_class = serializers.EstablishmentTypeBaseSerializer queryset = models.EstablishmentType.objects.all() From 868a3c1b783a30d74ee89c6c936e91d7e046ae38 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 11 Oct 2019 16:36:34 +0300 Subject: [PATCH 032/223] 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 cb762ce746dbfa20cb3f9c5afafee26c3dbbde51 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 11 Oct 2019 18:26:13 +0300 Subject: [PATCH 033/223] added param to attr for Establoishment model and small refactoring, boost queries --- .../migrations/0036_auto_20191011_1356.py | 18 ++++ apps/establishment/models.py | 7 ++ apps/establishment/serializers/back.py | 98 ++++++++++++++++++- apps/establishment/serializers/common.py | 59 ----------- apps/establishment/urls/back.py | 5 +- apps/establishment/views/back.py | 11 +-- 6 files changed, 130 insertions(+), 68 deletions(-) create mode 100644 apps/establishment/migrations/0036_auto_20191011_1356.py diff --git a/apps/establishment/migrations/0036_auto_20191011_1356.py b/apps/establishment/migrations/0036_auto_20191011_1356.py new file mode 100644 index 00000000..c2eb2e4e --- /dev/null +++ b/apps/establishment/migrations/0036_auto_20191011_1356.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-10-11 13:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0035_establishmentsubtypetagcategory'), + ] + + operations = [ + migrations.AlterField( + model_name='establishment', + name='establishment_subtypes', + field=models.ManyToManyField(blank=True, related_name='subtype_establishment', to='establishment.EstablishmentSubType', verbose_name='subtype'), + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 39b750fa..169590ff 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -23,6 +23,10 @@ from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin, TranslatedFieldsMixin, BaseAttributes) +class EstablishmentTypeQuerySet(models.QuerySet): + """QuerySet for model EstablishmentType.""" + + # todo: establishment type&subtypes check class EstablishmentType(TranslatedFieldsMixin, ProjectBaseMixin): """Establishment type model.""" @@ -82,6 +86,8 @@ class EstablishmentQuerySet(models.QuerySet): EstablishmentTag.objects.select_related('tag')), models.Prefetch('establishment_type__tag_categories', EstablishmentTypeTagCategory.objects.select_related('tag_category')), + models.Prefetch('establishment_type__establishmentsubtype_set', + EstablishmentSubType.objects.prefetch_related('tag_categories')), ) def with_extended_related(self): @@ -257,6 +263,7 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): on_delete=models.PROTECT, verbose_name=_('type')) establishment_subtypes = models.ManyToManyField(EstablishmentSubType, + blank=True, related_name='subtype_establishment', verbose_name=_('subtype')) address = models.ForeignKey(Address, blank=True, null=True, default=None, diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index e164b44a..21f63e17 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -1,12 +1,16 @@ +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from establishment import models from establishment.serializers import ( EstablishmentBaseSerializer, PlateSerializer, ContactEmailsSerializer, ContactPhonesSerializer, SocialNetworkRelatedSerializers, - EstablishmentTypeBaseSerializer) + EstablishmentTypeBaseSerializer, EstablishmentSubTypeBaseSerializer, + EstablishmentTypeTagCategoryBaseSerializer) from main.models import Currency +from tag.serializers import TagBaseSerializer from utils.decorators import with_base_attributes +from utils.serializers import TranslatedField class EstablishmentListCreateSerializer(EstablishmentBaseSerializer): @@ -141,3 +145,95 @@ class EmployeeBackSerializers(serializers.ModelSerializer): 'name' ] + +class EstablishmentTagCreateSerializer(serializers.ModelSerializer): + """Serializer for model EstablishmentTag.""" + class Meta: + model = models.EstablishmentTag + fields = [ + 'tag', + 'establishment' + ] + extra_kwargs = { + 'tag': {'write_only': True}, + 'establishment': {'write_only': True}, + } + + def validate(self, attrs): + """Validate method.""" + establishment = attrs.get('establishment') + tag = attrs.get('tag') + + # Check if tag is already added to establishment. + if establishment.tags.filter(tag=tag).exists(): + raise serializers.ValidationError(detail={'detail': _('Tag is already added.')}) + + # Сhecking tag availability for establishment type. + if not establishment.establishment_type.use_subtypes: + qs = establishment.establishment_type.tag_categories.filter(tag_category=tag.category) + else: + # Сhecking tag availability for establishment subtype. + qs = establishment.establishment_type.tag_categories.filter( + establishmentsubtype_set__tag_category=tag.category) + if not qs.exists(): + raise serializers.ValidationError( + detail={'detail': _('Tag is not available for this establishment type|subtype.')}) + return attrs + + +class EstablishmentSubTypeTagCategoryBaseSerializer(serializers.ModelSerializer): + """Serializer for intermediate model EstablishmentSubTypeTagCategories.""" + id = serializers.IntegerField(source='tag_category.id', read_only=True) + label_translated = TranslatedField(source='tag_category.label_translated') + tags = TagBaseSerializer(source='tag_category.tags', many=True, read_only=True) + + class Meta: + """Meta class.""" + model = models.EstablishmentSubTypeTagCategory + fields = [ + 'id', + 'label_translated', + 'tags', + 'establishment_subtype', + 'tag_category', + ] + extra_kwargs = { + 'establishment_subtype': {'write_only': True}, + 'tag_category': {'write_only': True}, + } + + def validate(self, attrs): + """Override validate method.""" + if models.EstablishmentTypeTagCategory.objects.filter( + establishment_type=attrs.get('establishment_type'), + tag_category=attrs.get('tag_category')).exists(): + raise serializers.ValidationError(detail={'detail': _('Objects is already attached.')}) + return attrs + + +class EstablishmentSubTypeSerializer(EstablishmentSubTypeBaseSerializer): + """Extended serializer for EstablishmentSubType model with tags.""" + tag_categories = EstablishmentSubTypeTagCategoryBaseSerializer(many=True, read_only=True) + + class Meta(EstablishmentSubTypeBaseSerializer.Meta): + """Meta class""" + fields = [ + 'id', + 'name_translated', + 'tag_categories' + ] + + +class EstablishmentTagsByType(EstablishmentTypeBaseSerializer): + """Tags by establishment type""" + tag_categories = EstablishmentTypeTagCategoryBaseSerializer(many=True) + subtypes = EstablishmentSubTypeSerializer(many=True, source='establishmentsubtype_set') + + class Meta(EstablishmentTypeBaseSerializer.Meta): + """Meta class.""" + fields = [ + 'id', + 'name_translated', + 'tag_categories', + 'subtypes', + ] diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index eec51466..1789da31 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -9,7 +9,6 @@ from favorites.models import Favorites from location.serializers import AddressBaseSerializer from main.serializers import AwardSerializer, CurrencySerializer from review import models as review_models -from tag import models as tag_models from tag.serializers import TagBaseSerializer from timetable.serialziers import ScheduleRUDSerializer from utils import exceptions as utils_exceptions @@ -170,64 +169,6 @@ class EstablishmentTypeTagCategoryBaseSerializer(serializers.ModelSerializer): return attrs -class EstablishmentSubTypeTagCategoryBaseSerializer(serializers.ModelSerializer): - """Serializer for intermediate model EstablishmentSubTypeTagCategories.""" - id = serializers.IntegerField(source='tag_category.id', read_only=True) - label_translated = TranslatedField(source='tag_category.label_translated') - tags = TagBaseSerializer(source='tag_category.tags', many=True, read_only=True) - - class Meta: - """Meta class.""" - model = models.EstablishmentSubTypeTagCategory - fields = [ - 'id', - 'label_translated', - 'tags', - 'establishment_subtype', - 'tag_category', - ] - extra_kwargs = { - 'establishment_subtype': {'write_only': True}, - 'tag_category': {'write_only': True}, - } - - def validate(self, attrs): - """Override validate method.""" - if models.EstablishmentTypeTagCategory.objects.filter( - establishment_type=attrs.get('establishment_type'), - tag_category=attrs.get('tag_category')).exists(): - raise serializers.ValidationError(detail={'detail': _('Objects is already attached.')}) - return attrs - - -class EstablishmentSubTypeSerializer(EstablishmentSubTypeBaseSerializer): - """Extended serializer for EstablishmentSubType model with tags.""" - tag_categories = EstablishmentSubTypeTagCategoryBaseSerializer(many=True, read_only=True) - - class Meta(EstablishmentSubTypeBaseSerializer.Meta): - """Meta class""" - fields = [ - 'id', - 'name_translated', - 'tag_categories' - ] - - -class EstablishmentTagsByType(EstablishmentTypeBaseSerializer): - """Tags by establishment type""" - tag_categories = EstablishmentTypeTagCategoryBaseSerializer(many=True) - subtypes = EstablishmentSubTypeSerializer(many=True, source='establishmentsubtype_set') - - class Meta(EstablishmentTypeBaseSerializer.Meta): - """Meta class.""" - fields = [ - 'id', - 'name_translated', - 'tag_categories', - 'subtypes', - ] - - class EstablishmentEmployeeSerializer(serializers.ModelSerializer): """Serializer for actual employees.""" diff --git a/apps/establishment/urls/back.py b/apps/establishment/urls/back.py index e0489783..c21aecc8 100644 --- a/apps/establishment/urls/back.py +++ b/apps/establishment/urls/back.py @@ -14,8 +14,9 @@ urlpatterns = [ name='schedule-rud'), path('/schedule/', views.EstablishmentScheduleCreateView.as_view(), name='schedule-create'), - path('/tags/', views.EstablishmentTagCategoryListView.as_view(), + path('/type/tags/', views.EstablishmentTagCategoryListView.as_view(), name='tag-category-list'), + path('attach-tag/', views.EstablishmentTagCreateView.as_view(), name='attach-tag'), path('menus/', views.MenuListCreateView.as_view(), name='menu-list'), path('menus//', views.MenuRUDView.as_view(), name='menu-rud'), path('plates/', views.PlateListCreateView.as_view(), name='plates'), @@ -36,4 +37,4 @@ urlpatterns = [ path('subtypes//', views.EstablishmentSubtypeRUDView.as_view(), name='subtype-rud'), path('subtypes/attach-tag-category/', views.EstablishmentSubTypeAttachTagCategoryView.as_view(), name='subtype-attach-tag-category'), -] \ No newline at end of file +] diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index 84855982..fd61485d 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -91,6 +91,11 @@ class EstablishmentTypeAttachTagCategoryView(generics.CreateAPIView): return Response(status=status.HTTP_200_OK) +class EstablishmentTagCreateView(EstablishmentMixinViews, generics.CreateAPIView): + """Attach tag to establishment.""" + serializer_class = serializers.EstablishmentTagCreateSerializer + + class MenuListCreateView(generics.ListCreateAPIView): """Menu list create view.""" serializer_class = serializers.MenuSerializers @@ -206,9 +211,3 @@ class EstablishmentSubTypeAttachTagCategoryView(generics.CreateAPIView): """Overridden post-method.""" super(EstablishmentSubTypeAttachTagCategoryView, self).post(request) return Response(status=status.HTTP_200_OK) - - -# class EstablishmentTagListCreateView(generics.CreateAPIView): -# """Establishment tag list/create view.""" -# serializer_class = serializers.EstablishmentTagCategoryListCreateSerializer -# queryset = models.EstablishmentTag.objects.all() From 47993a7155c8a682c4ec9132ff32dcb273416aef Mon Sep 17 00:00:00 2001 From: Anatoly Date: Mon, 14 Oct 2019 10:22:17 +0300 Subject: [PATCH 034/223] added endpoint ot get list of tag categories with tags by establishment type --- apps/establishment/urls/back.py | 1 + apps/establishment/views/back.py | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/apps/establishment/urls/back.py b/apps/establishment/urls/back.py index c21aecc8..f1afe1df 100644 --- a/apps/establishment/urls/back.py +++ b/apps/establishment/urls/back.py @@ -30,6 +30,7 @@ urlpatterns = [ path('employees/', views.EmployeeListCreateView.as_view(), name='employees'), path('employees//', views.EmployeeRUDView.as_view(), name='employees-rud'), path('types/', views.EstablishmentTypeListCreateView.as_view(), name='type-list'), + path('types/tags/', views.EstablishmentTypeTagListView.as_view(), name='type-tag-list'), path('types//', views.EstablishmentTypeRUDView.as_view(), name='type-rud'), path('types/attach-tag-category/', views.EstablishmentTypeAttachTagCategoryView.as_view(), name='type-attach-tag-category'), diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index fd61485d..09bf2492 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -186,6 +186,13 @@ class EstablishmentTypeRUDView(generics.RetrieveUpdateDestroyAPIView): queryset = models.EstablishmentType.objects.all() +class EstablishmentTypeTagListView(generics.ListAPIView): + """List of tags with categories by establishment type.""" + serializer_class = serializers.EstablishmentTagsByType + queryset = models.EstablishmentType.objects.all() + pagination_class = None + + class EstablishmentSubtypeListCreateView(generics.ListCreateAPIView): """Establishment subtype list/create view.""" serializer_class = serializers.EstablishmentSubTypeBaseSerializer From 9e237d465711b6f890ee878266828acf9a6b3394 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Mon, 14 Oct 2019 11:40:03 +0300 Subject: [PATCH 035/223] removed endpoint to display a list of tags by establishment id, added instead filter to filter tags by establishment id --- apps/account/views/common.py | 1 + apps/establishment/filters.py | 14 ++++++++++++++ apps/establishment/urls/back.py | 2 -- apps/establishment/views/back.py | 28 ++++------------------------ 4 files changed, 19 insertions(+), 26 deletions(-) diff --git a/apps/account/views/common.py b/apps/account/views/common.py index cb0d84d7..0a9a4d98 100644 --- a/apps/account/views/common.py +++ b/apps/account/views/common.py @@ -91,6 +91,7 @@ class ConfirmEmailView(JWTGenericViewMixin): else: raise utils_exceptions.UserNotFoundError() + class ConfirmPasswordView(JWTGenericViewMixin): """View for applying newly set password""" diff --git a/apps/establishment/filters.py b/apps/establishment/filters.py index 51b207dc..13d951b6 100644 --- a/apps/establishment/filters.py +++ b/apps/establishment/filters.py @@ -26,3 +26,17 @@ class EstablishmentFilter(filters.FilterSet): if value not in EMPTY_VALUES: return queryset.search(value, locale=self.request.locale) return queryset + + +class EstablishmentTypeTagFilter(filters.FilterSet): + """Establishment tag filter set.""" + + type_id = filters.NumberFilter(field_name='id') + + class Meta: + """Meta class.""" + + model = models.EstablishmentType + fields = ( + 'type_id', + ) diff --git a/apps/establishment/urls/back.py b/apps/establishment/urls/back.py index f1afe1df..3db81dce 100644 --- a/apps/establishment/urls/back.py +++ b/apps/establishment/urls/back.py @@ -14,8 +14,6 @@ urlpatterns = [ name='schedule-rud'), path('/schedule/', views.EstablishmentScheduleCreateView.as_view(), name='schedule-create'), - path('/type/tags/', views.EstablishmentTagCategoryListView.as_view(), - name='tag-category-list'), path('attach-tag/', views.EstablishmentTagCreateView.as_view(), name='attach-tag'), path('menus/', views.MenuListCreateView.as_view(), name='menu-list'), path('menus//', views.MenuRUDView.as_view(), name='menu-rud'), diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index 09bf2492..a5e2ff47 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -1,10 +1,11 @@ """Establishment app views.""" from django.shortcuts import get_object_or_404 -from rest_framework import generics, status +from rest_framework import generics, status, permissions from establishment import models, serializers from rest_framework.response import Response from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer +from establishment.filters import EstablishmentTypeTagFilter class EstablishmentMixinViews: @@ -54,29 +55,6 @@ class EstablishmentScheduleCreateView(generics.CreateAPIView): serializer_class = ScheduleCreateSerializer -class EstablishmentTagCategoryListView(EstablishmentMixinViews, generics.RetrieveAPIView): - """View for establishment tag categories.""" - serializer_class = serializers.EstablishmentTagsByType - pagination_class = None - - def get_object(self): - """ - Returns the object the view is displaying. - """ - queryset = super(EstablishmentTagCategoryListView, self).get_queryset() - - # Perform the lookup filtering. - lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field - - filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]} - obj = get_object_or_404(queryset, **filter_kwargs) - - # May raise a permission denied - self.check_object_permissions(self.request, obj) - - return obj.establishment_type - - class EstablishmentTypeAttachTagCategoryView(generics.CreateAPIView): """Attach tag category to establishment type.""" serializer_class = serializers.EstablishmentTypeTagCategoryBaseSerializer @@ -190,6 +168,8 @@ class EstablishmentTypeTagListView(generics.ListAPIView): """List of tags with categories by establishment type.""" serializer_class = serializers.EstablishmentTagsByType queryset = models.EstablishmentType.objects.all() + filter_class = EstablishmentTypeTagFilter + permission_classes = (permissions.AllowAny, ) pagination_class = None From 469f7bdf4cb506c8a317e3a127a96bff73cdc0e1 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Mon, 14 Oct 2019 12:24:26 +0300 Subject: [PATCH 036/223] added prefetch select related --- apps/establishment/models.py | 15 +++++++++++++++ apps/establishment/views/back.py | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 77992f9d..195f029d 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -16,6 +16,7 @@ from phonenumber_field.modelfields import PhoneNumberField from collection.models import Collection from location.models import Address from main.models import Award +from tag.models import Tag, TagCategory from review.models import Review from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin, TranslatedFieldsMixin, BaseAttributes) @@ -24,6 +25,18 @@ from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin, class EstablishmentTypeQuerySet(models.QuerySet): """QuerySet for model EstablishmentType.""" + def with_base_related(self): + """Return QuerySet with base related.""" + return self.prefetch_related( + models.Prefetch('tag_categories', + EstablishmentTypeTagCategory.objects.select_related('tag_category')), + models.Prefetch('establishmentsubtype_set', + EstablishmentSubType.objects.prefetch_related( + models.Prefetch( + 'tag_categories', + EstablishmentSubTypeTagCategory.objects.select_related('tag_category')))) + ) + # todo: establishment type&subtypes check class EstablishmentType(TranslatedFieldsMixin, ProjectBaseMixin): @@ -35,6 +48,8 @@ class EstablishmentType(TranslatedFieldsMixin, ProjectBaseMixin): help_text='{"en-GB":"some text"}') use_subtypes = models.BooleanField(_('Use subtypes'), default=True) + objects = EstablishmentTypeQuerySet.as_manager() + class Meta: """Meta class.""" diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index a5e2ff47..543dbd3e 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -167,7 +167,7 @@ class EstablishmentTypeRUDView(generics.RetrieveUpdateDestroyAPIView): class EstablishmentTypeTagListView(generics.ListAPIView): """List of tags with categories by establishment type.""" serializer_class = serializers.EstablishmentTagsByType - queryset = models.EstablishmentType.objects.all() + queryset = models.EstablishmentType.objects.with_base_related() filter_class = EstablishmentTypeTagFilter permission_classes = (permissions.AllowAny, ) pagination_class = None From b3eac3666b6cfa59f6667f668560d2ec9c6a9f52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Mon, 14 Oct 2019 14:48:13 +0300 Subject: [PATCH 037/223] Standart user, guest. --- .../migrations/0011_merge_20191014_0839.py | 14 +++ apps/comment/permissions.py | 28 ----- apps/comment/tests.py | 103 +---------------- apps/comment/views/back.py | 2 +- apps/utils/permissions.py | 63 ++++++++++- apps/utils/tests/__init__.py | 0 apps/utils/tests/tests_json_field.py | 37 +++++++ apps/utils/tests/tests_permissions.py | 104 ++++++++++++++++++ .../{tests.py => tests/tests_translated.py} | 38 ------- 9 files changed, 220 insertions(+), 169 deletions(-) create mode 100644 apps/account/migrations/0011_merge_20191014_0839.py delete mode 100644 apps/comment/permissions.py create mode 100644 apps/utils/tests/__init__.py create mode 100644 apps/utils/tests/tests_json_field.py create mode 100644 apps/utils/tests/tests_permissions.py rename apps/utils/{tests.py => tests/tests_translated.py} (80%) diff --git a/apps/account/migrations/0011_merge_20191014_0839.py b/apps/account/migrations/0011_merge_20191014_0839.py new file mode 100644 index 00000000..653f39b7 --- /dev/null +++ b/apps/account/migrations/0011_merge_20191014_0839.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.4 on 2019-10-14 08:39 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0009_auto_20191011_1123'), + ('account', '0010_user_password_confirmed'), + ] + + operations = [ + ] diff --git a/apps/comment/permissions.py b/apps/comment/permissions.py deleted file mode 100644 index 6d691c07..00000000 --- a/apps/comment/permissions.py +++ /dev/null @@ -1,28 +0,0 @@ -from rest_framework import permissions -from account.models import UserRole, Role, User - - -class IsCommentModerator(permissions.IsAuthenticatedOrReadOnly): - """ - Object-level permission to only allow owners of an object to edit it. - Assumes the model instance has an `owner` attribute. - """ - - def has_object_permission(self, request, view, obj): - # Read permissions are allowed to any request, - # so we'll always allow GET, HEAD or OPTIONS requests. - if request.method in permissions.SAFE_METHODS or \ - obj.user == request.user or request.user.is_superuser: - return True - - # Must have role - role = Role.objects.filter(role=Role.COMMENTS_MODERATOR, - country__languages__id=obj.language_id)\ - .first() # 'Comments moderator' - - is_access = UserRole.objects.filter(user=request.user, role=role).exists() - if obj.user != request.user and is_access: - return True - - return False - diff --git a/apps/comment/tests.py b/apps/comment/tests.py index 949ba597..6c6c3d6a 100644 --- a/apps/comment/tests.py +++ b/apps/comment/tests.py @@ -1,64 +1,12 @@ -from rest_framework.test import APITestCase from rest_framework import status -from authorization.tests.tests_authorization import get_tokens_for_user -from django.urls import reverse -from django.contrib.contenttypes.models import ContentType from http.cookies import SimpleCookie -from location.models import Country -from account.models import Role, User, UserRole -from comment.models import Comment -from translation.models import Language +from account.models import User +from utils.tests.tests_permissions import BasePermissionTests -class CommentModeratorPermissionTests(APITestCase): +class CommentModeratorPermissionTests(BasePermissionTests): def setUp(self): - - self.lang = Language.objects.create( - title='Russia', - locale='ru-RU' - ) - self.lang.save() - - self.country_ru = Country.objects.create( - name='{"ru-RU":"Russia"}', - code='23', - low_price=15, - high_price=150000, - ) - self.country_ru.languages.add(self.lang) - self.country_ru.save() - - self.role = Role.objects.create( - role=2, - country=self.country_ru - ) - self.role.save() - - self.moderator = User.objects.create_user(username='moderator', - email='moderator@mail.com', - password='passwordmoderator') - - self.userRole = UserRole.objects.create( - user=self.moderator, - role=self.role - ) - self.userRole.save() - - content_type = ContentType.objects.get(app_label='location', model='country') - - self.user_test = get_tokens_for_user() - self.comment = Comment.objects.create(text='Test comment', mark=1, - user=self.user_test["user"], - object_id= self.country_ru.pk, - content_type_id=content_type.id, - language=self.lang - ) - self.comment.save() - self.url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id}) - - def test_get(self): - response = self.client.get(self.url, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) + super().setUp() def test_put_moderator(self): tokens = User.create_jwt_tokens(self.moderator) @@ -76,48 +24,5 @@ class CommentModeratorPermissionTests(APITestCase): response = self.client.put(self.url, data=data, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) - def test_put_other_user(self): - other_user = User.objects.create_user(username='test', - email='test@mail.com', - password='passwordtest') - - tokens = User.create_jwt_tokens(other_user) - - self.client.cookies = SimpleCookie( - {'access_token': tokens.get('access_token'), - 'refresh_token': tokens.get('access_token')}) - - data = { - "id": self.comment.id, - "text": "test text moderator", - "mark": 1, - "user": other_user.id - } - - response = self.client.put(self.url, data=data, format='json') - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_put_super_user(self): - super_user = User.objects.create_user(username='super', - email='super@mail.com', - password='passwordtestsuper', - is_superuser=True) - - tokens = User.create_jwt_tokens(super_user) - - self.client.cookies = SimpleCookie( - {'access_token': tokens.get('access_token'), - 'refresh_token': tokens.get('access_token')}) - - data = { - "id": self.comment.id, - "text": "test text moderator", - "mark": 1, - "user": super_user.id - } - - response = self.client.put(self.url, data=data, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) - diff --git a/apps/comment/views/back.py b/apps/comment/views/back.py index 77edfa97..7f066f30 100644 --- a/apps/comment/views/back.py +++ b/apps/comment/views/back.py @@ -1,7 +1,7 @@ from rest_framework import generics, permissions from comment.serializers import back as serializers from comment import models -from comment.permissions import IsCommentModerator +from utils.permissions import IsCommentModerator class CommentLstView(generics.ListCreateAPIView): diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 09b24ecd..2d406850 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -1,12 +1,13 @@ """Project custom permissions""" -from rest_framework.permissions import BasePermission +from rest_framework import permissions from rest_framework_simplejwt.tokens import AccessToken +from account.models import UserRole, Role from authorization.models import JWTRefreshToken from utils.tokens import GMRefreshToken -class IsAuthenticatedAndTokenIsValid(BasePermission): +class IsAuthenticatedAndTokenIsValid(permissions.BasePermission): """ Check if user has a valid token and authenticated """ @@ -24,7 +25,7 @@ class IsAuthenticatedAndTokenIsValid(BasePermission): return False -class IsRefreshTokenValid(BasePermission): +class IsRefreshTokenValid(permissions.BasePermission): """ Check if user has a valid refresh token and authenticated """ @@ -38,3 +39,59 @@ class IsRefreshTokenValid(BasePermission): return refresh_token_qs.exists() else: return False + + def has_object_permission(self, request, view, obj): + # Read permissions are allowed to any request, + # so we'll always allow GET, HEAD or OPTIONS requests. + if request.method in permissions.SAFE_METHODS or \ + obj.user == request.user or request.user.is_superuser: + return True + return False + + +class IsGuest(permissions.IsAuthenticatedOrReadOnly): + """ + Object-level permission to only allow owners of an object to edit it. + """ + def has_object_permission(self, request, view, obj): + if request.method in permissions.SAFE_METHODS: + return True + + return False + + +class IsStandardUser(IsGuest): + """ + Object-level permission to only allow owners of an object to edit it. + Assumes the model instance has an `owner` attribute. + """ + def has_object_permission(self, request, view, obj): + # Read permissions are allowed to any request + if super().has_object_permission(request, view, obj) or\ + obj.user == request.user or request.user.is_superuser: + return True + return False + + +class IsCommentModerator(IsStandardUser): + """ + Object-level permission to only allow owners of an object to edit it. + Assumes the model instance has an `owner` attribute. + """ + + def has_object_permission(self, request, view, obj): + # Read permissions are allowed to any request. + + if super().has_object_permission(request, view, obj): + return True + + # Must have role + role = Role.objects.filter(role=Role.COMMENTS_MODERATOR, + country__languages__id=obj.language_id)\ + .first() # 'Comments moderator' + + is_access = UserRole.objects.filter(user=request.user, role=role).exists() + if obj.user != request.user and is_access: + return True + + return False diff --git a/apps/utils/tests/__init__.py b/apps/utils/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/utils/tests/tests_json_field.py b/apps/utils/tests/tests_json_field.py new file mode 100644 index 00000000..c0207def --- /dev/null +++ b/apps/utils/tests/tests_json_field.py @@ -0,0 +1,37 @@ +from django.test import TestCase +from translation.models import Language +from django.core import exceptions +from utils.serializers import validate_tjson + + +class ValidJSONTest(TestCase): + + def test_valid_json(self): + lang = Language.objects.create(title='English', locale='en-GB') + lang.save() + + data = 'str' + + with self.assertRaises(exceptions.ValidationError) as err: + validate_tjson(data) + + self.assertEqual(err.exception.code, 'invalid_json') + + data = { + "string": "value" + } + + with self.assertRaises(exceptions.ValidationError) as err: + validate_tjson(data) + + self.assertEqual(err.exception.code, 'invalid_translated_keys') + + data = { + "en-GB": "English" + } + + try: + validate_tjson(data) + self.assertTrue(True) + except exceptions.ValidationError: + self.assert_(False, "Test json translated FAILED") \ No newline at end of file diff --git a/apps/utils/tests/tests_permissions.py b/apps/utils/tests/tests_permissions.py new file mode 100644 index 00000000..1e1e0e81 --- /dev/null +++ b/apps/utils/tests/tests_permissions.py @@ -0,0 +1,104 @@ +from rest_framework.test import APITestCase +from rest_framework import status +from authorization.tests.tests_authorization import get_tokens_for_user +from django.urls import reverse +from django.contrib.contenttypes.models import ContentType +from http.cookies import SimpleCookie +from location.models import Country +from account.models import Role, User, UserRole +from comment.models import Comment +from translation.models import Language + + +class BasePermissionTests(APITestCase): + def setUp(self): + + self.lang = Language.objects.create( + title='Russia', + locale='ru-RU' + ) + self.lang.save() + + self.country_ru = Country.objects.create( + name='{"ru-RU":"Russia"}', + code='23', + low_price=15, + high_price=150000, + ) + self.country_ru.languages.add(self.lang) + self.country_ru.save() + + self.role = Role.objects.create( + role=2, + country=self.country_ru + ) + self.role.save() + + self.moderator = User.objects.create_user(username='moderator', + email='moderator@mail.com', + password='passwordmoderator') + + self.userRole = UserRole.objects.create( + user=self.moderator, + role=self.role + ) + self.userRole.save() + + content_type = ContentType.objects.get(app_label='location', model='country') + + self.user_test = get_tokens_for_user() + self.comment = Comment.objects.create(text='Test comment', mark=1, + user=self.user_test["user"], + object_id= self.country_ru.pk, + content_type_id=content_type.id, + language=self.lang + ) + self.comment.save() + self.url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id}) + + def test_get(self): + response = self.client.get(self.url, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_put_other_user(self): + other_user = User.objects.create_user(username='test', + email='test@mail.com', + password='passwordtest') + + tokens = User.create_jwt_tokens(other_user) + + self.client.cookies = SimpleCookie( + {'access_token': tokens.get('access_token'), + 'refresh_token': tokens.get('access_token')}) + + data = { + "id": self.comment.id, + "text": "test text moderator", + "mark": 1, + "user": other_user.id + } + + response = self.client.put(self.url, data=data, format='json') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_put_super_user(self): + super_user = User.objects.create_user(username='super', + email='super@mail.com', + password='passwordtestsuper', + is_superuser=True) + + tokens = User.create_jwt_tokens(super_user) + + self.client.cookies = SimpleCookie( + {'access_token': tokens.get('access_token'), + 'refresh_token': tokens.get('access_token')}) + + data = { + "id": self.comment.id, + "text": "test text moderator", + "mark": 1, + "user": super_user.id + } + + response = self.client.put(self.url, data=data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/apps/utils/tests.py b/apps/utils/tests/tests_translated.py similarity index 80% rename from apps/utils/tests.py rename to apps/utils/tests/tests_translated.py index 0eaf343d..8f5caaaf 100644 --- a/apps/utils/tests.py +++ b/apps/utils/tests/tests_translated.py @@ -8,11 +8,6 @@ from http.cookies import SimpleCookie from account.models import User from news.models import News, NewsType -from django.test import TestCase -from translation.models import Language -from django.core import exceptions -from .serializers import validate_tjson - from establishment.models import Establishment, EstablishmentType, Employee @@ -125,36 +120,3 @@ class BaseAttributeTests(BaseTestCase): employee.refresh_from_db() self.assertEqual(modify_user, employee.modified_by) self.assertEqual(self.user, employee.created_by) - - -class ValidJSONTest(TestCase): - - def test_valid_json(self): - lang = Language.objects.create(title='English', locale='en-GB') - lang.save() - - data = 'str' - - with self.assertRaises(exceptions.ValidationError) as err: - validate_tjson(data) - - self.assertEqual(err.exception.code, 'invalid_json') - - data = { - "string": "value" - } - - with self.assertRaises(exceptions.ValidationError) as err: - validate_tjson(data) - - self.assertEqual(err.exception.code, 'invalid_translated_keys') - - data = { - "en-GB": "English" - } - - try: - validate_tjson(data) - self.assertTrue(True) - except exceptions.ValidationError: - self.assert_(False, "Test json translated FAILED") \ No newline at end of file From 7b4553663cee20a5147ec54a193a85705167b24c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Mon, 14 Oct 2019 15:45:41 +0300 Subject: [PATCH 038/223] Fix --- apps/account/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/account/models.py b/apps/account/models.py index 24cc6d95..eb95fac1 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -24,7 +24,7 @@ class Role(ProjectBaseMixin): STANDARD_USER = 1 COMMENTS_MODERATOR = 2 - ROLE_CHOICES =( + ROLE_CHOICES = ( (STANDARD_USER, 'Standard user'), (COMMENTS_MODERATOR, 'Comments moderator'), ) @@ -243,4 +243,4 @@ class User(AbstractUser): class UserRole(ProjectBaseMixin): """UserRole model.""" user = models.ForeignKey(User, verbose_name=_('User'), on_delete=models.CASCADE) - role = models.ForeignKey(Role, verbose_name=_('Role'), on_delete=models.SET_NULL, null=True) \ No newline at end of file + role = models.ForeignKey(Role, verbose_name=_('Role'), on_delete=models.SET_NULL, null=True) From 0656e2ae3299e665732ba246d9b923e3bc4dba76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Mon, 14 Oct 2019 15:48:31 +0300 Subject: [PATCH 039/223] Confirmed email for user standard --- apps/utils/permissions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 2d406850..50bf78d2 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -68,7 +68,8 @@ class IsStandardUser(IsGuest): def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request if super().has_object_permission(request, view, obj) or\ - obj.user == request.user or request.user.is_superuser: + (obj.user == request.user and obj.user.email_confirmed) \ + or request.user.is_superuser: return True return False From 1bd3cc91703bf26fd98707f8256820c59c2f741a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Mon, 14 Oct 2019 17:32:34 +0300 Subject: [PATCH 040/223] 1 --- apps/account/models.py | 5 ++++- apps/utils/permissions.py | 20 +++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/apps/account/models.py b/apps/account/models.py index eb95fac1..4f227b56 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -23,14 +23,17 @@ class Role(ProjectBaseMixin): """Base Role model.""" STANDARD_USER = 1 COMMENTS_MODERATOR = 2 + COUNTRY_ADMIN = 3 ROLE_CHOICES = ( (STANDARD_USER, 'Standard user'), (COMMENTS_MODERATOR, 'Comments moderator'), + (COUNTRY_ADMIN, 'Country admin'), ) role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES, null=False, blank=False) - country = models.ForeignKey(Country, verbose_name=_('Country'), on_delete=models.CASCADE) + country = models.ForeignKey(Country, verbose_name=_('Country'), + null=True, blank=True, on_delete=models.SET_NULL) # is_list = models.BooleanField(verbose_name=_('list'), default=True, null=False) # is_create = models.BooleanField(verbose_name=_('create'), default=False, null=False) # is_update = models.BooleanField(verbose_name=_('update'), default=False, null=False) diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 50bf78d2..47689ed5 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -54,7 +54,7 @@ class IsGuest(permissions.IsAuthenticatedOrReadOnly): Object-level permission to only allow owners of an object to edit it. """ def has_object_permission(self, request, view, obj): - if request.method in permissions.SAFE_METHODS: + if request.method in permissions.SAFE_METHODS or request.user.is_superuser: return True return False @@ -68,8 +68,7 @@ class IsStandardUser(IsGuest): def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request if super().has_object_permission(request, view, obj) or\ - (obj.user == request.user and obj.user.email_confirmed) \ - or request.user.is_superuser: + (obj.user == request.user and obj.user.email_confirmed): return True return False @@ -96,3 +95,18 @@ class IsCommentModerator(IsStandardUser): return True return False + + +class IsCountryAdmin(IsGuest): + + def has_object_permission(self, request, view, obj): + # Read permissions are allowed to any request. + + # Must have role + role = Role.objects.filter(role=Role.COUNTRY_ADMIN).first() # 'Country admin' + is_access = UserRole.objects.filter(user=request.user, role=role).exists() + + if super().has_object_permission(request, view, obj) and is_access: + return True + + return False From 43531d88dc7ebd897737363f131eec88fb22927e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Tue, 15 Oct 2019 10:05:31 +0300 Subject: [PATCH 041/223] Country id --- .../migrations/0003_auto_20191015_0704.py | 24 +++++++++++++++++++ apps/comment/models.py | 8 ++++++- apps/utils/permissions.py | 2 +- apps/utils/tests/tests_permissions.py | 2 +- 4 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 apps/comment/migrations/0003_auto_20191015_0704.py diff --git a/apps/comment/migrations/0003_auto_20191015_0704.py b/apps/comment/migrations/0003_auto_20191015_0704.py new file mode 100644 index 00000000..09296253 --- /dev/null +++ b/apps/comment/migrations/0003_auto_20191015_0704.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2.4 on 2019-10-15 07:04 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('location', '0012_data_migrate'), + ('comment', '0002_comment_language'), + ] + + operations = [ + migrations.RemoveField( + model_name='comment', + name='language', + ), + migrations.AddField( + model_name='comment', + name='country', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='location.Country', verbose_name='Country'), + ), + ] diff --git a/apps/comment/models.py b/apps/comment/models.py index 0193055d..08ae2cde 100644 --- a/apps/comment/models.py +++ b/apps/comment/models.py @@ -7,6 +7,8 @@ from account.models import User from utils.models import ProjectBaseMixin from utils.querysets import ContentTypeQuerySetMixin from translation.models import Language +from location.models import Country + class CommentQuerySet(ContentTypeQuerySetMixin): """QuerySets for Comment model.""" @@ -41,7 +43,11 @@ class Comment(ProjectBaseMixin): content_object = generic.GenericForeignKey('content_type', 'object_id') objects = CommentQuerySet.as_manager() - language = models.ForeignKey(Language, verbose_name=_('Locale'), on_delete=models.SET_NULL, null=True) + country = models.ForeignKey(Country, verbose_name=_('Country'), + on_delete=models.SET_NULL, null=True) + # language = models.ForeignKey(Language, verbose_name=_('Locale'), + # on_delete=models.SET_NULL, null=True + # ) class Meta: """Meta class""" diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 47689ed5..f0da64e4 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -103,7 +103,7 @@ class IsCountryAdmin(IsGuest): # Read permissions are allowed to any request. # Must have role - role = Role.objects.filter(role=Role.COUNTRY_ADMIN).first() # 'Country admin' + role = Role.objects.filter(role=Role.COUNTRY_ADMIN, country_id=obj.country_id).first() # 'Country admin' is_access = UserRole.objects.filter(user=request.user, role=role).exists() if super().has_object_permission(request, view, obj) and is_access: diff --git a/apps/utils/tests/tests_permissions.py b/apps/utils/tests/tests_permissions.py index 1e1e0e81..f68f71d6 100644 --- a/apps/utils/tests/tests_permissions.py +++ b/apps/utils/tests/tests_permissions.py @@ -51,7 +51,7 @@ class BasePermissionTests(APITestCase): user=self.user_test["user"], object_id= self.country_ru.pk, content_type_id=content_type.id, - language=self.lang + country=self.country_ru ) self.comment.save() self.url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id}) From 5562a9e7c981aa825aed03c0e1e58f3bb6e7fc32 Mon Sep 17 00:00:00 2001 From: Semyon Yekhmenin Date: Tue, 15 Oct 2019 12:16:23 +0300 Subject: [PATCH 042/223] 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 043/223] 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 1764cfc08b17f6c36f5cdd85d87a8b9487e2bb1f Mon Sep 17 00:00:00 2001 From: Anatoly Date: Tue, 15 Oct 2019 12:52:21 +0300 Subject: [PATCH 044/223] added urlpath to get establishment tags to all platform --- apps/establishment/urls/common.py | 1 + project/urls/back.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/establishment/urls/common.py b/apps/establishment/urls/common.py index 5d7df146..18b768c0 100644 --- a/apps/establishment/urls/common.py +++ b/apps/establishment/urls/common.py @@ -7,6 +7,7 @@ app_name = 'establishment' urlpatterns = [ path('', views.EstablishmentListView.as_view(), name='list'), + path('types/tags/', views.EstablishmentTypeTagListView.as_view(), name='type-tag-list'), path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(), name='recent-reviews'), path('slug//', views.EstablishmentRetrieveView.as_view(), name='detail'), 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 ff7df815f44aaca0092477e8ad8c2efaf56b07c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Tue, 15 Oct 2019 15:20:44 +0300 Subject: [PATCH 045/223] Fix country_id --- .../account/migrations/0012_merge_20191015_0708.py | 14 ++++++++++++++ apps/comment/models.py | 3 --- apps/utils/permissions.py | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 apps/account/migrations/0012_merge_20191015_0708.py diff --git a/apps/account/migrations/0012_merge_20191015_0708.py b/apps/account/migrations/0012_merge_20191015_0708.py new file mode 100644 index 00000000..91dba02e --- /dev/null +++ b/apps/account/migrations/0012_merge_20191015_0708.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.4 on 2019-10-15 07:08 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0011_merge_20191014_0839'), + ('account', '0011_merge_20191014_1258'), + ] + + operations = [ + ] diff --git a/apps/comment/models.py b/apps/comment/models.py index 08ae2cde..55c7802e 100644 --- a/apps/comment/models.py +++ b/apps/comment/models.py @@ -45,9 +45,6 @@ class Comment(ProjectBaseMixin): objects = CommentQuerySet.as_manager() country = models.ForeignKey(Country, verbose_name=_('Country'), on_delete=models.SET_NULL, null=True) - # language = models.ForeignKey(Language, verbose_name=_('Locale'), - # on_delete=models.SET_NULL, null=True - # ) class Meta: """Meta class""" diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index f0da64e4..3b408b31 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -87,7 +87,7 @@ class IsCommentModerator(IsStandardUser): # Must have role role = Role.objects.filter(role=Role.COMMENTS_MODERATOR, - country__languages__id=obj.language_id)\ + country_id=obj.country_id)\ .first() # 'Comments moderator' is_access = UserRole.objects.filter(user=request.user, role=role).exists() From 9acef83894da6573ad991fb4b8c72dc2cf2f0a96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Tue, 15 Oct 2019 15:23:42 +0300 Subject: [PATCH 046/223] Add country admin --- apps/utils/permissions.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 3b408b31..dec3b848 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -73,7 +73,28 @@ class IsStandardUser(IsGuest): return False -class IsCommentModerator(IsStandardUser): +class IsCountryAdmin(IsStandardUser): + """ + Object-level permission to only allow owners of an object to edit it. + Assumes the model instance has an `owner` attribute. + """ + def has_object_permission(self, request, view, obj): + # Read permissions are allowed to any request. + if super().has_object_permission(request, view, obj): + return True + + # Must have role + role = Role.objects.filter(role=Role.COUNTRY_ADMIN, + country_id=obj.country_id) \ + .first() # 'Comments moderator' + + is_access = UserRole.objects.filter(user=request.user, role=role).exists() + if obj.user != request.user and is_access: + return True + return False + + +class IsCommentModerator(IsCountryAdmin): """ Object-level permission to only allow owners of an object to edit it. Assumes the model instance has an `owner` attribute. From 322cfcd89d21570f2982bf0f83c5692cd5c50ba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Tue, 15 Oct 2019 15:33:28 +0300 Subject: [PATCH 047/223] Fix test --- apps/comment/tests.py | 85 ++++++++++++++++++++++++++- apps/utils/permissions.py | 15 ----- apps/utils/tests/tests_permissions.py | 79 ------------------------- 3 files changed, 82 insertions(+), 97 deletions(-) diff --git a/apps/comment/tests.py b/apps/comment/tests.py index 6c6c3d6a..8cbcee88 100644 --- a/apps/comment/tests.py +++ b/apps/comment/tests.py @@ -1,13 +1,46 @@ -from rest_framework import status -from http.cookies import SimpleCookie -from account.models import User from utils.tests.tests_permissions import BasePermissionTests +from rest_framework import status +from authorization.tests.tests_authorization import get_tokens_for_user +from django.urls import reverse +from django.contrib.contenttypes.models import ContentType +from http.cookies import SimpleCookie +from account.models import Role, User, UserRole +from comment.models import Comment class CommentModeratorPermissionTests(BasePermissionTests): def setUp(self): super().setUp() + self.role = Role.objects.create( + role=2, + country=self.country_ru + ) + self.role.save() + + self.moderator = User.objects.create_user(username='moderator', + email='moderator@mail.com', + password='passwordmoderator') + + self.userRole = UserRole.objects.create( + user=self.moderator, + role=self.role + ) + self.userRole.save() + + content_type = ContentType.objects.get(app_label='location', model='country') + + self.user_test = get_tokens_for_user() + self.comment = Comment.objects.create(text='Test comment', mark=1, + user=self.user_test["user"], + object_id= self.country_ru.pk, + content_type_id=content_type.id, + country=self.country_ru + ) + self.comment.save() + self.url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id}) + + def test_put_moderator(self): tokens = User.create_jwt_tokens(self.moderator) self.client.cookies = SimpleCookie( @@ -24,5 +57,51 @@ class CommentModeratorPermissionTests(BasePermissionTests): response = self.client.put(self.url, data=data, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) + def test_get(self): + response = self.client.get(self.url, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_put_other_user(self): + other_user = User.objects.create_user(username='test', + email='test@mail.com', + password='passwordtest') + + tokens = User.create_jwt_tokens(other_user) + + self.client.cookies = SimpleCookie( + {'access_token': tokens.get('access_token'), + 'refresh_token': tokens.get('access_token')}) + + data = { + "id": self.comment.id, + "text": "test text moderator", + "mark": 1, + "user": other_user.id + } + + response = self.client.put(self.url, data=data, format='json') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + def test_put_super_user(self): + super_user = User.objects.create_user(username='super', + email='super@mail.com', + password='passwordtestsuper', + is_superuser=True) + + tokens = User.create_jwt_tokens(super_user) + + self.client.cookies = SimpleCookie( + {'access_token': tokens.get('access_token'), + 'refresh_token': tokens.get('access_token')}) + + data = { + "id": self.comment.id, + "text": "test text moderator", + "mark": 1, + "user": super_user.id + } + + response = self.client.put(self.url, data=data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index dec3b848..2754a3c5 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -116,18 +116,3 @@ class IsCommentModerator(IsCountryAdmin): return True return False - - -class IsCountryAdmin(IsGuest): - - def has_object_permission(self, request, view, obj): - # Read permissions are allowed to any request. - - # Must have role - role = Role.objects.filter(role=Role.COUNTRY_ADMIN, country_id=obj.country_id).first() # 'Country admin' - is_access = UserRole.objects.filter(user=request.user, role=role).exists() - - if super().has_object_permission(request, view, obj) and is_access: - return True - - return False diff --git a/apps/utils/tests/tests_permissions.py b/apps/utils/tests/tests_permissions.py index f68f71d6..ccb202ab 100644 --- a/apps/utils/tests/tests_permissions.py +++ b/apps/utils/tests/tests_permissions.py @@ -1,12 +1,5 @@ from rest_framework.test import APITestCase -from rest_framework import status -from authorization.tests.tests_authorization import get_tokens_for_user -from django.urls import reverse -from django.contrib.contenttypes.models import ContentType -from http.cookies import SimpleCookie from location.models import Country -from account.models import Role, User, UserRole -from comment.models import Comment from translation.models import Language @@ -28,77 +21,5 @@ class BasePermissionTests(APITestCase): self.country_ru.languages.add(self.lang) self.country_ru.save() - self.role = Role.objects.create( - role=2, - country=self.country_ru - ) - self.role.save() - self.moderator = User.objects.create_user(username='moderator', - email='moderator@mail.com', - password='passwordmoderator') - self.userRole = UserRole.objects.create( - user=self.moderator, - role=self.role - ) - self.userRole.save() - - content_type = ContentType.objects.get(app_label='location', model='country') - - self.user_test = get_tokens_for_user() - self.comment = Comment.objects.create(text='Test comment', mark=1, - user=self.user_test["user"], - object_id= self.country_ru.pk, - content_type_id=content_type.id, - country=self.country_ru - ) - self.comment.save() - self.url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id}) - - def test_get(self): - response = self.client.get(self.url, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) - - def test_put_other_user(self): - other_user = User.objects.create_user(username='test', - email='test@mail.com', - password='passwordtest') - - tokens = User.create_jwt_tokens(other_user) - - self.client.cookies = SimpleCookie( - {'access_token': tokens.get('access_token'), - 'refresh_token': tokens.get('access_token')}) - - data = { - "id": self.comment.id, - "text": "test text moderator", - "mark": 1, - "user": other_user.id - } - - response = self.client.put(self.url, data=data, format='json') - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_put_super_user(self): - super_user = User.objects.create_user(username='super', - email='super@mail.com', - password='passwordtestsuper', - is_superuser=True) - - tokens = User.create_jwt_tokens(super_user) - - self.client.cookies = SimpleCookie( - {'access_token': tokens.get('access_token'), - 'refresh_token': tokens.get('access_token')}) - - data = { - "id": self.comment.id, - "text": "test text moderator", - "mark": 1, - "user": super_user.id - } - - response = self.client.put(self.url, data=data, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) From 9c09f000fb894566f02460300751d40b8baeea36 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 15 Oct 2019 16:32:47 +0300 Subject: [PATCH 048/223] 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 60cdec99e02a9b0cbab04110517cebe4cfe80772 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Tue, 15 Oct 2019 17:17:03 +0300 Subject: [PATCH 049/223] refactor tags --- apps/establishment/admin.py | 28 +-- .../migrations/0037_auto_20191015_1404.py | 54 +++++ apps/establishment/models.py | 202 ++++++++++-------- apps/establishment/serializers/back.py | 182 ++++++++-------- apps/establishment/serializers/common.py | 84 ++++---- apps/establishment/urls/back.py | 12 +- apps/establishment/urls/common.py | 2 +- apps/establishment/views/back.py | 70 +++--- apps/search_indexes/documents/news.py | 18 +- apps/tag/filters.py | 48 +++++ apps/tag/models.py | 12 +- apps/tag/serializers.py | 46 ++-- apps/tag/urls/__init__.py | 0 apps/tag/{urls.py => urls/back.py} | 10 +- apps/tag/urls/web.py | 16 ++ apps/tag/views.py | 28 ++- project/settings/base.py | 2 +- project/urls/back.py | 2 +- project/urls/web.py | 1 + 19 files changed, 494 insertions(+), 323 deletions(-) create mode 100644 apps/establishment/migrations/0037_auto_20191015_1404.py create mode 100644 apps/tag/filters.py create mode 100644 apps/tag/urls/__init__.py rename apps/tag/{urls.py => urls/back.py} (53%) create mode 100644 apps/tag/urls/web.py diff --git a/apps/establishment/admin.py b/apps/establishment/admin.py index 1f200f41..8a5e57de 100644 --- a/apps/establishment/admin.py +++ b/apps/establishment/admin.py @@ -80,17 +80,17 @@ class MenuAdmin(admin.ModelAdmin): category_translated.short_description = _('category') - -@admin.register(models.EstablishmentTypeTagCategory) -class EstablishmentTypeTagCategoryAdmin(admin.ModelAdmin): - """EstablishmentTypeTagCategory admin.""" - - -@admin.register(models.EstablishmentSubTypeTagCategory) -class EstablishmentSubTypeTagCategoryAdmin(admin.ModelAdmin): - """EstablishmentTypeTagCategory admin.""" - - -@admin.register(models.EstablishmentTag) -class EstablishmentTagAdmin(admin.ModelAdmin): - """EstablishmentTag admin.""" +# +# @admin.register(models.EstablishmentTypeTagCategory) +# class EstablishmentTypeTagCategoryAdmin(admin.ModelAdmin): +# """EstablishmentTypeTagCategory admin.""" +# +# +# @admin.register(models.EstablishmentSubTypeTagCategory) +# class EstablishmentSubTypeTagCategoryAdmin(admin.ModelAdmin): +# """EstablishmentTypeTagCategory admin.""" +# +# +# @admin.register(models.EstablishmentTag) +# class EstablishmentTagAdmin(admin.ModelAdmin): +# """EstablishmentTag admin.""" diff --git a/apps/establishment/migrations/0037_auto_20191015_1404.py b/apps/establishment/migrations/0037_auto_20191015_1404.py new file mode 100644 index 00000000..971970e2 --- /dev/null +++ b/apps/establishment/migrations/0037_auto_20191015_1404.py @@ -0,0 +1,54 @@ +# Generated by Django 2.2.4 on 2019-10-15 14:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tag', '0002_auto_20191009_1408'), + ('establishment', '0036_auto_20191011_1356'), + ] + + operations = [ + migrations.RemoveField( + model_name='establishmenttag', + name='establishment', + ), + migrations.RemoveField( + model_name='establishmenttag', + name='tag', + ), + migrations.RemoveField( + model_name='establishmenttypetagcategory', + name='establishment_type', + ), + migrations.RemoveField( + model_name='establishmenttypetagcategory', + name='tag_category', + ), + migrations.AddField( + model_name='establishment', + name='tags', + field=models.ManyToManyField(related_name='establishments', to='tag.Tag', verbose_name='Tag'), + ), + migrations.AddField( + model_name='establishmentsubtype', + name='tag_categories', + field=models.ManyToManyField(related_name='establishment_subtypes', to='tag.TagCategory', verbose_name='Tag'), + ), + migrations.AddField( + model_name='establishmenttype', + name='tag_categories', + field=models.ManyToManyField(related_name='establishment_types', to='tag.TagCategory', verbose_name='Tag'), + ), + migrations.DeleteModel( + name='EstablishmentSubTypeTagCategory', + ), + migrations.DeleteModel( + name='EstablishmentTag', + ), + migrations.DeleteModel( + name='EstablishmentTypeTagCategory', + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 195f029d..14d7e02b 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -27,15 +27,16 @@ class EstablishmentTypeQuerySet(models.QuerySet): def with_base_related(self): """Return QuerySet with base related.""" - return self.prefetch_related( - models.Prefetch('tag_categories', - EstablishmentTypeTagCategory.objects.select_related('tag_category')), - models.Prefetch('establishmentsubtype_set', - EstablishmentSubType.objects.prefetch_related( - models.Prefetch( - 'tag_categories', - EstablishmentSubTypeTagCategory.objects.select_related('tag_category')))) - ) + return self + # return self.prefetch_related( + # models.Prefetch('tag_categories', + # EstablishmentTypeTagCategory.objects.select_related('tag_category')), + # models.Prefetch('establishmentsubtype_set', + # EstablishmentSubType.objects.prefetch_related( + # models.Prefetch( + # 'tag_categories', + # EstablishmentSubTypeTagCategory.objects.select_related('tag_category')))) + # ) # todo: establishment type&subtypes check @@ -47,6 +48,9 @@ class EstablishmentType(TranslatedFieldsMixin, ProjectBaseMixin): name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'), help_text='{"en-GB":"some text"}') use_subtypes = models.BooleanField(_('Use subtypes'), default=True) + tag_categories = models.ManyToManyField('tag.TagCategory', + related_name='establishment_types', + verbose_name=_('Tag')) objects = EstablishmentTypeQuerySet.as_manager() @@ -75,6 +79,9 @@ class EstablishmentSubType(ProjectBaseMixin, TranslatedFieldsMixin): establishment_type = models.ForeignKey(EstablishmentType, on_delete=models.CASCADE, verbose_name=_('Type')) + tag_categories = models.ManyToManyField('tag.TagCategory', + related_name='establishment_subtypes', + verbose_name=_('Tag')) objects = EstablishmentSubTypeManager() @@ -94,14 +101,15 @@ class EstablishmentQuerySet(models.QuerySet): def with_base_related(self): """Return qs with related objects.""" - return self.select_related('address', 'establishment_type').prefetch_related( - models.Prefetch('tags', - EstablishmentTag.objects.select_related('tag')), - models.Prefetch('establishment_type__tag_categories', - EstablishmentTypeTagCategory.objects.select_related('tag_category')), - models.Prefetch('establishment_type__establishmentsubtype_set', - EstablishmentSubType.objects.prefetch_related('tag_categories')), - ) + return self + # return self.select_related('address', 'establishment_type').prefetch_related( + # models.Prefetch('tags', + # EstablishmentTag.objects.select_related('tag')), + # models.Prefetch('establishment_type__tag_categories', + # EstablishmentTypeTagCategory.objects.select_related('tag_category')), + # models.Prefetch('establishment_type__establishmentsubtype_set', + # EstablishmentSubType.objects.prefetch_related('tag_categories')), + # ) def with_extended_related(self): return self.select_related('establishment_type').\ @@ -320,7 +328,11 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): verbose_name=_('Establishment slug'), editable=True) awards = generic.GenericRelation(to='main.Award', related_query_name='establishment') - tags = generic.GenericRelation(to='main.MetaDataContent') + # 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') @@ -575,81 +587,81 @@ class SocialNetwork(models.Model): def __str__(self): return self.title +# +# class EstablishmentTagQuerySet(models.QuerySet): +# """Establishment tag QuerySet.""" +# +# def by_country_code(self, code): +# """Return establishment tags by establishment country code.""" +# return self.filter(establishment__address__city__country__code=code) -class EstablishmentTagQuerySet(models.QuerySet): - """Establishment tag QuerySet.""" - - def by_country_code(self, code): - """Return establishment tags by establishment country code.""" - return self.filter(establishment__address__city__country__code=code) - - -class EstablishmentTag(models.Model): - """Establishment tag model.""" - tag = models.ForeignKey('tag.Tag', - on_delete=models.CASCADE, - related_name='tags', - verbose_name=_('tag')) - establishment = models.ForeignKey('establishment.Establishment', - on_delete=models.CASCADE, - related_name='tags', - verbose_name=_('establishment')) - objects = EstablishmentTagQuerySet.as_manager() - - class Meta: - verbose_name = _('establishment tag') - verbose_name_plural = _('establishment tags') - - -class EstablishmentTypeTagCategoryQuerySet(models.QuerySet): - """EstablishmentTypeTagCategory QuerySet.""" - - def by_country_code(self, code): - """Return establishment tags by country code""" - return self.filter(tag_category__country__code=code) - - def with_base_related(self): - """Return with related relations.""" - return self.select_related('establishment_type', 'tag_category') - - -class EstablishmentTypeTagCategory(models.Model): - """Tag categories based on establishment type.""" - establishment_type = models.ForeignKey(EstablishmentType, - on_delete=models.CASCADE, - related_name='tag_categories', - verbose_name=_('establishment type')) - tag_category = models.ForeignKey('tag.TagCategory', - on_delete=models.CASCADE, - related_name='est_type_tag_categories', - verbose_name=_('tag category')) - objects = EstablishmentTypeTagCategoryQuerySet.as_manager() - - class Meta: - verbose_name = _('establishment type tag categories') - verbose_name_plural = _('establishment type tag categories') - - -class EstablishmentSubTypeTagCategoryQuerySet(models.QuerySet): - """QuerySet for tag categories based on establishment subtype.""" - - def with_base_related(self): - """Return queryset with base related.""" - return self.select_related('establishment_subtype', 'tag_category') - - -class EstablishmentSubTypeTagCategory(models.Model): - """Tag categories based on establishment subtype.""" - establishment_subtype = models.ForeignKey(EstablishmentSubType, - on_delete=models.CASCADE, - related_name='tag_categories', - verbose_name=_('establishment subtype')) - tag_category = models.ForeignKey('tag.TagCategory', - on_delete=models.CASCADE, - related_name='est_subtype_tag_categories', - verbose_name=_('tag category')) - objects = EstablishmentSubTypeTagCategoryQuerySet.as_manager() - - class Meta: - verbose_name = _('establishment subtype tag categories') - verbose_name_plural = _('establishment subtype tag categories') +# +# class EstablishmentTag(models.Model): +# """Establishment tag model.""" +# tag = models.ForeignKey('tag.Tag', +# on_delete=models.CASCADE, +# related_name='tags', +# verbose_name=_('tag')) +# establishment = models.ForeignKey('establishment.Establishment', +# on_delete=models.CASCADE, +# related_name='tags', +# verbose_name=_('establishment')) +# objects = EstablishmentTagQuerySet.as_manager() +# +# class Meta: +# verbose_name = _('establishment tag') +# verbose_name_plural = _('establishment tags') +# +# +# class EstablishmentTypeTagCategoryQuerySet(models.QuerySet): +# """EstablishmentTypeTagCategory QuerySet.""" +# +# def by_country_code(self, code): +# """Return establishment tags by country code""" +# return self.filter(tag_category__country__code=code) +# +# def with_base_related(self): +# """Return with related relations.""" +# return self.select_related('establishment_type', 'tag_category') +# +# +# class EstablishmentTypeTagCategory(models.Model): +# """Tag categories based on establishment type.""" +# establishment_type = models.ForeignKey(EstablishmentType, +# on_delete=models.CASCADE, +# related_name='tag_categories', +# verbose_name=_('establishment type')) +# tag_category = models.ForeignKey('tag.TagCategory', +# on_delete=models.CASCADE, +# related_name='est_type_tag_categories', +# verbose_name=_('tag category')) +# objects = EstablishmentTypeTagCategoryQuerySet.as_manager() +# +# class Meta: +# verbose_name = _('establishment type tag categories') +# verbose_name_plural = _('establishment type tag categories') +# +# +# class EstablishmentSubTypeTagCategoryQuerySet(models.QuerySet): +# """QuerySet for tag categories based on establishment subtype.""" +# +# def with_base_related(self): +# """Return queryset with base related.""" +# return self.select_related('establishment_subtype', 'tag_category') +# +# +# class EstablishmentSubTypeTagCategory(models.Model): +# """Tag categories based on establishment subtype.""" +# establishment_subtype = models.ForeignKey(EstablishmentSubType, +# on_delete=models.CASCADE, +# related_name='tag_categories', +# verbose_name=_('establishment subtype')) +# tag_category = models.ForeignKey('tag.TagCategory', +# on_delete=models.CASCADE, +# related_name='est_subtype_tag_categories', +# verbose_name=_('tag category')) +# objects = EstablishmentSubTypeTagCategoryQuerySet.as_manager() +# +# class Meta: +# verbose_name = _('establishment subtype tag categories') +# verbose_name_plural = _('establishment subtype tag categories') diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index 21f63e17..dca25763 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -5,8 +5,8 @@ from establishment import models from establishment.serializers import ( EstablishmentBaseSerializer, PlateSerializer, ContactEmailsSerializer, ContactPhonesSerializer, SocialNetworkRelatedSerializers, - EstablishmentTypeBaseSerializer, EstablishmentSubTypeBaseSerializer, - EstablishmentTypeTagCategoryBaseSerializer) + EstablishmentTypeBaseSerializer, EstablishmentSubTypeBaseSerializer) + # EstablishmentTypeTagCategoryBaseSerializer) from main.models import Currency from tag.serializers import TagBaseSerializer from utils.decorators import with_base_attributes @@ -146,94 +146,94 @@ class EmployeeBackSerializers(serializers.ModelSerializer): ] -class EstablishmentTagCreateSerializer(serializers.ModelSerializer): - """Serializer for model EstablishmentTag.""" - class Meta: - model = models.EstablishmentTag - fields = [ - 'tag', - 'establishment' - ] - extra_kwargs = { - 'tag': {'write_only': True}, - 'establishment': {'write_only': True}, - } - - def validate(self, attrs): - """Validate method.""" - establishment = attrs.get('establishment') - tag = attrs.get('tag') - - # Check if tag is already added to establishment. - if establishment.tags.filter(tag=tag).exists(): - raise serializers.ValidationError(detail={'detail': _('Tag is already added.')}) - - # Сhecking tag availability for establishment type. - if not establishment.establishment_type.use_subtypes: - qs = establishment.establishment_type.tag_categories.filter(tag_category=tag.category) - else: - # Сhecking tag availability for establishment subtype. - qs = establishment.establishment_type.tag_categories.filter( - establishmentsubtype_set__tag_category=tag.category) - if not qs.exists(): - raise serializers.ValidationError( - detail={'detail': _('Tag is not available for this establishment type|subtype.')}) - return attrs +# class EstablishmentTagCreateSerializer(serializers.ModelSerializer): +# """Serializer for model EstablishmentTag.""" +# class Meta: +# model = models.EstablishmentTag +# fields = [ +# 'tag', +# 'establishment' +# ] +# extra_kwargs = { +# 'tag': {'write_only': True}, +# 'establishment': {'write_only': True}, +# } +# +# def validate(self, attrs): +# """Validate method.""" +# establishment = attrs.get('establishment') +# tag = attrs.get('tag') +# +# # Check if tag is already added to establishment. +# if establishment.tags.filter(tag=tag).exists(): +# raise serializers.ValidationError(detail={'detail': _('Tag is already added.')}) +# +# # Сhecking tag availability for establishment type. +# if not establishment.establishment_type.use_subtypes: +# qs = establishment.establishment_type.tag_categories.filter(tag_category=tag.category) +# else: +# # Сhecking tag availability for establishment subtype. +# qs = establishment.establishment_type.tag_categories.filter( +# establishmentsubtype_set__tag_category=tag.category) +# if not qs.exists(): +# raise serializers.ValidationError( +# detail={'detail': _('Tag is not available for this establishment type|subtype.')}) +# return attrs -class EstablishmentSubTypeTagCategoryBaseSerializer(serializers.ModelSerializer): - """Serializer for intermediate model EstablishmentSubTypeTagCategories.""" - id = serializers.IntegerField(source='tag_category.id', read_only=True) - label_translated = TranslatedField(source='tag_category.label_translated') - tags = TagBaseSerializer(source='tag_category.tags', many=True, read_only=True) - - class Meta: - """Meta class.""" - model = models.EstablishmentSubTypeTagCategory - fields = [ - 'id', - 'label_translated', - 'tags', - 'establishment_subtype', - 'tag_category', - ] - extra_kwargs = { - 'establishment_subtype': {'write_only': True}, - 'tag_category': {'write_only': True}, - } - - def validate(self, attrs): - """Override validate method.""" - if models.EstablishmentTypeTagCategory.objects.filter( - establishment_type=attrs.get('establishment_type'), - tag_category=attrs.get('tag_category')).exists(): - raise serializers.ValidationError(detail={'detail': _('Objects is already attached.')}) - return attrs - - -class EstablishmentSubTypeSerializer(EstablishmentSubTypeBaseSerializer): - """Extended serializer for EstablishmentSubType model with tags.""" - tag_categories = EstablishmentSubTypeTagCategoryBaseSerializer(many=True, read_only=True) - - class Meta(EstablishmentSubTypeBaseSerializer.Meta): - """Meta class""" - fields = [ - 'id', - 'name_translated', - 'tag_categories' - ] - - -class EstablishmentTagsByType(EstablishmentTypeBaseSerializer): - """Tags by establishment type""" - tag_categories = EstablishmentTypeTagCategoryBaseSerializer(many=True) - subtypes = EstablishmentSubTypeSerializer(many=True, source='establishmentsubtype_set') - - class Meta(EstablishmentTypeBaseSerializer.Meta): - """Meta class.""" - fields = [ - 'id', - 'name_translated', - 'tag_categories', - 'subtypes', - ] +# class EstablishmentSubTypeTagCategoryBaseSerializer(serializers.ModelSerializer): +# """Serializer for intermediate model EstablishmentSubTypeTagCategories.""" +# id = serializers.IntegerField(source='tag_category.id', read_only=True) +# label_translated = TranslatedField(source='tag_category.label_translated') +# tags = TagBaseSerializer(source='tag_category.tags', many=True, read_only=True) +# +# class Meta: +# """Meta class.""" +# model = models.EstablishmentSubTypeTagCategory +# fields = [ +# 'id', +# 'label_translated', +# 'tags', +# 'establishment_subtype', +# 'tag_category', +# ] +# extra_kwargs = { +# 'establishment_subtype': {'write_only': True}, +# 'tag_category': {'write_only': True}, +# } +# +# def validate(self, attrs): +# """Override validate method.""" +# if models.EstablishmentTypeTagCategory.objects.filter( +# establishment_type=attrs.get('establishment_type'), +# tag_category=attrs.get('tag_category')).exists(): +# raise serializers.ValidationError(detail={'detail': _('Objects is already attached.')}) +# return attrs +# +# +# class EstablishmentSubTypeSerializer(EstablishmentSubTypeBaseSerializer): +# """Extended serializer for EstablishmentSubType model with tags.""" +# tag_categories = EstablishmentSubTypeTagCategoryBaseSerializer(many=True, read_only=True) +# +# class Meta(EstablishmentSubTypeBaseSerializer.Meta): +# """Meta class""" +# fields = [ +# 'id', +# 'name_translated', +# 'tag_categories' +# ] +# +# +# class EstablishmentTagsByType(EstablishmentTypeBaseSerializer): +# """Tags by establishment type""" +# tag_categories = EstablishmentTypeTagCategoryBaseSerializer(many=True) +# subtypes = EstablishmentSubTypeSerializer(many=True, source='establishmentsubtype_set') +# +# class Meta(EstablishmentTypeBaseSerializer.Meta): +# """Meta class.""" +# fields = [ +# 'id', +# 'name_translated', +# 'tag_categories', +# 'subtypes', +# ] diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 1789da31..7c659c9b 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -139,34 +139,34 @@ class EstablishmentSubTypeBaseSerializer(serializers.ModelSerializer): } -class EstablishmentTypeTagCategoryBaseSerializer(serializers.ModelSerializer): - """Serializer for intermediate model EstablishmentTypeTagCategories.""" - id = serializers.IntegerField(source='tag_category.id', read_only=True) - label_translated = TranslatedField(source='tag_category.label_translated') - tags = TagBaseSerializer(source='tag_category.tags', many=True, read_only=True) - - class Meta: - """Meta class.""" - model = models.EstablishmentTypeTagCategory - fields = [ - 'id', - 'label_translated', - 'tags', - 'establishment_type', - 'tag_category', - ] - extra_kwargs = { - 'establishment_type': {'write_only': True}, - 'tag_category': {'write_only': True}, - } - - def validate(self, attrs): - """Override validate method.""" - if models.EstablishmentTypeTagCategory.objects.filter( - establishment_type=attrs.get('establishment_type'), - tag_category=attrs.get('tag_category')).exists(): - raise serializers.ValidationError(detail={'detail': _('Objects is already attached.')}) - return attrs +# class EstablishmentTypeTagCategoryBaseSerializer(serializers.ModelSerializer): +# """Serializer for intermediate model EstablishmentTypeTagCategories.""" +# id = serializers.IntegerField(source='tag_category.id', read_only=True) +# label_translated = TranslatedField(source='tag_category.label_translated') +# tags = TagBaseSerializer(source='tag_category.tags', many=True, read_only=True) +# +# class Meta: +# """Meta class.""" +# model = models.EstablishmentTypeTagCategory +# fields = [ +# 'id', +# 'label_translated', +# 'tags', +# 'establishment_type', +# 'tag_category', +# ] +# extra_kwargs = { +# 'establishment_type': {'write_only': True}, +# 'tag_category': {'write_only': True}, +# } +# +# def validate(self, attrs): +# """Override validate method.""" +# if models.EstablishmentTypeTagCategory.objects.filter( +# establishment_type=attrs.get('establishment_type'), +# tag_category=attrs.get('tag_category')).exists(): +# raise serializers.ValidationError(detail={'detail': _('Objects is already attached.')}) +# return attrs class EstablishmentEmployeeSerializer(serializers.ModelSerializer): @@ -184,19 +184,19 @@ class EstablishmentEmployeeSerializer(serializers.ModelSerializer): model = models.Employee fields = ('id', 'name', 'position_translated', 'awards', 'priority') - -class EstablishmentTagSerializer(serializers.ModelSerializer): - """Serializer for intermediate model EstablishmentTag.""" - id = serializers.IntegerField(source='tag.id') - label_translated = serializers.CharField(source='tag.label_translated') - - class Meta: - """Meta class.""" - model = models.EstablishmentTag - fields = [ - 'id', - 'label_translated' - ] +# +# class EstablishmentTagSerializer(serializers.ModelSerializer): +# """Serializer for intermediate model EstablishmentTag.""" +# id = serializers.IntegerField(source='tag.id') +# label_translated = serializers.CharField(source='tag.label_translated') +# +# class Meta: +# """Meta class.""" +# model = models.EstablishmentTag +# fields = [ +# 'id', +# 'label_translated' +# ] class EstablishmentBaseSerializer(ProjectModelSerializer): @@ -206,7 +206,7 @@ class EstablishmentBaseSerializer(ProjectModelSerializer): slug = serializers.SlugField(allow_blank=False, required=True, max_length=50) address = AddressBaseSerializer() in_favorites = serializers.BooleanField(allow_null=True) - tags = EstablishmentTagSerializer(many=True) + tags = TagBaseSerializer(read_only=True, many=True) class Meta: """Meta class.""" diff --git a/apps/establishment/urls/back.py b/apps/establishment/urls/back.py index 3db81dce..cdd4d183 100644 --- a/apps/establishment/urls/back.py +++ b/apps/establishment/urls/back.py @@ -14,7 +14,7 @@ urlpatterns = [ name='schedule-rud'), path('/schedule/', views.EstablishmentScheduleCreateView.as_view(), name='schedule-create'), - path('attach-tag/', views.EstablishmentTagCreateView.as_view(), name='attach-tag'), + # path('attach-tag/', views.EstablishmentTagCreateView.as_view(), name='attach-tag'), path('menus/', views.MenuListCreateView.as_view(), name='menu-list'), path('menus//', views.MenuRUDView.as_view(), name='menu-rud'), path('plates/', views.PlateListCreateView.as_view(), name='plates'), @@ -28,12 +28,12 @@ urlpatterns = [ path('employees/', views.EmployeeListCreateView.as_view(), name='employees'), path('employees//', views.EmployeeRUDView.as_view(), name='employees-rud'), path('types/', views.EstablishmentTypeListCreateView.as_view(), name='type-list'), - path('types/tags/', views.EstablishmentTypeTagListView.as_view(), name='type-tag-list'), + # path('types/tags/', views.EstablishmentTypeTagListView.as_view(), name='type-tag-list'), path('types//', views.EstablishmentTypeRUDView.as_view(), name='type-rud'), - path('types/attach-tag-category/', views.EstablishmentTypeAttachTagCategoryView.as_view(), - name='type-attach-tag-category'), + # path('types/attach-tag-category/', views.EstablishmentTypeAttachTagCategoryView.as_view(), + # name='type-attach-tag-category'), path('subtypes/', views.EstablishmentSubtypeListCreateView.as_view(), name='subtype-list'), path('subtypes//', views.EstablishmentSubtypeRUDView.as_view(), name='subtype-rud'), - path('subtypes/attach-tag-category/', views.EstablishmentSubTypeAttachTagCategoryView.as_view(), - name='subtype-attach-tag-category'), + # path('subtypes/attach-tag-category/', views.EstablishmentSubTypeAttachTagCategoryView.as_view(), + # name='subtype-attach-tag-category'), ] diff --git a/apps/establishment/urls/common.py b/apps/establishment/urls/common.py index 18b768c0..65b796ae 100644 --- a/apps/establishment/urls/common.py +++ b/apps/establishment/urls/common.py @@ -7,7 +7,7 @@ app_name = 'establishment' urlpatterns = [ path('', views.EstablishmentListView.as_view(), name='list'), - path('types/tags/', views.EstablishmentTypeTagListView.as_view(), name='type-tag-list'), + # path('types/tags/', views.EstablishmentTypeTagListView.as_view(), name='type-tag-list'), path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(), name='recent-reviews'), path('slug//', views.EstablishmentRetrieveView.as_view(), name='detail'), diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index 543dbd3e..f79707d9 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -55,24 +55,24 @@ class EstablishmentScheduleCreateView(generics.CreateAPIView): serializer_class = ScheduleCreateSerializer -class EstablishmentTypeAttachTagCategoryView(generics.CreateAPIView): - """Attach tag category to establishment type.""" - serializer_class = serializers.EstablishmentTypeTagCategoryBaseSerializer - - def get_queryset(self): - """Override get_queryset method.""" - return models.EstablishmentTypeTagCategory.objects.with_base_related() - - def post(self, request, *args, **kwargs): - """Overridden post-method.""" - super(EstablishmentTypeAttachTagCategoryView, self).post(request) - return Response(status=status.HTTP_200_OK) +# class EstablishmentTypeAttachTagCategoryView(generics.CreateAPIView): +# """Attach tag category to establishment type.""" +# serializer_class = serializers.EstablishmentTypeTagCategoryBaseSerializer +# +# def get_queryset(self): +# """Override get_queryset method.""" +# return models.EstablishmentTypeTagCategory.objects.with_base_related() +# +# def post(self, request, *args, **kwargs): +# """Overridden post-method.""" +# super(EstablishmentTypeAttachTagCategoryView, self).post(request) +# return Response(status=status.HTTP_200_OK) -class EstablishmentTagCreateView(EstablishmentMixinViews, generics.CreateAPIView): - """Attach tag to establishment.""" - serializer_class = serializers.EstablishmentTagCreateSerializer - +# class EstablishmentTagCreateView(EstablishmentMixinViews, generics.CreateAPIView): +# """Attach tag to establishment.""" +# serializer_class = serializers.EstablishmentTagCreateSerializer +# class MenuListCreateView(generics.ListCreateAPIView): """Menu list create view.""" @@ -164,13 +164,13 @@ class EstablishmentTypeRUDView(generics.RetrieveUpdateDestroyAPIView): queryset = models.EstablishmentType.objects.all() -class EstablishmentTypeTagListView(generics.ListAPIView): - """List of tags with categories by establishment type.""" - serializer_class = serializers.EstablishmentTagsByType - queryset = models.EstablishmentType.objects.with_base_related() - filter_class = EstablishmentTypeTagFilter - permission_classes = (permissions.AllowAny, ) - pagination_class = None +# class EstablishmentTypeTagListView(generics.ListAPIView): +# """List of tags with categories by establishment type.""" +# serializer_class = serializers.EstablishmentTagsByType +# queryset = models.EstablishmentType.objects.with_base_related() +# filter_class = EstablishmentTypeTagFilter +# permission_classes = (permissions.AllowAny, ) +# pagination_class = None class EstablishmentSubtypeListCreateView(generics.ListCreateAPIView): @@ -186,15 +186,15 @@ class EstablishmentSubtypeRUDView(generics.RetrieveUpdateDestroyAPIView): queryset = models.EstablishmentSubType.objects.all() -class EstablishmentSubTypeAttachTagCategoryView(generics.CreateAPIView): - """Attach tag category to establishment subtype.""" - serializer_class = serializers.EstablishmentSubTypeTagCategoryBaseSerializer - - def get_queryset(self): - """Override get_queryset method.""" - return models.EstablishmentSubTypeTagCategory.objects.with_base_related() - - def post(self, request, *args, **kwargs): - """Overridden post-method.""" - super(EstablishmentSubTypeAttachTagCategoryView, self).post(request) - return Response(status=status.HTTP_200_OK) +# class EstablishmentSubTypeAttachTagCategoryView(generics.CreateAPIView): +# """Attach tag category to establishment subtype.""" +# serializer_class = serializers.EstablishmentSubTypeTagCategoryBaseSerializer +# +# def get_queryset(self): +# """Override get_queryset method.""" +# return models.EstablishmentSubTypeTagCategory.objects.with_base_related() +# +# def post(self, request, *args, **kwargs): +# """Overridden post-method.""" +# super(EstablishmentSubTypeAttachTagCategoryView, self).post(request) +# return Response(status=status.HTTP_200_OK) diff --git a/apps/search_indexes/documents/news.py b/apps/search_indexes/documents/news.py index 6e0974d8..62049c05 100644 --- a/apps/search_indexes/documents/news.py +++ b/apps/search_indexes/documents/news.py @@ -24,15 +24,15 @@ class NewsDocument(Document): country = fields.ObjectField(properties={'id': fields.IntegerField(), 'code': fields.KeywordField()}) web_url = fields.KeywordField(attr='web_url') - tags = fields.ObjectField( - properties={ - 'id': fields.IntegerField(attr='metadata.id'), - 'label': fields.ObjectField(attr='metadata.label_indexing', - properties=OBJECT_FIELD_PROPERTIES), - 'category': fields.ObjectField(attr='metadata.category', - properties={'id': fields.IntegerField()}) - }, - multi=True) + # tags = fields.ObjectField( + # properties={ + # 'id': fields.IntegerField(attr='metadata.id'), + # 'label': fields.ObjectField(attr='metadata.label_indexing', + # properties=OBJECT_FIELD_PROPERTIES), + # 'category': fields.ObjectField(attr='metadata.category', + # properties={'id': fields.IntegerField()}) + # }, + # multi=True) class Django: diff --git a/apps/tag/filters.py b/apps/tag/filters.py new file mode 100644 index 00000000..95f56b53 --- /dev/null +++ b/apps/tag/filters.py @@ -0,0 +1,48 @@ +"""Tag app filters.""" +from django_filters import rest_framework as filters +from tag import models + + +class TagCategoryFilterSet(filters.FilterSet): + """TagCategory filterset.""" + + # Object type choices + NEWS = 'news' + ESTABLISHMENT = 'establishment' + + TYPE_CHOICES = ( + (NEWS, 'News'), + (ESTABLISHMENT, 'Establishment'), + ) + + type = filters.MultipleChoiceFilter(choices=TYPE_CHOICES, + method='filter_by_type') + + # Establishment type choices + RESTAURANT = 'restaurant' + + ESTABLISHMENT_TYPE_CHOICES = ( + (RESTAURANT, 'restaurant'), + ) + + establishment_type = filters.MultipleChoiceFilter( + choices=ESTABLISHMENT_TYPE_CHOICES, + method='filter_by_establishment_type') + + class Meta: + """Meta class.""" + + model = models.TagCategory + fields = ('type', + 'establishment_type', ) + + def filter_by_type(self, queryset, name, value): + if self.NEWS in value: + queryset = queryset.for_news() + if self.ESTABLISHMENT in value: + queryset = queryset.for_establishments() + return queryset + + # todo: filter by establishment type + def filter_by_establishment_type(self, queryset, name, value): + return queryset.for_establishments() diff --git a/apps/tag/models.py b/apps/tag/models.py index b552bbcb..381e552d 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -32,8 +32,16 @@ class Tag(TranslatedFieldsMixin, models.Model): class TagCategoryQuerySet(models.QuerySet): """Extended queryset for TagCategory model.""" - def by_news_type(self, news_type): - return self.filter(news_types=news_type) + def with_base_related(self): + return self.prefetch_related('tags') + + def for_news(self): + return self.filter(news_types__isnull=True) + + def for_establishments(self): + return self.filter(models.Q(est_subtype_tag_categories__isnull=True) | + models.Q(est_type_tag_categories__isnull=True)).\ + distinct() def with_related(self): return self.select_related('country').prefetch_related('tags') diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py index ecb1b68d..895fe2a1 100644 --- a/apps/tag/serializers.py +++ b/apps/tag/serializers.py @@ -13,23 +13,29 @@ class TagBaseSerializer(serializers.ModelSerializer): """Meta class.""" model = models.Tag - fields = [ + fields = ( 'id', - 'label', 'label_translated', + ) + + +class TagBackOfficeSerializer(TagBaseSerializer): + """Serializer for Tag model for Back office users.""" + + class Meta(TagBaseSerializer.Meta): + """Meta class.""" + + fields = TagBaseSerializer.Meta.fields + ( + 'label', 'category' - ] - extra_kwargs = { - 'label': {'write_only': True}, - 'category': {'write_only': True} - } + ) class TagCategoryBaseSerializer(serializers.ModelSerializer): """Serializer for model TagCategory.""" label_translated = TranslatedField() - country_translated = TranslatedField(source='country.name_translated') + tags = TagBaseSerializer(many=True, read_only=True) class Meta: """Meta class.""" @@ -37,23 +43,29 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer): model = models.TagCategory fields = ( 'id', - 'label', 'label_translated', - 'country', - 'country_translated', - 'public', + 'tags' ) - extra_kwargs = { - 'label': {'write_only': True}, - 'country': {'write_only': True}, - } class TagCategoryDetailSerializer(TagCategoryBaseSerializer): - tags = TagBaseSerializer(many=True) class Meta(TagCategoryBaseSerializer.Meta): """Meta class.""" fields = TagCategoryBaseSerializer.Meta.fields + ('tags', ) + + +class TagCategoryBackOfficeDetailSerializer(TagCategoryDetailSerializer): + + country_translated = TranslatedField(source='country.name_translated') + + class Meta(TagCategoryDetailSerializer.Meta): + """Meta class.""" + + fields = TagCategoryDetailSerializer.Meta.fields + ( + 'news_types', + 'country', + 'country_translated', + ) diff --git a/apps/tag/urls/__init__.py b/apps/tag/urls/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/tag/urls.py b/apps/tag/urls/back.py similarity index 53% rename from apps/tag/urls.py rename to apps/tag/urls/back.py index 0d0b86d2..9f03bb45 100644 --- a/apps/tag/urls.py +++ b/apps/tag/urls/back.py @@ -1,10 +1,16 @@ """Urlconf for app tag.""" from django.urls import path -from . import views +from rest_framework.routers import SimpleRouter +from tag import views app_name = 'tag' +router = SimpleRouter() +router.register(r'', views.TagViewSet) + + urlpatterns = [ - path('', views.TagListCreateView.as_view(), name='list-create'), path('category/', views.TagCategoryListCreateView.as_view(), name='category-list-create'), ] + +urlpatterns += router.urls diff --git a/apps/tag/urls/web.py b/apps/tag/urls/web.py new file mode 100644 index 00000000..c99253eb --- /dev/null +++ b/apps/tag/urls/web.py @@ -0,0 +1,16 @@ +"""Tag app urlpatterns web users.""" +from rest_framework.routers import SimpleRouter +from tag import views + + +app_name = 'tag' + +router = SimpleRouter() +router.register(r'categories', views.TagCategoryViewSet) + +urlpatterns = [ + +] + +urlpatterns += router.urls + diff --git a/apps/tag/views.py b/apps/tag/views.py index 5bf4d045..2bc9d8d2 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -1,19 +1,33 @@ """Tag views.""" -from rest_framework import generics -from tag import serializers, models +from rest_framework import generics, viewsets, mixins +from tag import filters, models, serializers +from rest_framework import permissions -class TagListCreateView(generics.ListCreateAPIView): +class TagViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, + mixins.UpdateModelMixin, mixins.DestroyModelMixin, + viewsets.GenericViewSet): """List/create tag view.""" - queryset = models.Tag.objects.all() - serializer_class = serializers.TagBaseSerializer pagination_class = None + queryset = models.Tag.objects.all() + serializer_class = serializers.TagBackOfficeSerializer class TagCategoryListCreateView(generics.ListCreateAPIView): """List/create tag category view.""" - queryset = models.TagCategory.objects.all() - serializer_class = serializers.TagCategoryBaseSerializer pagination_class = None + permission_classes = (permissions.AllowAny, ) + queryset = models.TagCategory.objects.all() + serializer_class = serializers.TagCategoryBackOfficeDetailSerializer + + +class TagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): + """ViewSet for TagCategory model.""" + + filterset_class = filters.TagCategoryFilterSet + pagination_class = None + permission_classes = (permissions.AllowAny, ) + queryset = models.TagCategory.objects.with_base_related() + serializer_class = serializers.TagCategoryBaseSerializer diff --git a/project/settings/base.py b/project/settings/base.py index 4618887d..1de0c109 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -222,8 +222,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'],), diff --git a/project/urls/back.py b/project/urls/back.py index eb049b9c..389c6f51 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.back', 'tag'), namespace='tag')), path('account/', include('account.urls.back')), path('comment/', include('comment.urls.back')), ] diff --git a/project/urls/web.py b/project/urls/web.py index 89debab2..5bd67207 100644 --- a/project/urls/web.py +++ b/project/urls/web.py @@ -30,6 +30,7 @@ urlpatterns = [ path('location/', include('location.urls.web')), path('main/', include('main.urls')), path('recipes/', include('recipe.urls.web')), + path('tags/', include('tag.urls.web')), path('translation/', include('translation.urls')), path('comments/', include('comment.urls.web')), path('favorites/', include('favorites.urls')), From c94ad3f017e24680ef9b483a0b49ea5139ff99f5 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Tue, 15 Oct 2019 17:48:01 +0300 Subject: [PATCH 050/223] comment some code --- apps/search_indexes/documents/establishment.py | 14 +++++++------- apps/tag/models.py | 7 ++++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index e2147fbc..5d45c45b 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -39,13 +39,13 @@ class EstablishmentDocument(Document): }), }, multi=True) - tags = fields.ObjectField( - properties={ - 'tag': fields.ObjectField(properties={ - 'id': fields.IntegerField(), - }), - }, - multi=True) + # tags = fields.ObjectField( + # properties={ + # 'tag': fields.ObjectField(properties={ + # 'id': fields.IntegerField(), + # }), + # }, + # multi=True) address = fields.ObjectField( properties={ 'id': fields.IntegerField(), diff --git a/apps/tag/models.py b/apps/tag/models.py index 381e552d..32cbfa05 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -39,9 +39,10 @@ class TagCategoryQuerySet(models.QuerySet): return self.filter(news_types__isnull=True) def for_establishments(self): - return self.filter(models.Q(est_subtype_tag_categories__isnull=True) | - models.Q(est_type_tag_categories__isnull=True)).\ - distinct() + return self + # return self.filter(models.Q(est_subtype_tag_categories__isnull=True) | + # models.Q(est_type_tag_categories__isnull=True)).\ + # distinct() def with_related(self): return self.select_related('country').prefetch_related('tags') From 42ae1cbb8773636dbbd956e2742c18360496c6cd Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Tue, 15 Oct 2019 18:13:37 +0300 Subject: [PATCH 051/223] fix tagcategory queryset --- apps/tag/models.py | 13 +++++++++---- apps/tag/serializers.py | 2 +- apps/tag/views.py | 13 ++----------- project/urls/back.py | 2 +- 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/apps/tag/models.py b/apps/tag/models.py index 32cbfa05..3deed7d9 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -33,16 +33,21 @@ class TagCategoryQuerySet(models.QuerySet): """Extended queryset for TagCategory model.""" def with_base_related(self): + """Select related objects.""" return self.prefetch_related('tags') def for_news(self): + """Select tag categories for news.""" return self.filter(news_types__isnull=True) def for_establishments(self): - return self - # return self.filter(models.Q(est_subtype_tag_categories__isnull=True) | - # models.Q(est_type_tag_categories__isnull=True)).\ - # distinct() + """Select tag categories for establishments.""" + return self.filter(models.Q(establishment_types__isnull=False) | + models.Q(establishment_subtypes__isnull=False)) + + def with_tags(self, switcher=True): + """Filter by existing tags.""" + return self.filter(tags__isnull=not switcher) def with_related(self): return self.select_related('country').prefetch_related('tags') diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py index 895fe2a1..946206f4 100644 --- a/apps/tag/serializers.py +++ b/apps/tag/serializers.py @@ -49,7 +49,7 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer): class TagCategoryDetailSerializer(TagCategoryBaseSerializer): - + """Detailed serializer for TagCategory model.""" class Meta(TagCategoryBaseSerializer.Meta): """Meta class.""" diff --git a/apps/tag/views.py b/apps/tag/views.py index 2bc9d8d2..622ec849 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -1,5 +1,5 @@ """Tag views.""" -from rest_framework import generics, viewsets, mixins +from rest_framework import viewsets, mixins from tag import filters, models, serializers from rest_framework import permissions @@ -14,20 +14,11 @@ class TagViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, serializer_class = serializers.TagBackOfficeSerializer -class TagCategoryListCreateView(generics.ListCreateAPIView): - """List/create tag category view.""" - - pagination_class = None - permission_classes = (permissions.AllowAny, ) - queryset = models.TagCategory.objects.all() - serializer_class = serializers.TagCategoryBackOfficeDetailSerializer - - class TagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): """ViewSet for TagCategory model.""" filterset_class = filters.TagCategoryFilterSet pagination_class = None permission_classes = (permissions.AllowAny, ) - queryset = models.TagCategory.objects.with_base_related() + queryset = models.TagCategory.objects.with_tags().with_base_related() serializer_class = serializers.TagCategoryBaseSerializer diff --git a/project/urls/back.py b/project/urls/back.py index 389c6f51..5d221932 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.back', 'tag'), namespace='tag')), + # path('tags/', include(('tag.urls.back', 'tag'), namespace='tag')), path('account/', include('account.urls.back')), path('comment/', include('comment.urls.back')), ] From 9d4c483f514554dd1adc504e2b4d1f47350d46b3 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Tue, 15 Oct 2019 18:25:09 +0300 Subject: [PATCH 052/223] clean models (tags) --- apps/establishment/admin.py | 15 ---- apps/establishment/models.py | 109 +---------------------- apps/establishment/serializers/back.py | 99 +------------------- apps/establishment/serializers/common.py | 44 --------- apps/establishment/urls/back.py | 6 -- apps/establishment/urls/common.py | 1 - apps/establishment/views/back.py | 46 +--------- apps/news/admin.py | 2 +- apps/news/urls/web.py | 2 - apps/news/views.py | 13 --- apps/tag/models.py | 3 - apps/tag/serializers.py | 15 +--- 12 files changed, 8 insertions(+), 347 deletions(-) diff --git a/apps/establishment/admin.py b/apps/establishment/admin.py index 8a5e57de..50c21b90 100644 --- a/apps/establishment/admin.py +++ b/apps/establishment/admin.py @@ -79,18 +79,3 @@ class MenuAdmin(admin.ModelAdmin): return obj.category_translated category_translated.short_description = _('category') - -# -# @admin.register(models.EstablishmentTypeTagCategory) -# class EstablishmentTypeTagCategoryAdmin(admin.ModelAdmin): -# """EstablishmentTypeTagCategory admin.""" -# -# -# @admin.register(models.EstablishmentSubTypeTagCategory) -# class EstablishmentSubTypeTagCategoryAdmin(admin.ModelAdmin): -# """EstablishmentTypeTagCategory admin.""" -# -# -# @admin.register(models.EstablishmentTag) -# class EstablishmentTagAdmin(admin.ModelAdmin): -# """EstablishmentTag admin.""" diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 14d7e02b..c93bbf89 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -16,29 +16,11 @@ from phonenumber_field.modelfields import PhoneNumberField from collection.models import Collection from location.models import Address from main.models import Award -from tag.models import Tag, TagCategory from review.models import Review from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin, TranslatedFieldsMixin, BaseAttributes) -class EstablishmentTypeQuerySet(models.QuerySet): - """QuerySet for model EstablishmentType.""" - - def with_base_related(self): - """Return QuerySet with base related.""" - return self - # return self.prefetch_related( - # models.Prefetch('tag_categories', - # EstablishmentTypeTagCategory.objects.select_related('tag_category')), - # models.Prefetch('establishmentsubtype_set', - # EstablishmentSubType.objects.prefetch_related( - # models.Prefetch( - # 'tag_categories', - # EstablishmentSubTypeTagCategory.objects.select_related('tag_category')))) - # ) - - # todo: establishment type&subtypes check class EstablishmentType(TranslatedFieldsMixin, ProjectBaseMixin): """Establishment type model.""" @@ -52,8 +34,6 @@ class EstablishmentType(TranslatedFieldsMixin, ProjectBaseMixin): related_name='establishment_types', verbose_name=_('Tag')) - objects = EstablishmentTypeQuerySet.as_manager() - class Meta: """Meta class.""" @@ -101,15 +81,8 @@ class EstablishmentQuerySet(models.QuerySet): def with_base_related(self): """Return qs with related objects.""" - return self - # return self.select_related('address', 'establishment_type').prefetch_related( - # models.Prefetch('tags', - # EstablishmentTag.objects.select_related('tag')), - # models.Prefetch('establishment_type__tag_categories', - # EstablishmentTypeTagCategory.objects.select_related('tag_category')), - # models.Prefetch('establishment_type__establishmentsubtype_set', - # EstablishmentSubType.objects.prefetch_related('tag_categories')), - # ) + return self.select_related('address', 'establishment_type').\ + prefetch_related('tags') def with_extended_related(self): return self.select_related('establishment_type').\ @@ -587,81 +560,3 @@ class SocialNetwork(models.Model): def __str__(self): return self.title -# -# class EstablishmentTagQuerySet(models.QuerySet): -# """Establishment tag QuerySet.""" -# -# def by_country_code(self, code): -# """Return establishment tags by establishment country code.""" -# return self.filter(establishment__address__city__country__code=code) - -# -# class EstablishmentTag(models.Model): -# """Establishment tag model.""" -# tag = models.ForeignKey('tag.Tag', -# on_delete=models.CASCADE, -# related_name='tags', -# verbose_name=_('tag')) -# establishment = models.ForeignKey('establishment.Establishment', -# on_delete=models.CASCADE, -# related_name='tags', -# verbose_name=_('establishment')) -# objects = EstablishmentTagQuerySet.as_manager() -# -# class Meta: -# verbose_name = _('establishment tag') -# verbose_name_plural = _('establishment tags') -# -# -# class EstablishmentTypeTagCategoryQuerySet(models.QuerySet): -# """EstablishmentTypeTagCategory QuerySet.""" -# -# def by_country_code(self, code): -# """Return establishment tags by country code""" -# return self.filter(tag_category__country__code=code) -# -# def with_base_related(self): -# """Return with related relations.""" -# return self.select_related('establishment_type', 'tag_category') -# -# -# class EstablishmentTypeTagCategory(models.Model): -# """Tag categories based on establishment type.""" -# establishment_type = models.ForeignKey(EstablishmentType, -# on_delete=models.CASCADE, -# related_name='tag_categories', -# verbose_name=_('establishment type')) -# tag_category = models.ForeignKey('tag.TagCategory', -# on_delete=models.CASCADE, -# related_name='est_type_tag_categories', -# verbose_name=_('tag category')) -# objects = EstablishmentTypeTagCategoryQuerySet.as_manager() -# -# class Meta: -# verbose_name = _('establishment type tag categories') -# verbose_name_plural = _('establishment type tag categories') -# -# -# class EstablishmentSubTypeTagCategoryQuerySet(models.QuerySet): -# """QuerySet for tag categories based on establishment subtype.""" -# -# def with_base_related(self): -# """Return queryset with base related.""" -# return self.select_related('establishment_subtype', 'tag_category') -# -# -# class EstablishmentSubTypeTagCategory(models.Model): -# """Tag categories based on establishment subtype.""" -# establishment_subtype = models.ForeignKey(EstablishmentSubType, -# on_delete=models.CASCADE, -# related_name='tag_categories', -# verbose_name=_('establishment subtype')) -# tag_category = models.ForeignKey('tag.TagCategory', -# on_delete=models.CASCADE, -# related_name='est_subtype_tag_categories', -# verbose_name=_('tag category')) -# objects = EstablishmentSubTypeTagCategoryQuerySet.as_manager() -# -# class Meta: -# verbose_name = _('establishment subtype tag categories') -# verbose_name_plural = _('establishment subtype tag categories') diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index dca25763..59725710 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -1,16 +1,12 @@ -from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from establishment import models from establishment.serializers import ( EstablishmentBaseSerializer, PlateSerializer, ContactEmailsSerializer, ContactPhonesSerializer, SocialNetworkRelatedSerializers, - EstablishmentTypeBaseSerializer, EstablishmentSubTypeBaseSerializer) - # EstablishmentTypeTagCategoryBaseSerializer) + EstablishmentTypeBaseSerializer) from main.models import Currency -from tag.serializers import TagBaseSerializer from utils.decorators import with_base_attributes -from utils.serializers import TranslatedField class EstablishmentListCreateSerializer(EstablishmentBaseSerializer): @@ -144,96 +140,3 @@ class EmployeeBackSerializers(serializers.ModelSerializer): 'user', 'name' ] - - -# class EstablishmentTagCreateSerializer(serializers.ModelSerializer): -# """Serializer for model EstablishmentTag.""" -# class Meta: -# model = models.EstablishmentTag -# fields = [ -# 'tag', -# 'establishment' -# ] -# extra_kwargs = { -# 'tag': {'write_only': True}, -# 'establishment': {'write_only': True}, -# } -# -# def validate(self, attrs): -# """Validate method.""" -# establishment = attrs.get('establishment') -# tag = attrs.get('tag') -# -# # Check if tag is already added to establishment. -# if establishment.tags.filter(tag=tag).exists(): -# raise serializers.ValidationError(detail={'detail': _('Tag is already added.')}) -# -# # Сhecking tag availability for establishment type. -# if not establishment.establishment_type.use_subtypes: -# qs = establishment.establishment_type.tag_categories.filter(tag_category=tag.category) -# else: -# # Сhecking tag availability for establishment subtype. -# qs = establishment.establishment_type.tag_categories.filter( -# establishmentsubtype_set__tag_category=tag.category) -# if not qs.exists(): -# raise serializers.ValidationError( -# detail={'detail': _('Tag is not available for this establishment type|subtype.')}) -# return attrs - - -# class EstablishmentSubTypeTagCategoryBaseSerializer(serializers.ModelSerializer): -# """Serializer for intermediate model EstablishmentSubTypeTagCategories.""" -# id = serializers.IntegerField(source='tag_category.id', read_only=True) -# label_translated = TranslatedField(source='tag_category.label_translated') -# tags = TagBaseSerializer(source='tag_category.tags', many=True, read_only=True) -# -# class Meta: -# """Meta class.""" -# model = models.EstablishmentSubTypeTagCategory -# fields = [ -# 'id', -# 'label_translated', -# 'tags', -# 'establishment_subtype', -# 'tag_category', -# ] -# extra_kwargs = { -# 'establishment_subtype': {'write_only': True}, -# 'tag_category': {'write_only': True}, -# } -# -# def validate(self, attrs): -# """Override validate method.""" -# if models.EstablishmentTypeTagCategory.objects.filter( -# establishment_type=attrs.get('establishment_type'), -# tag_category=attrs.get('tag_category')).exists(): -# raise serializers.ValidationError(detail={'detail': _('Objects is already attached.')}) -# return attrs -# -# -# class EstablishmentSubTypeSerializer(EstablishmentSubTypeBaseSerializer): -# """Extended serializer for EstablishmentSubType model with tags.""" -# tag_categories = EstablishmentSubTypeTagCategoryBaseSerializer(many=True, read_only=True) -# -# class Meta(EstablishmentSubTypeBaseSerializer.Meta): -# """Meta class""" -# fields = [ -# 'id', -# 'name_translated', -# 'tag_categories' -# ] -# -# -# class EstablishmentTagsByType(EstablishmentTypeBaseSerializer): -# """Tags by establishment type""" -# tag_categories = EstablishmentTypeTagCategoryBaseSerializer(many=True) -# subtypes = EstablishmentSubTypeSerializer(many=True, source='establishmentsubtype_set') -# -# class Meta(EstablishmentTypeBaseSerializer.Meta): -# """Meta class.""" -# fields = [ -# 'id', -# 'name_translated', -# 'tag_categories', -# 'subtypes', -# ] diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 7c659c9b..389d0d1c 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -139,36 +139,6 @@ class EstablishmentSubTypeBaseSerializer(serializers.ModelSerializer): } -# class EstablishmentTypeTagCategoryBaseSerializer(serializers.ModelSerializer): -# """Serializer for intermediate model EstablishmentTypeTagCategories.""" -# id = serializers.IntegerField(source='tag_category.id', read_only=True) -# label_translated = TranslatedField(source='tag_category.label_translated') -# tags = TagBaseSerializer(source='tag_category.tags', many=True, read_only=True) -# -# class Meta: -# """Meta class.""" -# model = models.EstablishmentTypeTagCategory -# fields = [ -# 'id', -# 'label_translated', -# 'tags', -# 'establishment_type', -# 'tag_category', -# ] -# extra_kwargs = { -# 'establishment_type': {'write_only': True}, -# 'tag_category': {'write_only': True}, -# } -# -# def validate(self, attrs): -# """Override validate method.""" -# if models.EstablishmentTypeTagCategory.objects.filter( -# establishment_type=attrs.get('establishment_type'), -# tag_category=attrs.get('tag_category')).exists(): -# raise serializers.ValidationError(detail={'detail': _('Objects is already attached.')}) -# return attrs - - class EstablishmentEmployeeSerializer(serializers.ModelSerializer): """Serializer for actual employees.""" @@ -184,20 +154,6 @@ class EstablishmentEmployeeSerializer(serializers.ModelSerializer): model = models.Employee fields = ('id', 'name', 'position_translated', 'awards', 'priority') -# -# class EstablishmentTagSerializer(serializers.ModelSerializer): -# """Serializer for intermediate model EstablishmentTag.""" -# id = serializers.IntegerField(source='tag.id') -# label_translated = serializers.CharField(source='tag.label_translated') -# -# class Meta: -# """Meta class.""" -# model = models.EstablishmentTag -# fields = [ -# 'id', -# 'label_translated' -# ] - class EstablishmentBaseSerializer(ProjectModelSerializer): """Base serializer for Establishment model.""" diff --git a/apps/establishment/urls/back.py b/apps/establishment/urls/back.py index cdd4d183..6a12e792 100644 --- a/apps/establishment/urls/back.py +++ b/apps/establishment/urls/back.py @@ -14,7 +14,6 @@ urlpatterns = [ name='schedule-rud'), path('/schedule/', views.EstablishmentScheduleCreateView.as_view(), name='schedule-create'), - # path('attach-tag/', views.EstablishmentTagCreateView.as_view(), name='attach-tag'), path('menus/', views.MenuListCreateView.as_view(), name='menu-list'), path('menus//', views.MenuRUDView.as_view(), name='menu-rud'), path('plates/', views.PlateListCreateView.as_view(), name='plates'), @@ -28,12 +27,7 @@ urlpatterns = [ path('employees/', views.EmployeeListCreateView.as_view(), name='employees'), path('employees//', views.EmployeeRUDView.as_view(), name='employees-rud'), path('types/', views.EstablishmentTypeListCreateView.as_view(), name='type-list'), - # path('types/tags/', views.EstablishmentTypeTagListView.as_view(), name='type-tag-list'), path('types//', views.EstablishmentTypeRUDView.as_view(), name='type-rud'), - # path('types/attach-tag-category/', views.EstablishmentTypeAttachTagCategoryView.as_view(), - # name='type-attach-tag-category'), path('subtypes/', views.EstablishmentSubtypeListCreateView.as_view(), name='subtype-list'), path('subtypes//', views.EstablishmentSubtypeRUDView.as_view(), name='subtype-rud'), - # path('subtypes/attach-tag-category/', views.EstablishmentSubTypeAttachTagCategoryView.as_view(), - # name='subtype-attach-tag-category'), ] diff --git a/apps/establishment/urls/common.py b/apps/establishment/urls/common.py index 65b796ae..5d7df146 100644 --- a/apps/establishment/urls/common.py +++ b/apps/establishment/urls/common.py @@ -7,7 +7,6 @@ app_name = 'establishment' urlpatterns = [ path('', views.EstablishmentListView.as_view(), name='list'), - # path('types/tags/', views.EstablishmentTypeTagListView.as_view(), name='type-tag-list'), path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(), name='recent-reviews'), path('slug//', views.EstablishmentRetrieveView.as_view(), name='detail'), diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index f79707d9..d87baf6d 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -1,11 +1,9 @@ """Establishment app views.""" from django.shortcuts import get_object_or_404 -from rest_framework import generics, status, permissions +from rest_framework import generics from establishment import models, serializers -from rest_framework.response import Response from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer -from establishment.filters import EstablishmentTypeTagFilter class EstablishmentMixinViews: @@ -55,25 +53,6 @@ class EstablishmentScheduleCreateView(generics.CreateAPIView): serializer_class = ScheduleCreateSerializer -# class EstablishmentTypeAttachTagCategoryView(generics.CreateAPIView): -# """Attach tag category to establishment type.""" -# serializer_class = serializers.EstablishmentTypeTagCategoryBaseSerializer -# -# def get_queryset(self): -# """Override get_queryset method.""" -# return models.EstablishmentTypeTagCategory.objects.with_base_related() -# -# def post(self, request, *args, **kwargs): -# """Overridden post-method.""" -# super(EstablishmentTypeAttachTagCategoryView, self).post(request) -# return Response(status=status.HTTP_200_OK) - - -# class EstablishmentTagCreateView(EstablishmentMixinViews, generics.CreateAPIView): -# """Attach tag to establishment.""" -# serializer_class = serializers.EstablishmentTagCreateSerializer -# - class MenuListCreateView(generics.ListCreateAPIView): """Menu list create view.""" serializer_class = serializers.MenuSerializers @@ -164,15 +143,6 @@ class EstablishmentTypeRUDView(generics.RetrieveUpdateDestroyAPIView): queryset = models.EstablishmentType.objects.all() -# class EstablishmentTypeTagListView(generics.ListAPIView): -# """List of tags with categories by establishment type.""" -# serializer_class = serializers.EstablishmentTagsByType -# queryset = models.EstablishmentType.objects.with_base_related() -# filter_class = EstablishmentTypeTagFilter -# permission_classes = (permissions.AllowAny, ) -# pagination_class = None - - class EstablishmentSubtypeListCreateView(generics.ListCreateAPIView): """Establishment subtype list/create view.""" serializer_class = serializers.EstablishmentSubTypeBaseSerializer @@ -184,17 +154,3 @@ class EstablishmentSubtypeRUDView(generics.RetrieveUpdateDestroyAPIView): """Establishment subtype retrieve/update/destroy view.""" serializer_class = serializers.EstablishmentSubTypeBaseSerializer queryset = models.EstablishmentSubType.objects.all() - - -# class EstablishmentSubTypeAttachTagCategoryView(generics.CreateAPIView): -# """Attach tag category to establishment subtype.""" -# serializer_class = serializers.EstablishmentSubTypeTagCategoryBaseSerializer -# -# def get_queryset(self): -# """Override get_queryset method.""" -# return models.EstablishmentSubTypeTagCategory.objects.with_base_related() -# -# def post(self, request, *args, **kwargs): -# """Overridden post-method.""" -# super(EstablishmentSubTypeAttachTagCategoryView, self).post(request) -# return Response(status=status.HTTP_200_OK) diff --git a/apps/news/admin.py b/apps/news/admin.py index 77ea8388..5d7f79f0 100644 --- a/apps/news/admin.py +++ b/apps/news/admin.py @@ -1,8 +1,8 @@ from django.contrib import admin - from news import models from .tasks import send_email_with_news + @admin.register(models.NewsType) class NewsTypeAdmin(admin.ModelAdmin): """News type admin.""" diff --git a/apps/news/urls/web.py b/apps/news/urls/web.py index 0671f5f6..80fcf072 100644 --- a/apps/news/urls/web.py +++ b/apps/news/urls/web.py @@ -7,7 +7,5 @@ app_name = 'news' urlpatterns = [ path('', views.NewsListView.as_view(), name='list'), path('types/', views.NewsTypeListView.as_view(), name='type'), - path('types//tags/', views.NewsTypeTagsView.as_view(), - name='type-tags'), path('slug//', views.NewsDetailView.as_view(), name='rud'), ] diff --git a/apps/news/views.py b/apps/news/views.py index 1a845a7b..b167324e 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -3,7 +3,6 @@ from django.shortcuts import get_object_or_404 from rest_framework import generics, permissions from news import filters, models, serializers from rating.tasks import add_rating -from tag.serializers import TagCategoryDetailSerializer class NewsMixinView: @@ -47,18 +46,6 @@ class NewsTypeListView(generics.ListAPIView): serializer_class = serializers.NewsTypeSerializer -class NewsTypeTagsView(generics.ListAPIView): - """Resource to get a list of tags for a news type.""" - - pagination_class = None - permission_classes = (permissions.AllowAny, ) - serializer_class = TagCategoryDetailSerializer - - def get_queryset(self): - news_type = get_object_or_404(models.NewsType, pk=self.kwargs.get('pk')) - return news_type.tag_categories.with_related() - - class NewsBackOfficeMixinView: """News back office mixin view.""" diff --git a/apps/tag/models.py b/apps/tag/models.py index 3deed7d9..4fa04f8e 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -49,9 +49,6 @@ class TagCategoryQuerySet(models.QuerySet): """Filter by existing tags.""" return self.filter(tags__isnull=not switcher) - def with_related(self): - return self.select_related('country').prefetch_related('tags') - class TagCategory(TranslatedFieldsMixin, models.Model): """Tag base category model.""" diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py index 946206f4..e4ecf25d 100644 --- a/apps/tag/serializers.py +++ b/apps/tag/serializers.py @@ -48,23 +48,14 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer): ) -class TagCategoryDetailSerializer(TagCategoryBaseSerializer): - """Detailed serializer for TagCategory model.""" - - class Meta(TagCategoryBaseSerializer.Meta): - """Meta class.""" - - fields = TagCategoryBaseSerializer.Meta.fields + ('tags', ) - - -class TagCategoryBackOfficeDetailSerializer(TagCategoryDetailSerializer): +class TagCategoryBackOfficeDetailSerializer(TagCategoryBaseSerializer): country_translated = TranslatedField(source='country.name_translated') - class Meta(TagCategoryDetailSerializer.Meta): + class Meta(TagBaseSerializer.Meta): """Meta class.""" - fields = TagCategoryDetailSerializer.Meta.fields + ( + fields = TagCategoryBaseSerializer.Meta.fields + ( 'news_types', 'country', 'country_translated', From bde81b3700b16f6950b1d490e446ce040aab3110 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Tue, 15 Oct 2019 18:32:16 +0300 Subject: [PATCH 053/223] update ES documents --- apps/search_indexes/documents/establishment.py | 16 +++++++++------- apps/search_indexes/documents/news.py | 18 +++++++++--------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index 5d45c45b..6a00f0f1 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -39,13 +39,15 @@ class EstablishmentDocument(Document): }), }, multi=True) - # tags = fields.ObjectField( - # properties={ - # 'tag': fields.ObjectField(properties={ - # 'id': fields.IntegerField(), - # }), - # }, - # multi=True) + tags = fields.ObjectField( + properties={ + 'id': fields.IntegerField(attr='id'), + 'label': fields.ObjectField(attr='label_indexing', + properties=OBJECT_FIELD_PROPERTIES), + 'category': fields.ObjectField(attr='category', + properties={'id': fields.IntegerField()}) + }, + multi=True) address = fields.ObjectField( properties={ 'id': fields.IntegerField(), diff --git a/apps/search_indexes/documents/news.py b/apps/search_indexes/documents/news.py index 62049c05..25eec31d 100644 --- a/apps/search_indexes/documents/news.py +++ b/apps/search_indexes/documents/news.py @@ -24,15 +24,15 @@ class NewsDocument(Document): country = fields.ObjectField(properties={'id': fields.IntegerField(), 'code': fields.KeywordField()}) web_url = fields.KeywordField(attr='web_url') - # tags = fields.ObjectField( - # properties={ - # 'id': fields.IntegerField(attr='metadata.id'), - # 'label': fields.ObjectField(attr='metadata.label_indexing', - # properties=OBJECT_FIELD_PROPERTIES), - # 'category': fields.ObjectField(attr='metadata.category', - # properties={'id': fields.IntegerField()}) - # }, - # multi=True) + tags = fields.ObjectField( + properties={ + 'id': fields.IntegerField(attr='id'), + 'label': fields.ObjectField(attr='label_indexing', + properties=OBJECT_FIELD_PROPERTIES), + 'category': fields.ObjectField(attr='category', + properties={'id': fields.IntegerField()}) + }, + multi=True) class Django: From 3a54b70093df293e9b893a0de8efda80f79557fa Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Tue, 15 Oct 2019 18:43:08 +0300 Subject: [PATCH 054/223] update tag on document index --- apps/search_indexes/documents/establishment.py | 2 -- apps/search_indexes/documents/news.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index 6a00f0f1..ca1da993 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -44,8 +44,6 @@ class EstablishmentDocument(Document): 'id': fields.IntegerField(attr='id'), 'label': fields.ObjectField(attr='label_indexing', properties=OBJECT_FIELD_PROPERTIES), - 'category': fields.ObjectField(attr='category', - properties={'id': fields.IntegerField()}) }, multi=True) address = fields.ObjectField( diff --git a/apps/search_indexes/documents/news.py b/apps/search_indexes/documents/news.py index 25eec31d..99071e53 100644 --- a/apps/search_indexes/documents/news.py +++ b/apps/search_indexes/documents/news.py @@ -29,8 +29,6 @@ class NewsDocument(Document): 'id': fields.IntegerField(attr='id'), 'label': fields.ObjectField(attr='label_indexing', properties=OBJECT_FIELD_PROPERTIES), - 'category': fields.ObjectField(attr='category', - properties={'id': fields.IntegerField()}) }, multi=True) From ccf6e12e3f949658d32342682170e70d578a65a0 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Tue, 15 Oct 2019 18:52:32 +0300 Subject: [PATCH 055/223] remove tag category from establishment type --- apps/search_indexes/documents/establishment.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index ca1da993..c30d4c58 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -22,13 +22,6 @@ class EstablishmentDocument(Document): 'id': fields.IntegerField(), 'name': fields.ObjectField(attr='name_indexing', properties=OBJECT_FIELD_PROPERTIES), - 'tag_categories': fields.ObjectField(properties={ - 'tag_category': fields.ObjectField( - properties={ - 'id': fields.IntegerField() - } - ) - }), }) establishment_subtypes = fields.ObjectField( properties={ From e0bce99ca952c542a8bf314f71cd4b1dac3332ca Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 15 Oct 2019 21:40:55 +0300 Subject: [PATCH 056/223] 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 86692019d1983770e1add6a634511acca33fe970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 16 Oct 2019 10:14:23 +0300 Subject: [PATCH 057/223] Add Country admin to views --- apps/account/models.py | 2 ++ apps/account/permissions.py | 0 apps/comment/views/back.py | 4 +-- apps/establishment/models.py | 7 +++++ apps/establishment/views/back.py | 3 ++ apps/establishment/views/web.py | 2 +- apps/location/models.py | 4 +++ apps/location/views/back.py | 14 ++++++---- apps/news/tests.py | 32 +++++++++++++++++++++- apps/news/views.py | 2 ++ apps/utils/permissions.py | 47 ++++++++++++++++++++++++-------- 11 files changed, 97 insertions(+), 20 deletions(-) delete mode 100644 apps/account/permissions.py diff --git a/apps/account/models.py b/apps/account/models.py index 63486f96..96927c1e 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -24,11 +24,13 @@ class Role(ProjectBaseMixin): STANDARD_USER = 1 COMMENTS_MODERATOR = 2 COUNTRY_ADMIN = 3 + CONTENT_PAGE_MANAGER = 4 ROLE_CHOICES = ( (STANDARD_USER, 'Standard user'), (COMMENTS_MODERATOR, 'Comments moderator'), (COUNTRY_ADMIN, 'Country admin'), + (CONTENT_PAGE_MANAGER, 'Content page manager') ) role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES, null=False, blank=False) diff --git a/apps/account/permissions.py b/apps/account/permissions.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/comment/views/back.py b/apps/comment/views/back.py index 7f066f30..2895fdbe 100644 --- a/apps/comment/views/back.py +++ b/apps/comment/views/back.py @@ -1,7 +1,7 @@ from rest_framework import generics, permissions from comment.serializers import back as serializers from comment import models -from utils.permissions import IsCommentModerator +from utils.permissions import IsCommentModerator, IsCountryAdmin class CommentLstView(generics.ListCreateAPIView): @@ -15,5 +15,5 @@ class CommentRUDView(generics.RetrieveUpdateDestroyAPIView): """Comment RUD view.""" serializer_class = serializers.CommentBaseSerializer queryset = models.Comment.objects.all() - permission_classes = [IsCommentModerator] + permission_classes = [IsCountryAdmin|IsCommentModerator] lookup_field = 'id' diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 2fc63bd5..c97562cc 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -382,6 +382,13 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): return Award.objects.filter(Q(establishment=self) | Q(employees__establishments=self)).latest( field_name='vintage_year') + @property + def country_id(self): + """ + Return Country object of establishment location + """ + return self.address.city.country.id + class Position(BaseAttributes, TranslatedFieldsMixin): """Position model.""" diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index 5cba8255..fe9633ab 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -4,6 +4,7 @@ from rest_framework import generics from establishment import models from establishment import serializers +from utils.permissions import IsCountryAdmin class EstablishmentMixinViews: @@ -18,11 +19,13 @@ class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAP """Establishment list/create view.""" queryset = models.Establishment.objects.all() serializer_class = serializers.EstablishmentListCreateSerializer + permission_classes = [IsCountryAdmin] class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView): queryset = models.Establishment.objects.all() serializer_class = serializers.EstablishmentRUDSerializer + permission_classes = [IsCountryAdmin] class MenuListCreateView(generics.ListCreateAPIView): diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index 8f5d2a26..36f2a027 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -11,7 +11,7 @@ from main import methods from main.models import MetaDataContent from timetable.serialziers import ScheduleRUDSerializer, ScheduleCreateSerializer from utils.pagination import EstablishmentPortionPagination - +from utils.permissions import IsCountryAdmin class EstablishmentMixinView: """Establishment mixin.""" diff --git a/apps/location/models.py b/apps/location/models.py index 2298c28e..7f797811 100644 --- a/apps/location/models.py +++ b/apps/location/models.py @@ -112,6 +112,10 @@ class Address(models.Model): return {'lat': self.latitude, 'lon': self.longitude} + @property + def country_id(self): + return self.city.country_id + # todo: Make recalculate price levels @receiver(post_save, sender=Country) diff --git a/apps/location/views/back.py b/apps/location/views/back.py index ce6589ed..5c028545 100644 --- a/apps/location/views/back.py +++ b/apps/location/views/back.py @@ -3,50 +3,54 @@ from rest_framework import generics from location import models, serializers from location.views import common - +from utils.permissions import IsCountryAdmin # Address class AddressListCreateView(common.AddressViewMixin, generics.ListCreateAPIView): """Create view for model Address.""" serializer_class = serializers.AddressDetailSerializer queryset = models.Address.objects.all() + permission_classes = [IsCountryAdmin] class AddressRUDView(common.AddressViewMixin, generics.RetrieveUpdateDestroyAPIView): """RUD view for model Address.""" serializer_class = serializers.AddressDetailSerializer queryset = models.Address.objects.all() + permission_classes = [IsCountryAdmin] # City class CityListCreateView(common.CityViewMixin, generics.ListCreateAPIView): """Create view for model City.""" serializer_class = serializers.CitySerializer - + permission_classes = [IsCountryAdmin] class CityRUDView(common.CityViewMixin, generics.RetrieveUpdateDestroyAPIView): """RUD view for model City.""" serializer_class = serializers.CitySerializer + permission_classes = [IsCountryAdmin] # Region class RegionListCreateView(common.RegionViewMixin, generics.ListCreateAPIView): """Create view for model Region""" serializer_class = serializers.RegionSerializer - + permission_classes = [IsCountryAdmin] class RegionRUDView(common.RegionViewMixin, generics.RetrieveUpdateDestroyAPIView): """Retrieve view for model Region""" serializer_class = serializers.RegionSerializer - + permission_classes = [IsCountryAdmin] # Country class CountryListCreateView(common.CountryViewMixin, generics.ListCreateAPIView): """List/Create view for model Country.""" serializer_class = serializers.CountryBackSerializer pagination_class = None - + permission_classes = [IsCountryAdmin] class CountryRUDView(common.CountryViewMixin, generics.RetrieveUpdateDestroyAPIView): """RUD view for model Country.""" serializer_class = serializers.CountryBackSerializer + permission_classes = [IsCountryAdmin] \ No newline at end of file diff --git a/apps/news/tests.py b/apps/news/tests.py index 2e24ac45..27ede62c 100644 --- a/apps/news/tests.py +++ b/apps/news/tests.py @@ -1,3 +1,4 @@ +from django.urls import reverse from http.cookies import SimpleCookie from rest_framework.test import APITestCase @@ -6,7 +7,8 @@ from datetime import datetime, timedelta from news.models import NewsType, News from account.models import User - +from translation.models import Language +from location.models import Country # Create your tests here. @@ -27,7 +29,20 @@ class BaseTestCase(APITestCase): playlist=1, start=datetime.now() + timedelta(hours=-2), end=datetime.now() + timedelta(hours=2), state=News.PUBLISHED, slug='test-news-slug',) + self.lang = Language.objects.create( + title='Russia', + locale='ru-RU' + ) + self.lang.save() + self.country_ru = Country.objects.create( + name='{"ru-RU":"Russia"}', + code='23', + low_price=15, + high_price=150000, + ) + self.country_ru.languages.add(self.lang) + self.country_ru.save() class NewsTestCase(BaseTestCase): @@ -50,3 +65,18 @@ class NewsTestCase(BaseTestCase): def test_news_type_list(self): response = self.client.get("/api/web/news/types/") self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_news_back_detail_put(self): + # retrieve-update-destroy + url = reverse('back:news:retrieve-update-destroy', kwargs={'pk': self.test_news.id}) + data = { + 'id': self.test_news.id, + 'description': {"en-GB": "Description test news!"}, + 'slug': self.test_news.slug, + 'start': self.test_news.start, + 'playlist': self.test_news.playlist, + 'news_type_id':self.test_news.news_type_id, + 'country_id': self.country_ru.id + } + response = self.client.put(url, data=data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) \ No newline at end of file diff --git a/apps/news/views.py b/apps/news/views.py index 61a57251..b5273a5e 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -2,6 +2,7 @@ from rest_framework import generics, permissions from news import filters, models, serializers from rating.tasks import add_rating +from utils.permissions import IsCountryAdmin class NewsMixinView: """News mixin.""" @@ -57,6 +58,7 @@ class NewsBackOfficeLCView(NewsBackOfficeMixinView, serializer_class = serializers.NewsBackOfficeBaseSerializer create_serializers_class = serializers.NewsBackOfficeDetailSerializer + permission_classes = [IsCountryAdmin] def get_serializer_class(self): """Override serializer class.""" diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 2754a3c5..e2a2b80a 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -67,9 +67,34 @@ class IsStandardUser(IsGuest): """ def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request - if super().has_object_permission(request, view, obj) or\ - (obj.user == request.user and obj.user.email_confirmed): + if obj.user == request.user and obj.user.email_confirmed: return True + + if super().has_object_permission(request, view, obj): + return True + + return False + + +class IsContentPageManager(IsStandardUser): + """ + Object-level permission to only allow owners of an object to edit it. + Assumes the model instance has an `owner` attribute. + """ + + def has_object_permission(self, request, view, obj): + # Read permissions are allowed to any request. + role = Role.objects.filter(role=Role.CONTENT_PAGE_MANAGER, + country_id=obj.country_id)\ + .first() # 'Comments moderator' + + is_access = UserRole.objects.filter(user=request.user, role=role).exists() + if obj.user != request.user and is_access: + return True + + if super().has_object_permission(request, view, obj): + return True + return False @@ -80,17 +105,18 @@ class IsCountryAdmin(IsStandardUser): """ def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request. - if super().has_object_permission(request, view, obj): - return True - - # Must have role role = Role.objects.filter(role=Role.COUNTRY_ADMIN, country_id=obj.country_id) \ .first() # 'Comments moderator' is_access = UserRole.objects.filter(user=request.user, role=role).exists() + if obj.user != request.user and is_access: return True + + if super().has_object_permission(request, view, obj): + return True + return False @@ -102,17 +128,16 @@ class IsCommentModerator(IsCountryAdmin): def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request. - - if super().has_object_permission(request, view, obj): - return True - - # Must have role role = Role.objects.filter(role=Role.COMMENTS_MODERATOR, country_id=obj.country_id)\ .first() # 'Comments moderator' is_access = UserRole.objects.filter(user=request.user, role=role).exists() + if obj.user != request.user and is_access: return True + if super().has_object_permission(request, view, obj): + return True + return False From 04a6bbea191d522d30c4714f1bfc776030646b76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 16 Oct 2019 10:43:06 +0300 Subject: [PATCH 058/223] Add content page manager --- apps/news/views.py | 5 +++-- apps/utils/permissions.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/news/views.py b/apps/news/views.py index b5273a5e..105b9064 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -2,7 +2,7 @@ from rest_framework import generics, permissions from news import filters, models, serializers from rating.tasks import add_rating -from utils.permissions import IsCountryAdmin +from utils.permissions import IsCountryAdmin, IsContentPageManager class NewsMixinView: """News mixin.""" @@ -58,7 +58,7 @@ class NewsBackOfficeLCView(NewsBackOfficeMixinView, serializer_class = serializers.NewsBackOfficeBaseSerializer create_serializers_class = serializers.NewsBackOfficeDetailSerializer - permission_classes = [IsCountryAdmin] + permission_classes = [IsCountryAdmin|IsContentPageManager] def get_serializer_class(self): """Override serializer class.""" @@ -76,6 +76,7 @@ class NewsBackOfficeRUDView(NewsBackOfficeMixinView, """Resource for detailed information about news for back-office users.""" serializer_class = serializers.NewsBackOfficeDetailSerializer + permission_classes = [IsCountryAdmin|IsContentPageManager] def get(self, request, pk, *args, **kwargs): add_rating(remote_addr=request.META.get('REMOTE_ADDR'), diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index e2a2b80a..f1c2f46d 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -120,7 +120,7 @@ class IsCountryAdmin(IsStandardUser): return False -class IsCommentModerator(IsCountryAdmin): +class IsCommentModerator(IsStandardUser): """ Object-level permission to only allow owners of an object to edit it. Assumes the model instance has an `owner` attribute. From 3271a6fed99ab72f1cd4def10754419a28c55225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 16 Oct 2019 11:02:26 +0300 Subject: [PATCH 059/223] Establishment manager --- apps/products/urls/back.py | 0 apps/products/views/back.py | 0 apps/utils/permissions.py | 5 ++++- 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 apps/products/urls/back.py create mode 100644 apps/products/views/back.py diff --git a/apps/products/urls/back.py b/apps/products/urls/back.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/products/views/back.py b/apps/products/views/back.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index f1c2f46d..ff10e117 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -75,7 +75,6 @@ class IsStandardUser(IsGuest): return False - class IsContentPageManager(IsStandardUser): """ Object-level permission to only allow owners of an object to edit it. @@ -141,3 +140,7 @@ class IsCommentModerator(IsStandardUser): return True return False + + +class IsEstablishmentManager(IsStandardUser): + pass From 9fd89bf812a508cdb1e7819fc67db0d48b6bd762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 16 Oct 2019 11:40:51 +0300 Subject: [PATCH 060/223] Add establishment manager role --- apps/account/admin.py | 2 +- .../migrations/0013_auto_20191016_0810.py | 30 +++++++++++++++++++ apps/account/models.py | 7 ++++- apps/utils/permissions.py | 15 +++++++++- 4 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 apps/account/migrations/0013_auto_20191016_0810.py diff --git a/apps/account/admin.py b/apps/account/admin.py index 3b247289..651e5a5a 100644 --- a/apps/account/admin.py +++ b/apps/account/admin.py @@ -12,7 +12,7 @@ class RoleAdmin(admin.ModelAdmin): @admin.register(models.UserRole) class UserRoleAdmin(admin.ModelAdmin): - list_display = ['user', 'role'] + list_display = ['user', 'role', 'establishment'] @admin.register(models.User) diff --git a/apps/account/migrations/0013_auto_20191016_0810.py b/apps/account/migrations/0013_auto_20191016_0810.py new file mode 100644 index 00000000..72955cee --- /dev/null +++ b/apps/account/migrations/0013_auto_20191016_0810.py @@ -0,0 +1,30 @@ +# Generated by Django 2.2.4 on 2019-10-16 08:10 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0033_auto_20191003_0943_squashed_0034_auto_20191003_1036'), + ('account', '0012_merge_20191015_0708'), + ] + + operations = [ + migrations.AddField( + model_name='userrole', + name='establishment', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='establishment.Establishment', verbose_name='Establishment'), + ), + migrations.AlterField( + model_name='role', + name='country', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='location.Country', verbose_name='Country'), + ), + 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')], verbose_name='Role'), + ), + ] diff --git a/apps/account/models.py b/apps/account/models.py index 96927c1e..17bbd66a 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -13,6 +13,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework.authtoken.models import Token from authorization.models import Application +from establishment.models import Establishment from location.models import Country from utils.models import GMTokenGenerator from utils.models import ImageMixin, ProjectBaseMixin, PlatformMixin @@ -25,12 +26,14 @@ class Role(ProjectBaseMixin): COMMENTS_MODERATOR = 2 COUNTRY_ADMIN = 3 CONTENT_PAGE_MANAGER = 4 + ESTABLISHMENT_MANAGER = 5 ROLE_CHOICES = ( (STANDARD_USER, 'Standard user'), (COMMENTS_MODERATOR, 'Comments moderator'), (COUNTRY_ADMIN, 'Country admin'), - (CONTENT_PAGE_MANAGER, 'Content page manager') + (CONTENT_PAGE_MANAGER, 'Content page manager'), + (ESTABLISHMENT_MANAGER, 'Establishment manager') ) role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES, null=False, blank=False) @@ -230,3 +233,5 @@ class UserRole(ProjectBaseMixin): """UserRole model.""" user = models.ForeignKey(User, verbose_name=_('User'), on_delete=models.CASCADE) role = models.ForeignKey(Role, verbose_name=_('Role'), on_delete=models.SET_NULL, null=True) + establishment = models.ForeignKey(Establishment, verbose_name=_('Establishment'), + on_delete=models.SET_NULL, null=True, blank=True) \ No newline at end of file diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index ff10e117..9c703f50 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -143,4 +143,17 @@ class IsCommentModerator(IsStandardUser): class IsEstablishmentManager(IsStandardUser): - pass + + def has_object_permission(self, request, view, obj): + role = Role.objects.filter(role=Role.ESTABLISHMENT_MANAGER)\ + .first() # 'Comments moderator' + + is_access = UserRole.objects.filter(user=request.user, role=role, + establishment_id=obj.establishment_id).exists() + if is_access: + return True + + if super().has_object_permission(request, view, obj): + return True + + return False From 5ad3776fe30ee761842176c8ec43da6052135bf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 16 Oct 2019 12:14:13 +0300 Subject: [PATCH 061/223] establishment manager --- apps/establishment/models.py | 9 ++++++++- apps/establishment/views/back.py | 16 +++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index c97562cc..7938ae5a 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -385,10 +385,17 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): @property def country_id(self): """ - Return Country object of establishment location + Return Country id of establishment location """ return self.address.city.country.id + @property + def establishment_id(self): + """ + Return establishment id of establishment location + """ + return self.id + class Position(BaseAttributes, TranslatedFieldsMixin): """Position model.""" diff --git a/apps/establishment/views/back.py b/apps/establishment/views/back.py index fe9633ab..591fcc36 100644 --- a/apps/establishment/views/back.py +++ b/apps/establishment/views/back.py @@ -4,7 +4,7 @@ from rest_framework import generics from establishment import models from establishment import serializers -from utils.permissions import IsCountryAdmin +from utils.permissions import IsCountryAdmin, IsEstablishmentManager class EstablishmentMixinViews: @@ -19,25 +19,27 @@ class EstablishmentListCreateView(EstablishmentMixinViews, generics.ListCreateAP """Establishment list/create view.""" queryset = models.Establishment.objects.all() serializer_class = serializers.EstablishmentListCreateSerializer - permission_classes = [IsCountryAdmin] + permission_classes = [IsCountryAdmin|IsEstablishmentManager] class EstablishmentRUDView(generics.RetrieveUpdateDestroyAPIView): queryset = models.Establishment.objects.all() serializer_class = serializers.EstablishmentRUDSerializer - permission_classes = [IsCountryAdmin] + permission_classes = [IsCountryAdmin|IsEstablishmentManager] class MenuListCreateView(generics.ListCreateAPIView): """Menu list create view.""" serializer_class = serializers.MenuSerializers queryset = models.Menu.objects.all() + permission_classes = [IsEstablishmentManager] class MenuRUDView(generics.RetrieveUpdateDestroyAPIView): """Menu RUD view.""" serializer_class = serializers.MenuRUDSerializers queryset = models.Menu.objects.all() + permission_classes = [IsEstablishmentManager] class SocialListCreateView(generics.ListCreateAPIView): @@ -45,12 +47,14 @@ class SocialListCreateView(generics.ListCreateAPIView): serializer_class = serializers.SocialNetworkSerializers queryset = models.SocialNetwork.objects.all() pagination_class = None + permission_classes = [IsEstablishmentManager] class SocialRUDView(generics.RetrieveUpdateDestroyAPIView): """Social RUD view.""" serializer_class = serializers.SocialNetworkSerializers queryset = models.SocialNetwork.objects.all() + permission_classes = [IsEstablishmentManager] class PlateListCreateView(generics.ListCreateAPIView): @@ -58,12 +62,14 @@ class PlateListCreateView(generics.ListCreateAPIView): serializer_class = serializers.PlatesSerializers queryset = models.Plate.objects.all() pagination_class = None + permission_classes = [IsEstablishmentManager] class PlateRUDView(generics.RetrieveUpdateDestroyAPIView): """Social RUD view.""" serializer_class = serializers.PlatesSerializers queryset = models.Plate.objects.all() + permission_classes = [IsEstablishmentManager] class PhonesListCreateView(generics.ListCreateAPIView): @@ -71,12 +77,14 @@ class PhonesListCreateView(generics.ListCreateAPIView): serializer_class = serializers.ContactPhoneBackSerializers queryset = models.ContactPhone.objects.all() pagination_class = None + permission_classes = [IsEstablishmentManager] class PhonesRUDView(generics.RetrieveUpdateDestroyAPIView): """Social RUD view.""" serializer_class = serializers.ContactPhoneBackSerializers queryset = models.ContactPhone.objects.all() + permission_classes = [IsEstablishmentManager] class EmailListCreateView(generics.ListCreateAPIView): @@ -84,12 +92,14 @@ class EmailListCreateView(generics.ListCreateAPIView): serializer_class = serializers.ContactEmailBackSerializers queryset = models.ContactEmail.objects.all() pagination_class = None + permission_classes = [IsEstablishmentManager] class EmailRUDView(generics.RetrieveUpdateDestroyAPIView): """Social RUD view.""" serializer_class = serializers.ContactEmailBackSerializers queryset = models.ContactEmail.objects.all() + permission_classes = [IsEstablishmentManager] class EmployeeListCreateView(generics.ListCreateAPIView): From 8dad3df4bf4a64332e262338689496517ae3e17e Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 14:11:07 +0300 Subject: [PATCH 062/223] 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 063/223] 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 064/223] 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 065/223] 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 766fdcdad59402bd944a2fbeaf5d523cb6989345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 16 Oct 2019 15:18:31 +0300 Subject: [PATCH 066/223] Add reviewer manager --- apps/account/models.py | 4 +++- apps/utils/permissions.py | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/account/models.py b/apps/account/models.py index 17bbd66a..559d3ef8 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -27,13 +27,15 @@ class Role(ProjectBaseMixin): COUNTRY_ADMIN = 3 CONTENT_PAGE_MANAGER = 4 ESTABLISHMENT_MANAGER = 5 + REVIEWER_MANGER = 6 ROLE_CHOICES = ( (STANDARD_USER, 'Standard user'), (COMMENTS_MODERATOR, 'Comments moderator'), (COUNTRY_ADMIN, 'Country admin'), (CONTENT_PAGE_MANAGER, 'Content page manager'), - (ESTABLISHMENT_MANAGER, 'Establishment manager') + (ESTABLISHMENT_MANAGER, 'Establishment manager'), + (REVIEWER_MANGER, 'Reviewer manager') ) role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES, null=False, blank=False) diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 9c703f50..0450e4d3 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -157,3 +157,15 @@ class IsEstablishmentManager(IsStandardUser): return True return False + + +class IsReviewerManager(IsStandardUser): + def has_object_permission(self, request, view, obj): + access_models=[""] + + role = Role.objects.filter(role=Role.REVIEWER_MANGER)\ + .first() # 'Comments moderator' + + is_access = UserRole.objects.filter(user=request.user, role=role) + return False + From ed03f0f40f253ca6814866184b865d23a73c6e88 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 19:05:16 +0300 Subject: [PATCH 067/223] 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 068/223] 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 069/223] 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 070/223] 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 071/223] 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 072/223] 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 073/223] 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 074/223] 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 075/223] 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 19b3502f582601adbed20e1a1272c2197a4a9fa3 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Thu, 17 Oct 2019 14:34:31 +0300 Subject: [PATCH 076/223] revert auth backends --- 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 1de0c109..4618887d 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -222,8 +222,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 c59811e0379462ae2406f9a780c0227579f093a7 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 17 Oct 2019 14:34:49 +0300 Subject: [PATCH 077/223] 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 078/223] 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 079/223] 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 080/223] 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 c80b53a0fca1dffbc8b369a39cfffc43600a333e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Thu, 17 Oct 2019 15:04:08 +0300 Subject: [PATCH 081/223] reviewer manager --- apps/comment/tests.py | 2 +- apps/utils/permissions.py | 98 +++++++++++++++++++-------------------- 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/apps/comment/tests.py b/apps/comment/tests.py index 8cbcee88..9b060f4e 100644 --- a/apps/comment/tests.py +++ b/apps/comment/tests.py @@ -59,7 +59,7 @@ class CommentModeratorPermissionTests(BasePermissionTests): def test_get(self): response = self.client.get(self.url, format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) def test_put_other_user(self): other_user = User.objects.create_user(username='test', diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 0450e4d3..4cfabee8 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -53,11 +53,16 @@ class IsGuest(permissions.IsAuthenticatedOrReadOnly): """ Object-level permission to only allow owners of an object to edit it. """ - def has_object_permission(self, request, view, obj): - if request.method in permissions.SAFE_METHODS or request.user.is_superuser: - return True + def has_permission(self, request, view): + return request.user.is_authenticated - return False + def has_object_permission(self, request, view, obj): + + rules = [ + request.user.is_superuser, + request.method in permissions.SAFE_METHODS + ] + return any(rules) class IsStandardUser(IsGuest): @@ -67,34 +72,32 @@ class IsStandardUser(IsGuest): """ def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request - if obj.user == request.user and obj.user.email_confirmed: - return True + rules = [ + obj.user == request.user and obj.user.email_confirmed, + super().has_object_permission(request, view, obj) + ] - if super().has_object_permission(request, view, obj): - return True + return any(rules) - return False class IsContentPageManager(IsStandardUser): """ Object-level permission to only allow owners of an object to edit it. Assumes the model instance has an `owner` attribute. """ - def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request. + role = Role.objects.filter(role=Role.CONTENT_PAGE_MANAGER, country_id=obj.country_id)\ .first() # 'Comments moderator' - is_access = UserRole.objects.filter(user=request.user, role=role).exists() - if obj.user != request.user and is_access: - return True - - if super().has_object_permission(request, view, obj): - return True - - return False + rules = [ + UserRole.objects.filter(user=request.user, role=role).exists() and + obj.user != request.user, + super().has_object_permission(request, view, obj) + ] + return any(rules) class IsCountryAdmin(IsStandardUser): @@ -108,15 +111,13 @@ class IsCountryAdmin(IsStandardUser): country_id=obj.country_id) \ .first() # 'Comments moderator' - is_access = UserRole.objects.filter(user=request.user, role=role).exists() + rules = [ + obj.user != request.user and + UserRole.objects.filter(user=request.user, role=role).exists(), + super().has_object_permission(request, view, obj), + ] - if obj.user != request.user and is_access: - return True - - if super().has_object_permission(request, view, obj): - return True - - return False + return any(rules) class IsCommentModerator(IsStandardUser): @@ -124,22 +125,18 @@ class IsCommentModerator(IsStandardUser): Object-level permission to only allow owners of an object to edit it. Assumes the model instance has an `owner` attribute. """ - def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request. role = Role.objects.filter(role=Role.COMMENTS_MODERATOR, country_id=obj.country_id)\ .first() # 'Comments moderator' - is_access = UserRole.objects.filter(user=request.user, role=role).exists() - - if obj.user != request.user and is_access: - return True - - if super().has_object_permission(request, view, obj): - return True - - return False + rules = [ + UserRole.objects.filter(user=request.user, role=role).exists() and + obj.user != request.user, + super().has_object_permission(request, view, obj) + ] + return any(rules) class IsEstablishmentManager(IsStandardUser): @@ -148,24 +145,27 @@ class IsEstablishmentManager(IsStandardUser): role = Role.objects.filter(role=Role.ESTABLISHMENT_MANAGER)\ .first() # 'Comments moderator' - is_access = UserRole.objects.filter(user=request.user, role=role, - establishment_id=obj.establishment_id).exists() - if is_access: - return True + rules = [ + UserRole.objects.filter(user=request.user, role=role, + establishment_id=obj.establishment_id).exists(), + super().has_object_permission(request, view, obj) + ] - if super().has_object_permission(request, view, obj): - return True - - return False + return any(rules) class IsReviewerManager(IsStandardUser): + def has_object_permission(self, request, view, obj): - access_models=[""] - role = Role.objects.filter(role=Role.REVIEWER_MANGER)\ - .first() # 'Comments moderator' + role = Role.objects.filter(role=Role.REVIEWER_MANGER, + country_id=obj.country_id)\ + .first() - is_access = UserRole.objects.filter(user=request.user, role=role) - return False + rules = [ + UserRole.objects.filter(user=request.user, role=role).exists(), + super().has_object_permission(request, view, obj) + ] + + return any(rules) From d2e34e5d49a668b6e1886afc834bafed2f98f623 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 17 Oct 2019 15:10:26 +0300 Subject: [PATCH 082/223] 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 9661de481075b94978f8093d4126a727b016e7bf Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Thu, 17 Oct 2019 15:17:29 +0300 Subject: [PATCH 083/223] Establishment types --- .../0038_establishmenttype_index_name.py | 34 +++++++++++++++++++ apps/establishment/models.py | 14 ++++++++ apps/search_indexes/signals.py | 16 +++------ apps/tag/filters.py | 18 ++++------ apps/tag/models.py | 4 +++ apps/tag/views.py | 3 +- 6 files changed, 64 insertions(+), 25 deletions(-) create mode 100644 apps/establishment/migrations/0038_establishmenttype_index_name.py diff --git a/apps/establishment/migrations/0038_establishmenttype_index_name.py b/apps/establishment/migrations/0038_establishmenttype_index_name.py new file mode 100644 index 00000000..657dbe42 --- /dev/null +++ b/apps/establishment/migrations/0038_establishmenttype_index_name.py @@ -0,0 +1,34 @@ +# Generated by Django 2.2.4 on 2019-10-16 11:33 + +from django.db import migrations, models + + +def fill_establishment_type(apps, schemaeditor): + import ipdb; ipdb.set_trace() + EstablishmentType = apps.get_model('establishment', 'EstablishmentType') + for n, et in enumerate(EstablishmentType.objects.all()): + et.index_name = f'Type {n}' + et.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0037_auto_20191015_1404'), + ] + + operations = [ + migrations.AddField( + model_name='establishmenttype', + name='index_name', + field=models.CharField(blank=True, db_index=True, max_length=50, null=True, unique=True, default=None, verbose_name='Index name'), + ), + migrations.RunPython(fill_establishment_type, migrations.RunPython.noop), + migrations.AlterField( + model_name='establishmenttype', + name='index_name', + field=models.CharField(choices=[('restaurant', 'Restaurant'), ('artisan', 'Artisan'), + ('producer', 'Producer')], db_index=True, max_length=50, + unique=True, verbose_name='Index name'), + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index c93bbf89..7466414c 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -27,8 +27,22 @@ class EstablishmentType(TranslatedFieldsMixin, ProjectBaseMixin): STR_FIELD_NAME = 'name' + # INDEX NAME CHOICES + RESTAURANT = 'restaurant' + ARTISAN = 'artisan' + PRODUCER = 'producer' + + INDEX_NAME_TYPES = ( + (RESTAURANT, _('Restaurant')), + (ARTISAN, _('Artisan')), + (PRODUCER, _('Producer')), + ) + name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'), help_text='{"en-GB":"some text"}') + index_name = models.CharField(max_length=50, choices=INDEX_NAME_TYPES, + unique=True, db_index=True, + verbose_name=_('Index name')) use_subtypes = models.BooleanField(_('Use subtypes'), default=True) tag_categories = models.ManyToManyField('tag.TagCategory', related_name='establishment_types', diff --git a/apps/search_indexes/signals.py b/apps/search_indexes/signals.py index f7520b57..0f6a071f 100644 --- a/apps/search_indexes/signals.py +++ b/apps/search_indexes/signals.py @@ -40,12 +40,8 @@ def update_document(sender, **kwargs): for establishment in establishments: registry.update(establishment) - if app_label == 'main': - if model_name == 'metadata': - establishments = Establishment.objects.filter(tags__metadata=instance) - for establishment in establishments: - registry.update(establishment) - if model_name == 'metadatacontent': + if app_label == 'tag': + if model_name == 'tag': establishments = Establishment.objects.filter(tags=instance) for establishment in establishments: registry.update(establishment) @@ -70,12 +66,8 @@ def update_news(sender, **kwargs): for news in qs: registry.update(news) - if app_label == 'main': - if model_name == 'metadata': - qs = News.objects.filter(tags__metadata=instance) - for news in qs: - registry.update(news) - if model_name == 'metadatacontent': + if app_label == 'tag': + if model_name == 'tag': qs = News.objects.filter(tags=instance) for news in qs: registry.update(news) diff --git a/apps/tag/filters.py b/apps/tag/filters.py index 95f56b53..8816f820 100644 --- a/apps/tag/filters.py +++ b/apps/tag/filters.py @@ -1,5 +1,6 @@ """Tag app filters.""" from django_filters import rest_framework as filters +from establishment.models import EstablishmentType from tag import models @@ -18,16 +19,9 @@ class TagCategoryFilterSet(filters.FilterSet): type = filters.MultipleChoiceFilter(choices=TYPE_CHOICES, method='filter_by_type') - # Establishment type choices - RESTAURANT = 'restaurant' - - ESTABLISHMENT_TYPE_CHOICES = ( - (RESTAURANT, 'restaurant'), - ) - - establishment_type = filters.MultipleChoiceFilter( - choices=ESTABLISHMENT_TYPE_CHOICES, - method='filter_by_establishment_type') + establishment_type = filters.ChoiceFilter( + choices=EstablishmentType.INDEX_NAME_TYPES, + method='by_establishment_type') class Meta: """Meta class.""" @@ -44,5 +38,5 @@ class TagCategoryFilterSet(filters.FilterSet): return queryset # todo: filter by establishment type - def filter_by_establishment_type(self, queryset, name, value): - return queryset.for_establishments() + def by_establishment_type(self, queryset, name, value): + return queryset.by_establishment_type(value) diff --git a/apps/tag/models.py b/apps/tag/models.py index 4fa04f8e..d8d32639 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -45,6 +45,10 @@ class TagCategoryQuerySet(models.QuerySet): return self.filter(models.Q(establishment_types__isnull=False) | models.Q(establishment_subtypes__isnull=False)) + def by_establishment_type(self, index_name): + """Filter by establishment type index name.""" + return self.filter(establishment_types__index_name=index_name) + def with_tags(self, switcher=True): """Filter by existing tags.""" return self.filter(tags__isnull=not switcher) diff --git a/apps/tag/views.py b/apps/tag/views.py index 622ec849..ea83f3d5 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -20,5 +20,6 @@ class TagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): filterset_class = filters.TagCategoryFilterSet pagination_class = None permission_classes = (permissions.AllowAny, ) - queryset = models.TagCategory.objects.with_tags().with_base_related() + queryset = models.TagCategory.objects.with_tags().with_base_related().\ + distinct() serializer_class = serializers.TagCategoryBaseSerializer From 5a058c14f4e2276847a53cf1a82569c347906323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Thu, 17 Oct 2019 15:26:26 +0300 Subject: [PATCH 084/223] Add back for review --- apps/review/migrations/0004_review_country.py | 20 +++++++++++++++++++ apps/review/models.py | 3 +++ apps/review/serializers/back.py | 18 +++++++++++++++++ apps/review/urls/back.py | 11 ++++++++++ apps/review/views/back.py | 19 ++++++++++++++++++ project/urls/back.py | 1 + 6 files changed, 72 insertions(+) create mode 100644 apps/review/migrations/0004_review_country.py create mode 100644 apps/review/serializers/back.py create mode 100644 apps/review/urls/back.py create mode 100644 apps/review/views/back.py diff --git a/apps/review/migrations/0004_review_country.py b/apps/review/migrations/0004_review_country.py new file mode 100644 index 00000000..1d4173e0 --- /dev/null +++ b/apps/review/migrations/0004_review_country.py @@ -0,0 +1,20 @@ +# Generated by Django 2.2.4 on 2019-10-17 12:17 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('location', '0012_data_migrate'), + ('review', '0003_review_text'), + ] + + operations = [ + migrations.AddField( + model_name='review', + name='country', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='country', to='location.Country', verbose_name='Country'), + ), + ] diff --git a/apps/review/models.py b/apps/review/models.py index 9d3a39c4..4c7f3385 100644 --- a/apps/review/models.py +++ b/apps/review/models.py @@ -65,6 +65,9 @@ class Review(BaseAttributes, TranslatedFieldsMixin): validators=[MinValueValidator(1900), MaxValueValidator(2100)]) + country = models.ForeignKey('location.Country', on_delete=models.CASCADE, + related_name='country', verbose_name=_('Country'), + null=True) objects = ReviewQuerySet.as_manager() class Meta: diff --git a/apps/review/serializers/back.py b/apps/review/serializers/back.py new file mode 100644 index 00000000..3e816394 --- /dev/null +++ b/apps/review/serializers/back.py @@ -0,0 +1,18 @@ +"""Review app common serializers.""" +from review import models +from rest_framework import serializers + + +class ReviewBaseSerializer(serializers.ModelSerializer): + class Meta: + model = models.Review + fields = ('id', + 'reviewer', + 'text', + 'language', + 'status', + 'child', + 'published_at', + 'vintage', + 'country' + ) \ No newline at end of file diff --git a/apps/review/urls/back.py b/apps/review/urls/back.py new file mode 100644 index 00000000..84ca49f3 --- /dev/null +++ b/apps/review/urls/back.py @@ -0,0 +1,11 @@ +"""Back review URLs""" +from django.urls import path + +from review.views import back as views + +app_name = 'review' + +urlpatterns = [ + path('', views.ReviewLstView.as_view(), name='review-list-create'), + path('/', views.ReviewRUDView.as_view(), name='review-crud'), +] diff --git a/apps/review/views/back.py b/apps/review/views/back.py new file mode 100644 index 00000000..a09f8fd9 --- /dev/null +++ b/apps/review/views/back.py @@ -0,0 +1,19 @@ +from rest_framework import generics, permissions +from review.serializers import back as serializers +from review import models +from utils.permissions import IsReviewerManager + + +class ReviewLstView(generics.ListCreateAPIView): + """Comment list create view.""" + serializer_class = serializers.ReviewBaseSerializer + queryset = models.Review.objects.all() + permission_classes = [permissions.IsAuthenticatedOrReadOnly,] + + +class ReviewRUDView(generics.RetrieveUpdateDestroyAPIView): + """Comment RUD view.""" + serializer_class = serializers.ReviewBaseSerializer + queryset = models.Review.objects.all() + permission_classes = [IsReviewerManager] + lookup_field = 'id' diff --git a/project/urls/back.py b/project/urls/back.py index 59758c66..eb713c97 100644 --- a/project/urls/back.py +++ b/project/urls/back.py @@ -10,4 +10,5 @@ urlpatterns = [ path('news/', include('news.urls.back')), path('account/', include('account.urls.back')), path('comment/', include('comment.urls.back')), + path('review/', include('review.urls.back')), ] \ No newline at end of file From c012f969afbedf204f47886b6e2594f55eb729b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Thu, 17 Oct 2019 16:17:17 +0300 Subject: [PATCH 085/223] Add RestaurantReviewer role --- apps/account/models.py | 4 +++- apps/utils/permissions.py | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/apps/account/models.py b/apps/account/models.py index 559d3ef8..a9f739bd 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -28,6 +28,7 @@ class Role(ProjectBaseMixin): CONTENT_PAGE_MANAGER = 4 ESTABLISHMENT_MANAGER = 5 REVIEWER_MANGER = 6 + RESTAURANT_REVIEWER = 7 ROLE_CHOICES = ( (STANDARD_USER, 'Standard user'), @@ -35,7 +36,8 @@ class Role(ProjectBaseMixin): (COUNTRY_ADMIN, 'Country admin'), (CONTENT_PAGE_MANAGER, 'Content page manager'), (ESTABLISHMENT_MANAGER, 'Establishment manager'), - (REVIEWER_MANGER, 'Reviewer manager') + (REVIEWER_MANGER, 'Reviewer manager'), + (RESTAURANT_REVIEWER, 'Restaurant reviewer') ) role = models.PositiveIntegerField(verbose_name=_('Role'), choices=ROLE_CHOICES, null=False, blank=False) diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 4cfabee8..f979dca7 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -1,4 +1,6 @@ """Project custom permissions""" +from django.contrib.contenttypes.models import ContentType + from rest_framework import permissions from rest_framework_simplejwt.tokens import AccessToken @@ -169,3 +171,23 @@ class IsReviewerManager(IsStandardUser): return any(rules) + +class IsRestaurantReviewer(IsStandardUser): + + def has_object_permission(self, request, view, obj): + + content_type = ContentType.objects.get(app_lable='establishment', + model='establishment') + + role = Role.objects.filter(role=Role.RESTAURANT_REVIEWER, + country=obj.country_id).first() + + rules = [ + obj.content_type_id == content_type.id and + UserRole.objects.filter(user=request.user, role=role, + establishment_id=obj.object_id + ).exists(), + super().has_object_permission(request, view, obj) + ] + + return any(rules) From 64ac59ee99e36a6c2c8b67bbd5f5a0a7c997b2d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Thu, 17 Oct 2019 16:20:25 +0300 Subject: [PATCH 086/223] Add permission to view ReviewRUDView --- apps/review/views/back.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/review/views/back.py b/apps/review/views/back.py index a09f8fd9..2b4288d2 100644 --- a/apps/review/views/back.py +++ b/apps/review/views/back.py @@ -1,7 +1,7 @@ from rest_framework import generics, permissions from review.serializers import back as serializers from review import models -from utils.permissions import IsReviewerManager +from utils.permissions import IsReviewerManager, IsRestaurantReviewer class ReviewLstView(generics.ListCreateAPIView): @@ -15,5 +15,5 @@ class ReviewRUDView(generics.RetrieveUpdateDestroyAPIView): """Comment RUD view.""" serializer_class = serializers.ReviewBaseSerializer queryset = models.Review.objects.all() - permission_classes = [IsReviewerManager] + permission_classes = [IsReviewerManager|IsRestaurantReviewer] lookup_field = 'id' From 1ff571b7670fcd190ac9b105b75ebc5d7fdc5c1b Mon Sep 17 00:00:00 2001 From: Semyon Yekhmenin Date: Thu, 17 Oct 2019 13:29:29 +0000 Subject: [PATCH 087/223] Feature/mobile features --- apps/location/urls/web.py | 3 +- apps/main/migrations/0017_feature_route.py | 19 ++++++ apps/main/migrations/0018_feature_source.py | 18 ++++++ apps/main/models.py | 54 +++++++++------- apps/main/serializers.py | 6 +- apps/main/urls.py | 15 ----- apps/main/urls/__init__.py | 0 apps/main/urls/common.py | 12 ++++ apps/main/urls/mobile.py | 11 ++++ apps/main/urls/web.py | 11 ++++ apps/main/views/__init__.py | 0 apps/main/{views.py => views/common.py} | 69 ++++++--------------- apps/main/views/mobile.py | 16 +++++ apps/main/views/web.py | 38 ++++++++++++ project/urls/mobile.py | 1 + project/urls/web.py | 2 +- 16 files changed, 185 insertions(+), 90 deletions(-) create mode 100644 apps/main/migrations/0017_feature_route.py create mode 100644 apps/main/migrations/0018_feature_source.py delete mode 100644 apps/main/urls.py create mode 100644 apps/main/urls/__init__.py create mode 100644 apps/main/urls/common.py create mode 100644 apps/main/urls/mobile.py create mode 100644 apps/main/urls/web.py create mode 100644 apps/main/views/__init__.py rename apps/main/{views.py => views/common.py} (72%) create mode 100644 apps/main/views/mobile.py create mode 100644 apps/main/views/web.py diff --git a/apps/location/urls/web.py b/apps/location/urls/web.py index cac89037..e86a3992 100644 --- a/apps/location/urls/web.py +++ b/apps/location/urls/web.py @@ -1,7 +1,6 @@ """Location app web 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/main/migrations/0017_feature_route.py b/apps/main/migrations/0017_feature_route.py new file mode 100644 index 00000000..0b0f46bb --- /dev/null +++ b/apps/main/migrations/0017_feature_route.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.4 on 2019-10-07 14:39 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0016_merge_20190919_0954'), + ] + + operations = [ + migrations.AddField( + model_name='feature', + name='route', + field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='main.Page'), + ), + ] diff --git a/apps/main/migrations/0018_feature_source.py b/apps/main/migrations/0018_feature_source.py new file mode 100644 index 00000000..cf94cad2 --- /dev/null +++ b/apps/main/migrations/0018_feature_source.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-10-07 14:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0017_feature_route'), + ] + + operations = [ + migrations.AddField( + model_name='feature', + name='source', + field=models.PositiveSmallIntegerField(choices=[(0, 'Mobile'), (1, 'Web'), (2, 'All')], default=0, verbose_name='Source'), + ), + ] diff --git a/apps/main/models.py b/apps/main/models.py index fa6cf7d1..46598257 100644 --- a/apps/main/models.py +++ b/apps/main/models.py @@ -1,19 +1,23 @@ """Main app models.""" +from typing import Iterable + from django.conf import settings from django.contrib.contenttypes import fields as generic +from django.contrib.contenttypes.models import ContentType from django.contrib.postgres.fields import JSONField from django.db import models +from django.db.models import Q from django.utils.translation import gettext_lazy as _ -from django.contrib.contenttypes.models import ContentType from advertisement.models import Advertisement +from configuration.models import TranslationSettings from location.models import Country from main import methods from review.models import Review from utils.models import (ProjectBaseMixin, TJSONField, - TranslatedFieldsMixin, ImageMixin) + TranslatedFieldsMixin, ImageMixin, PlatformMixin) from utils.querysets import ContentTypeQuerySetMixin -from configuration.models import TranslationSettings + # # @@ -109,7 +113,6 @@ class SiteSettingsQuerySet(models.QuerySet): class SiteSettings(ProjectBaseMixin): - subdomain = models.CharField(max_length=255, db_index=True, unique=True, verbose_name=_('Subdomain')) country = models.OneToOneField(Country, on_delete=models.PROTECT, @@ -150,7 +153,8 @@ class SiteSettings(ProjectBaseMixin): @property def published_sitefeatures(self): - return self.sitefeature_set.filter(published=True) + return self.sitefeature_set\ + .filter(Q(published=True) and Q(feature__source__in=[PlatformMixin.WEB, PlatformMixin.ALL])) @property def site_url(self): @@ -159,11 +163,27 @@ class SiteSettings(ProjectBaseMixin): domain=settings.SITE_DOMAIN_URI) -class Feature(ProjectBaseMixin): +class Page(models.Model): + """Page model.""" + + page_name = models.CharField(max_length=255, unique=True) + advertisements = models.ManyToManyField(Advertisement) + + class Meta: + """Meta class.""" + verbose_name = _('Page') + verbose_name_plural = _('Pages') + + def __str__(self): + return f'{self.page_name}' + + +class Feature(ProjectBaseMixin, PlatformMixin): """Feature model.""" slug = models.CharField(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') class Meta: @@ -181,6 +201,12 @@ class SiteFeatureQuerySet(models.QuerySet): def published(self, switcher=True): return self.filter(published=switcher) + def by_country_code(self, country_code: str): + return self.filter(site_settings__country__code=country_code) + + def by_sources(self, sources: Iterable[int]): + return self.filter(feature__source__in=sources) + class SiteFeature(ProjectBaseMixin): """SiteFeature model.""" @@ -351,19 +377,3 @@ class Carousel(models.Model): def model_name(self): if hasattr(self.content_object, 'establishment_type'): return self.content_object.establishment_type.name_translated - - - -class Page(models.Model): - """Page model.""" - - page_name = models.CharField(max_length=255, unique=True) - advertisements = models.ManyToManyField(Advertisement) - - class Meta: - """Meta class.""" - verbose_name = _('Page') - verbose_name_plural = _('Pages') - - def __str__(self): - return f'{self.page_name}' diff --git a/apps/main/serializers.py b/apps/main/serializers.py index 03ea73e6..d425016d 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -25,6 +25,8 @@ class SiteFeatureSerializer(serializers.ModelSerializer): id = serializers.IntegerField(source='feature.id') slug = serializers.CharField(source='feature.slug') priority = serializers.IntegerField(source='feature.priority') + route = serializers.CharField(source='feature.route.page_name') + source = serializers.IntegerField(source='feature.source') class Meta: """Meta class.""" @@ -32,7 +34,9 @@ class SiteFeatureSerializer(serializers.ModelSerializer): fields = ('main', 'id', 'slug', - 'priority' + 'priority', + 'route', + 'source' ) diff --git a/apps/main/urls.py b/apps/main/urls.py deleted file mode 100644 index 12a98c84..00000000 --- a/apps/main/urls.py +++ /dev/null @@ -1,15 +0,0 @@ -"""Main app urls.""" -from django.urls import path -from main import views - -app = 'main' - -urlpatterns = [ - path('determine-site/', views.DetermineSiteView.as_view(), name='determine-site'), - path('determine-location/', views.DetermineLocation.as_view(), name='determine-location'), - path('sites/', views.SiteListView.as_view(), name='site-list'), - path('site-settings//', views.SiteSettingsView.as_view(), name='site-settings'), - path('awards/', views.AwardView.as_view(), name='awards_list'), - path('awards//', views.AwardRetrieveView.as_view(), name='awards_retrieve'), - path('carousel/', views.CarouselListView.as_view(), name='carousel-list'), -] \ No newline at end of file diff --git a/apps/main/urls/__init__.py b/apps/main/urls/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/main/urls/common.py b/apps/main/urls/common.py new file mode 100644 index 00000000..bac16add --- /dev/null +++ b/apps/main/urls/common.py @@ -0,0 +1,12 @@ +"""Main app urls.""" +from django.urls import path +from main.views.common import * + +app = 'main' + +common_urlpatterns = [ + path('awards/', AwardView.as_view(), name='awards_list'), + path('awards//', AwardRetrieveView.as_view(), name='awards_retrieve'), + path('carousel/', CarouselListView.as_view(), name='carousel-list'), +path('determine-location/', DetermineLocation.as_view(), name='determine-location') +] diff --git a/apps/main/urls/mobile.py b/apps/main/urls/mobile.py new file mode 100644 index 00000000..b0383d4e --- /dev/null +++ b/apps/main/urls/mobile.py @@ -0,0 +1,11 @@ +from main.urls.common import common_urlpatterns + +from django.urls import path + +from main.views.mobile import FeaturesView + +urlpatterns = [ + path('features/', FeaturesView.as_view(), name='features'), +] + +urlpatterns.extend(common_urlpatterns) diff --git a/apps/main/urls/web.py b/apps/main/urls/web.py new file mode 100644 index 00000000..2126b0c0 --- /dev/null +++ b/apps/main/urls/web.py @@ -0,0 +1,11 @@ +from main.urls.common import common_urlpatterns +from django.urls import path + +from main.views.web import DetermineSiteView, SiteListView, SiteSettingsView + +urlpatterns = [ + path('determine-site/', DetermineSiteView.as_view(), name='determine-site'), + path('sites/', SiteListView.as_view(), name='site-list'), + path('site-settings//', SiteSettingsView.as_view(), name='site-settings'), ] + +urlpatterns.extend(common_urlpatterns) diff --git a/apps/main/views/__init__.py b/apps/main/views/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/main/views.py b/apps/main/views/common.py similarity index 72% rename from apps/main/views.py rename to apps/main/views/common.py index ee06160e..e6fd3444 100644 --- a/apps/main/views.py +++ b/apps/main/views/common.py @@ -1,56 +1,9 @@ """Main app views.""" +from django.http import Http404 from rest_framework import generics, permissions from rest_framework.response import Response + from main import methods, models, serializers -from utils.serializers import EmptySerializer -from django.http import Http404 - - -class DetermineSiteView(generics.GenericAPIView): - """Determine user's site.""" - - permission_classes = (permissions.AllowAny,) - serializer_class = EmptySerializer - - def get(self, request, *args, **kwargs): - user_ip = methods.get_user_ip(request) - country_code = methods.determine_country_code(user_ip) - url = methods.determine_user_site_url(country_code) - return Response(data={'url': url}) - - -class DetermineLocation(generics.GenericAPIView): - """Determine user's location.""" - - permission_classes = (permissions.AllowAny,) - serializer_class = EmptySerializer - - def get(self, request, *args, **kwargs): - user_ip = methods.get_user_ip(request) - longitude, latitude = methods.determine_coordinates(user_ip) - city = methods.determine_user_city(user_ip) - if longitude and latitude and city: - return Response(data={'latitude': latitude, 'longitude': longitude, 'city': city}) - else: - raise Http404 - - -class SiteSettingsView(generics.RetrieveAPIView): - """Site settings View.""" - - lookup_field = 'subdomain' - permission_classes = (permissions.AllowAny,) - queryset = models.SiteSettings.objects.all() - serializer_class = serializers.SiteSettingsSerializer - - -class SiteListView(generics.ListAPIView): - """Site settings View.""" - - pagination_class = None - permission_classes = (permissions.AllowAny,) - queryset = models.SiteSettings.objects.with_country() - serializer_class = serializers.SiteSerializer # @@ -89,6 +42,7 @@ class SiteListView(generics.ListAPIView): # class SiteFeaturesRUDView(SiteFeaturesViewMixin, # generics.RetrieveUpdateDestroyAPIView): # """Site features RUD.""" +from utils.serializers import EmptySerializer class AwardView(generics.ListAPIView): @@ -111,3 +65,20 @@ class CarouselListView(generics.ListAPIView): serializer_class = serializers.CarouselListSerializer permission_classes = (permissions.AllowAny,) pagination_class = None + + +class DetermineLocation(generics.GenericAPIView): + """Determine user's location.""" + + permission_classes = (permissions.AllowAny,) + serializer_class = EmptySerializer + + def get(self, request, *args, **kwargs): + user_ip = methods.get_user_ip(request) + longitude, latitude = methods.determine_coordinates(user_ip) + city = methods.determine_user_city(user_ip) + if longitude and latitude and city: + return Response(data={'latitude': latitude, 'longitude': longitude, 'city': city}) + else: + raise Http404 + diff --git a/apps/main/views/mobile.py b/apps/main/views/mobile.py new file mode 100644 index 00000000..b992dbb8 --- /dev/null +++ b/apps/main/views/mobile.py @@ -0,0 +1,16 @@ +from rest_framework import generics, permissions + +from main import models, serializers +from utils.models import PlatformMixin + + +class FeaturesView(generics.ListAPIView): + pagination_class = None + permission_classes = (permissions.AllowAny,) + serializer_class = serializers.SiteFeatureSerializer + + def get_queryset(self): + return models.SiteFeature.objects\ + .prefetch_related('feature', 'feature__route') \ + .by_country_code(self.request.country_code) \ + .by_sources([PlatformMixin.ALL, PlatformMixin.MOBILE]) diff --git a/apps/main/views/web.py b/apps/main/views/web.py new file mode 100644 index 00000000..e1dc32ef --- /dev/null +++ b/apps/main/views/web.py @@ -0,0 +1,38 @@ +from typing import Iterable + +from rest_framework import generics, permissions + +from utils.serializers import EmptySerializer +from rest_framework.response import Response +from main import methods, models, serializers + + +class DetermineSiteView(generics.GenericAPIView): + """Determine user's site.""" + + permission_classes = (permissions.AllowAny,) + serializer_class = EmptySerializer + + def get(self, request, *args, **kwargs): + user_ip = methods.get_user_ip(request) + country_code = methods.determine_country_code(user_ip) + url = methods.determine_user_site_url(country_code) + return Response(data={'url': url}) + + +class SiteSettingsView(generics.RetrieveAPIView): + """Site settings View.""" + + lookup_field = 'subdomain' + permission_classes = (permissions.AllowAny,) + queryset = models.SiteSettings.objects.all() + serializer_class = serializers.SiteSettingsSerializer + + +class SiteListView(generics.ListAPIView): + """Site settings View.""" + + pagination_class = None + permission_classes = (permissions.AllowAny,) + queryset = models.SiteSettings.objects.with_country() + serializer_class = serializers.SiteSerializer diff --git a/project/urls/mobile.py b/project/urls/mobile.py index 0bcbd31c..0bea5305 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('main/', include('main.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 59c08a6f..9c05e1a6 100644 --- a/project/urls/web.py +++ b/project/urls/web.py @@ -27,7 +27,7 @@ urlpatterns = [ path('notifications/', include(('notification.urls.web', "notification"), namespace='notification')), path('partner/', include('partner.urls.web')), path('location/', include('location.urls.web')), - path('main/', include('main.urls')), + path('main/', include('main.urls.web')), path('recipes/', include('recipe.urls.web')), path('translation/', include('translation.urls')), path('comments/', include('comment.urls.web')), From cce0913245cf559e1625903001d49b6e5ef04660 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Thu, 17 Oct 2019 18:37:48 +0300 Subject: [PATCH 088/223] 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 089/223] 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 090/223] 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 091/223] 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 092/223] 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 2e2ed19118a6f2018621758ada99e4ca812e89f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Fri, 18 Oct 2019 12:14:10 +0300 Subject: [PATCH 093/223] Fix establishment tests --- apps/establishment/models.py | 6 ++- apps/establishment/tests.py | 84 ++++++++++++++++++++++++++++-------- apps/utils/permissions.py | 12 ++++-- apps/utils/serializers.py | 4 +- 4 files changed, 81 insertions(+), 25 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 7938ae5a..4aa37ebe 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -387,7 +387,7 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): """ Return Country id of establishment location """ - return self.address.city.country.id + return self.address.country_id @property def establishment_id(self): @@ -529,6 +529,10 @@ class Plate(TranslatedFieldsMixin, models.Model): menu = models.ForeignKey( 'establishment.Menu', verbose_name=_('menu'), on_delete=models.CASCADE) + @property + def establishment_id(self): + return self.menu.establishment.id + class Meta: verbose_name = _('plate') verbose_name_plural = _('plates') diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index 39e28861..9eb8c987 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -7,10 +7,11 @@ from main.models import Currency from establishment.models import Establishment, EstablishmentType, Menu # Create your tests here. from translation.models import Language +from account.models import Role, UserRole +from location.models import Country, Address, City, Region class BaseTestCase(APITestCase): - def setUp(self): self.username = 'sedragurda' self.password = 'sedragurdaredips19' @@ -27,10 +28,49 @@ class BaseTestCase(APITestCase): self.establishment_type = EstablishmentType.objects.create(name="Test establishment type") # Create lang object - Language.objects.create( + self.lang = Language.objects.create( title='English', locale='en-GB' ) + self.lang.save() + + self.country_ru = Country.objects.create( + name='{"ru-RU":"Russia"}', + code='23', + low_price=15, + high_price=150000, + ) + self.country_ru.languages.add(self.lang) + self.country_ru.save() + + self.region = Region.objects.create(name='Moscow area', code='01', + country=self.country_ru) + self.region.save() + + self.city = City.objects.create(name='Mosocow', code='01', + region=self.region, country=self.country_ru) + self.city.save() + + self.address = Address.objects.create(city=self.city, street_name_1='Krasnaya', + number=2, postal_code='010100') + self.address.save() + + self.role = Role.objects.create(role=Role.ESTABLISHMENT_MANAGER) + self.role.save() + + self.establishment = Establishment.objects.create( + name="Test establishment", + establishment_type_id=self.establishment_type.id, + is_publish=True, + slug="test", + address=self.address + ) + + self.establishment.save() + + self.user_role = UserRole.objects.create(user=self.user, role=self.role, + establishment=self.establishment) + self.user_role.save() class EstablishmentBTests(BaseTestCase): @@ -43,25 +83,25 @@ class EstablishmentBTests(BaseTestCase): 'name': 'Test establishment', 'type_id': self.establishment_type.id, 'is_publish': True, - 'slug': 'test-establishment-slug', + 'slug': 'test-establishment-slug' } response = self.client.post('/api/back/establishments/', data=data, format='json') self.assertEqual(response.status_code, status.HTTP_201_CREATED) - establishment = response.json() - - response = self.client.get(f'/api/back/establishments/{establishment["id"]}/', format='json') + response = self.client.get(f'/api/back/establishments/{self.establishment.id}/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) update_data = { 'name': 'Test new establishment' } - response = self.client.patch(f'/api/back/establishments/{establishment["id"]}/', data=update_data) + response = self.client.patch(f'/api/back/establishments/{self.establishment.id}/', + data=update_data) self.assertEqual(response.status_code, status.HTTP_200_OK) - response = self.client.delete(f'/api/back/establishments/{establishment["id"]}/', format='json') + response = self.client.delete(f'/api/back/establishments/{self.establishment.id}/', + format='json') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) @@ -96,39 +136,45 @@ class EmployeeTests(BaseTestCase): class ChildTestCase(BaseTestCase): def setUp(self): super().setUp() - self.establishment = Establishment.objects.create( - name="Test establishment", - establishment_type_id=self.establishment_type.id, - is_publish=True, - slug="test" - ) - # Test childs class EmailTests(ChildTestCase): - def test_email_CRUD(self): + def setUp(self): + super().setUp() + + def test_get(self): response = self.client.get('/api/back/establishments/emails/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) + def test_post(self): data = { 'email': "test@test.com", 'establishment': self.establishment.id } response = self.client.post('/api/back/establishments/emails/', data=data) + self.id_email = response.json()['id'] self.assertEqual(response.status_code, status.HTTP_201_CREATED) - response = self.client.get('/api/back/establishments/emails/1/', format='json') + def test_get_by_pk(self): + self.test_post() + response = self.client.get(f'/api/back/establishments/emails/{self.id_email}/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) + def test_patch(self): + self.test_post() + update_data = { 'email': 'testnew@test.com' } - response = self.client.patch('/api/back/establishments/emails/1/', data=update_data) + response = self.client.patch(f'/api/back/establishments/emails/{self.id_email}/', + data=update_data) self.assertEqual(response.status_code, status.HTTP_200_OK) - response = self.client.delete('/api/back/establishments/emails/1/') + def test_email_CRUD(self): + self.test_post() + response = self.client.delete(f'/api/back/establishments/emails/{self.id_email}/') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index f979dca7..17179483 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -75,10 +75,15 @@ class IsStandardUser(IsGuest): def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request rules = [ - obj.user == request.user and obj.user.email_confirmed, super().has_object_permission(request, view, obj) ] + if hasattr(obj, 'user'): + rules = [ + obj.user == request.user and obj.user.email_confirmed, + super().has_object_permission(request, view, obj) + ] + return any(rules) @@ -114,7 +119,7 @@ class IsCountryAdmin(IsStandardUser): .first() # 'Comments moderator' rules = [ - obj.user != request.user and + # obj.user != request.user and UserRole.objects.filter(user=request.user, role=role).exists(), super().has_object_permission(request, view, obj), ] @@ -149,7 +154,8 @@ class IsEstablishmentManager(IsStandardUser): rules = [ UserRole.objects.filter(user=request.user, role=role, - establishment_id=obj.establishment_id).exists(), + establishment_id=obj.establishment_id + ).exists(), super().has_object_permission(request, view, obj) ] diff --git a/apps/utils/serializers.py b/apps/utils/serializers.py index 2b2282d1..90efea00 100644 --- a/apps/utils/serializers.py +++ b/apps/utils/serializers.py @@ -33,8 +33,8 @@ def validate_tjson(value): code='invalid_json', params={'value': value}, ) - lang_count = Language.objects.filter(locale__in=value.keys()).count() - if lang_count != len(value.keys()): + is_lang = Language.objects.filter(locale__in=value.keys()).exists() + if not is_lang: raise exceptions.ValidationError( 'invalid_translated_keys', code='invalid_translated_keys', From c44f8c39c1ab6db474500d39a3c0a79519e97952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Fri, 18 Oct 2019 13:05:42 +0300 Subject: [PATCH 094/223] Fix tests news --- apps/news/tests.py | 44 +++++++++++++++++++++++++++------------ apps/utils/permissions.py | 5 ++--- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/apps/news/tests.py b/apps/news/tests.py index 27ede62c..dd256bac 100644 --- a/apps/news/tests.py +++ b/apps/news/tests.py @@ -6,7 +6,7 @@ from rest_framework import status from datetime import datetime, timedelta from news.models import NewsType, News -from account.models import User +from account.models import User, Role, UserRole from translation.models import Language from location.models import Country # Create your tests here. @@ -24,11 +24,7 @@ class BaseTestCase(APITestCase): self.client.cookies = SimpleCookie({'access_token': tokkens.get('access_token'), 'refresh_token': tokkens.get('refresh_token')}) self.test_news_type = NewsType.objects.create(name="Test news type") - self.test_news = News.objects.create(created_by=self.user, modified_by=self.user, title={"en-GB": "Test news"}, - news_type=self.test_news_type, description={"en-GB": "Description test news"}, - playlist=1, start=datetime.now() + timedelta(hours=-2), - end=datetime.now() + timedelta(hours=2), - state=News.PUBLISHED, slug='test-news-slug',) + self.lang = Language.objects.create( title='Russia', locale='ru-RU' @@ -44,16 +40,41 @@ class BaseTestCase(APITestCase): self.country_ru.languages.add(self.lang) self.country_ru.save() -class NewsTestCase(BaseTestCase): + role = Role.objects.create( + role=Role.CONTENT_PAGE_MANAGER, + country=self.country_ru + ) + role.save() - def test_news_list(self): + user_role = UserRole.objects.create( + user=self.user, + role=role + ) + user_role.save() + + self.test_news = News.objects.create(created_by=self.user, modified_by=self.user, + title={"en-GB": "Test news"}, + news_type=self.test_news_type, + description={"en-GB": "Description test news"}, + playlist=1, start=datetime.now() + timedelta(hours=-2), + end=datetime.now() + timedelta(hours=2), + state=News.PUBLISHED, slug='test-news-slug', + country=self.country_ru) + +class NewsTestCase(BaseTestCase): + def setUp(self): + super().setUp() + + def test_web_news(self): response = self.client.get("/api/web/news/") self.assertEqual(response.status_code, status.HTTP_200_OK) - def test_news_web_detail(self): response = self.client.get(f"/api/web/news/slug/{self.test_news.slug}/") self.assertEqual(response.status_code, status.HTTP_200_OK) + response = self.client.get("/api/web/news/types/") + self.assertEqual(response.status_code, status.HTTP_200_OK) + def test_news_back_detail(self): response = self.client.get(f"/api/back/news/{self.test_news.id}/") self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -62,10 +83,6 @@ class NewsTestCase(BaseTestCase): response = self.client.get("/api/back/news/") self.assertEqual(response.status_code, status.HTTP_200_OK) - def test_news_type_list(self): - response = self.client.get("/api/web/news/types/") - self.assertEqual(response.status_code, status.HTTP_200_OK) - def test_news_back_detail_put(self): # retrieve-update-destroy url = reverse('back:news:retrieve-update-destroy', kwargs={'pk': self.test_news.id}) @@ -78,5 +95,6 @@ class NewsTestCase(BaseTestCase): 'news_type_id':self.test_news.news_type_id, 'country_id': self.country_ru.id } + response = self.client.put(url, data=data, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) \ No newline at end of file diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 17179483..45d978a0 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -100,8 +100,8 @@ class IsContentPageManager(IsStandardUser): .first() # 'Comments moderator' rules = [ - UserRole.objects.filter(user=request.user, role=role).exists() and - obj.user != request.user, + UserRole.objects.filter(user=request.user, role=role).exists(), + # and obj.user != request.user, super().has_object_permission(request, view, obj) ] return any(rules) @@ -119,7 +119,6 @@ class IsCountryAdmin(IsStandardUser): .first() # 'Comments moderator' rules = [ - # obj.user != request.user and UserRole.objects.filter(user=request.user, role=role).exists(), super().has_object_permission(request, view, obj), ] From 5772558c4b8da5fa19b902ddf5afee9930ba9dfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Fri, 18 Oct 2019 16:04:21 +0300 Subject: [PATCH 095/223] Fix --- apps/location/tests.py | 26 +++++++++++++++++++------- apps/location/views/back.py | 5 +++-- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/apps/location/tests.py b/apps/location/tests.py index f68ba56b..331a9e2b 100644 --- a/apps/location/tests.py +++ b/apps/location/tests.py @@ -5,8 +5,8 @@ from account.models import User from rest_framework import status from http.cookies import SimpleCookie -from location.models import City, Region, Country - +from location.models import City, Region, Country, Language +from account.models import Role, UserRole class BaseTestCase(APITestCase): @@ -20,27 +20,39 @@ class BaseTestCase(APITestCase): # get tokens + # self.user.is_superuser = True + # self.user.save() + tokkens = User.create_jwt_tokens(self.user) self.client.cookies = SimpleCookie( {'access_token': tokkens.get('access_token'), 'refresh_token': tokkens.get('refresh_token')}) + self.lang = Language.objects.create( + title='Russia', + locale='ru-RU' + ) + self.lang.save() + + # role = Role.objects.create(role=Role.COUNTRY_ADMIN) + class CountryTests(BaseTestCase): + def setUp(self): + super().setUp() def test_country_CRUD(self): - response = self.client.get('/api/back/location/countries/', format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) - data = { - 'name': 'Test country', + 'name': {"ru-RU":"Russia"}, 'code': 'test' } - response = self.client.post('/api/back/location/countries/', data=data, format='json') response_data = response.json() self.assertEqual(response.status_code, status.HTTP_201_CREATED) + response = self.client.get('/api/back/location/countries/', format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + response = self.client.get(f'/api/back/location/countries/{response_data["id"]}/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/apps/location/views/back.py b/apps/location/views/back.py index 5c028545..3a2739b2 100644 --- a/apps/location/views/back.py +++ b/apps/location/views/back.py @@ -43,14 +43,15 @@ class RegionRUDView(common.RegionViewMixin, generics.RetrieveUpdateDestroyAPIVie serializer_class = serializers.RegionSerializer permission_classes = [IsCountryAdmin] + # Country -class CountryListCreateView(common.CountryViewMixin, generics.ListCreateAPIView): +class CountryListCreateView(generics.ListCreateAPIView): """List/Create view for model Country.""" serializer_class = serializers.CountryBackSerializer pagination_class = None permission_classes = [IsCountryAdmin] -class CountryRUDView(common.CountryViewMixin, generics.RetrieveUpdateDestroyAPIView): +class CountryRUDView(generics.RetrieveUpdateDestroyAPIView): """RUD view for model Country.""" serializer_class = serializers.CountryBackSerializer permission_classes = [IsCountryAdmin] \ No newline at end of file From 7be7315cef145ced8352e93a456aa70e57b2fad8 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 18 Oct 2019 18:00:57 +0300 Subject: [PATCH 096/223] added filters and endpoint to get winery list --- apps/establishment/filters.py | 12 ++++++ .../0038_establishmenttype_index_name.py | 5 ++- .../0039_establishmentsubtype_index_name.py | 35 +++++++++++++++++ apps/establishment/models.py | 38 +++++++++++++++++++ apps/establishment/urls/common.py | 1 + apps/establishment/views/web.py | 11 ++++++ 6 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 apps/establishment/migrations/0039_establishmentsubtype_index_name.py diff --git a/apps/establishment/filters.py b/apps/establishment/filters.py index 13d951b6..20ece644 100644 --- a/apps/establishment/filters.py +++ b/apps/establishment/filters.py @@ -10,6 +10,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') class Meta: """Meta class.""" @@ -19,6 +23,8 @@ class EstablishmentFilter(filters.FilterSet): 'tag_id', 'award_id', 'search', + 'est_type', + 'est_subtype', ) def search_text(self, queryset, name, value): @@ -27,6 +33,12 @@ class EstablishmentFilter(filters.FilterSet): return queryset.search(value, locale=self.request.locale) return queryset + def by_type(self, queryset, name, value): + return queryset.by_type(value) + + def by_subtype(self, queryset, name, value): + return queryset.by_subtype(value) + class EstablishmentTypeTagFilter(filters.FilterSet): """Establishment tag filter set.""" diff --git a/apps/establishment/migrations/0038_establishmenttype_index_name.py b/apps/establishment/migrations/0038_establishmenttype_index_name.py index 657dbe42..5f9d5879 100644 --- a/apps/establishment/migrations/0038_establishmenttype_index_name.py +++ b/apps/establishment/migrations/0038_establishmenttype_index_name.py @@ -3,8 +3,9 @@ from django.db import migrations, models -def fill_establishment_type(apps, schemaeditor): - import ipdb; ipdb.set_trace() +def fill_establishment_type(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. EstablishmentType = apps.get_model('establishment', 'EstablishmentType') for n, et in enumerate(EstablishmentType.objects.all()): et.index_name = f'Type {n}' diff --git a/apps/establishment/migrations/0039_establishmentsubtype_index_name.py b/apps/establishment/migrations/0039_establishmentsubtype_index_name.py new file mode 100644 index 00000000..5473600a --- /dev/null +++ b/apps/establishment/migrations/0039_establishmentsubtype_index_name.py @@ -0,0 +1,35 @@ +# Generated by Django 2.2.4 on 2019-10-18 13:47 + +from django.db import migrations, models + + +def fill_establishment_subtype(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. + EstablishmentSubType = apps.get_model('establishment', 'EstablishmentSubType') + for n, et in enumerate(EstablishmentSubType.objects.all()): + et.index_name = f'Type {n}' + et.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0038_establishmenttype_index_name'), + ] + + operations = [ + migrations.AddField( + model_name='establishmentsubtype', + name='index_name', + field=models.CharField(blank=True, db_index=True, max_length=50, null=True, unique=True, default=None, verbose_name='Index name'), + ), + migrations.RunPython(fill_establishment_subtype), + migrations.AlterField( + model_name='establishmentsubtype', + name='index_name', + field=models.CharField(choices=[('winery', 'Winery'), ], db_index=True, max_length=50, + unique=True, verbose_name='Index name'), + ), + + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 7466414c..d69c0395 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -68,8 +68,18 @@ class EstablishmentSubTypeManager(models.Manager): class EstablishmentSubType(ProjectBaseMixin, TranslatedFieldsMixin): """Establishment type model.""" + # INDEX NAME CHOICES + WINERY = 'winery' + + INDEX_NAME_TYPES = ( + (WINERY, _('Winery')), + ) + name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'), help_text='{"en-GB":"some text"}') + index_name = models.CharField(max_length=50, choices=INDEX_NAME_TYPES, + unique=True, db_index=True, + verbose_name=_('Index name')) establishment_type = models.ForeignKey(EstablishmentType, on_delete=models.CASCADE, verbose_name=_('Type')) @@ -104,6 +114,9 @@ class EstablishmentQuerySet(models.QuerySet): 'phones').\ prefetch_actual_employees() + def with_type_related(self): + return self.prefetch_related('establishment_subtypes') + def search(self, value, locale=None): """Search text in JSON fields.""" if locale is not None: @@ -251,6 +264,31 @@ class EstablishmentQuerySet(models.QuerySet): kwargs = {unit: radius} return self.filter(address__coordinates__distance_lte=(center, DistanceMeasure(**kwargs))) + def artisans(self): + """Return artisans.""" + return self.filter(establishment_type__index_name=EstablishmentType.ARTISAN) + + def producers(self): + """Return producers.""" + return self.filter(establishment_type__index_name=EstablishmentType.PRODUCER) + + def restaurants(self): + """Return restaurants.""" + return self.filter(establishment_type__index_name=EstablishmentType.RESTAURANT) + + def wineries(self): + """Return wineries.""" + return self.producers().filter( + establishment_subtypes__index_name=EstablishmentSubType.WINERY) + + def by_type(self, value): + """Return QuerySet with type by value.""" + return self.filter(establishment_type__index_name=value) + + def by_subtype(self, value): + """Return QuerySet with subtype by value.""" + return self.filter(establishment_subtypes__index_name=value) + class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): """Establishment model.""" diff --git a/apps/establishment/urls/common.py b/apps/establishment/urls/common.py index 5d7df146..8d9453c1 100644 --- a/apps/establishment/urls/common.py +++ b/apps/establishment/urls/common.py @@ -9,6 +9,7 @@ urlpatterns = [ path('', views.EstablishmentListView.as_view(), name='list'), path('recent-reviews/', views.EstablishmentRecentReviewListView.as_view(), name='recent-reviews'), + # path('wineries/', views.WineriesListView.as_view(), name='wineries-list'), path('slug//', views.EstablishmentRetrieveView.as_view(), name='detail'), path('slug//similar/', views.EstablishmentSimilarListView.as_view(), name='similar'), path('slug//comments/', views.EstablishmentCommentListView.as_view(), name='list-comments'), diff --git a/apps/establishment/views/web.py b/apps/establishment/views/web.py index f164ec73..cd83fed5 100644 --- a/apps/establishment/views/web.py +++ b/apps/establishment/views/web.py @@ -174,3 +174,14 @@ class EstablishmentNearestRetrieveView(EstablishmentListView, generics.ListAPIVi return qs.by_distance_from_point(**{k: v for k, v in filter_kwargs.items() if v is not None}) return qs + + +# Wineries +# todo: find out about difference between subtypes data +# class WineriesListView(EstablishmentListView): +# """Return list establishments with type Wineries""" +# +# def get_queryset(self): +# """Overridden get_queryset method.""" +# qs = super(WineriesListView, self).get_queryset() +# return qs.with_type_related().wineries() From 21e3f76f5a24847490894aefbb206f41dc4c9658 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Fri, 18 Oct 2019 18:02:24 +0300 Subject: [PATCH 097/223] Backoffice Tag&TagCategory --- .../tag/migrations/0003_auto_20191018_0758.py | 19 +++ apps/tag/models.py | 6 +- apps/tag/serializers.py | 104 ++++++++++++++++- apps/tag/urls/back.py | 11 +- apps/tag/views.py | 108 ++++++++++++++++-- .../migrations/0004_auto_20191018_0832.py | 18 +++ apps/translation/models.py | 2 +- apps/utils/exceptions.py | 23 +++- project/urls/back.py | 10 +- 9 files changed, 272 insertions(+), 29 deletions(-) create mode 100644 apps/tag/migrations/0003_auto_20191018_0758.py create mode 100644 apps/translation/migrations/0004_auto_20191018_0832.py diff --git a/apps/tag/migrations/0003_auto_20191018_0758.py b/apps/tag/migrations/0003_auto_20191018_0758.py new file mode 100644 index 00000000..3814d05a --- /dev/null +++ b/apps/tag/migrations/0003_auto_20191018_0758.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.4 on 2019-10-18 07:58 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('tag', '0002_auto_20191009_1408'), + ] + + operations = [ + migrations.AlterField( + model_name='tag', + name='category', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='tags', to='tag.TagCategory', verbose_name='Category'), + ), + ] diff --git a/apps/tag/models.py b/apps/tag/models.py index d8d32639..85d86e74 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -11,7 +11,7 @@ class Tag(TranslatedFieldsMixin, models.Model): label = TJSONField(blank=True, null=True, default=None, verbose_name=_('label'), help_text='{"en-GB":"some text"}') - category = models.ForeignKey('TagCategory', on_delete=models.PROTECT, + category = models.ForeignKey('TagCategory', on_delete=models.CASCADE, null=True, related_name='tags', verbose_name=_('Category')) @@ -36,6 +36,10 @@ class TagCategoryQuerySet(models.QuerySet): """Select related objects.""" return self.prefetch_related('tags') + def with_extended_related(self): + """Select related objects.""" + return self.select_related('country') + def for_news(self): """Select tag categories for news.""" return self.filter(news_types__isnull=True) diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py index e4ecf25d..8c994d8d 100644 --- a/apps/tag/serializers.py +++ b/apps/tag/serializers.py @@ -1,6 +1,11 @@ """Tag serializers.""" from rest_framework import serializers +from establishment.models import (Establishment, EstablishmentType, + EstablishmentSubType) +from news.models import News, NewsType from tag import models +from utils.exceptions import (ObjectAlreadyAdded, BindingObjectNotFound, + RemovedBindingObjectNotFound) from utils.serializers import TranslatedField @@ -49,14 +54,109 @@ class TagCategoryBaseSerializer(serializers.ModelSerializer): class TagCategoryBackOfficeDetailSerializer(TagCategoryBaseSerializer): + """Tag Category detail serializer for back-office users.""" country_translated = TranslatedField(source='country.name_translated') - class Meta(TagBaseSerializer.Meta): + class Meta(TagCategoryBaseSerializer.Meta): """Meta class.""" fields = TagCategoryBaseSerializer.Meta.fields + ( - 'news_types', + 'label', 'country', 'country_translated', ) + + +class TagBindObjectSerializer(serializers.Serializer): + """Serializer for binding tag category and objects""" + + ESTABLISHMENT = 'establishment' + NEWS = 'news' + + TYPE_CHOICES = ( + (ESTABLISHMENT, 'Establishment type'), + (NEWS, 'News type'), + ) + + type = serializers.ChoiceField(TYPE_CHOICES) + object_id = serializers.IntegerField() + + def validate(self, attrs): + obj_type = attrs.get('type') + obj_id = attrs.get('object_id') + request = self.context.get('request') + view = self.context.get('view') + tag = view.get_object() + attrs['tag'] = tag + if obj_type == self.ESTABLISHMENT: + establishment = Establishment.objects.filter(pk=obj_id).first() + if not establishment: + raise BindingObjectNotFound() + if request.method == 'POST' and tag.establishments.filter( + pk=establishment.pk).exists(): + raise ObjectAlreadyAdded() + if request.method == 'DELETE' and not tag.establishments.filter( + pk=establishment.pk).exists(): + raise RemovedBindingObjectNotFound() + attrs['related_object'] = establishment + elif obj_type == self.NEWS: + news = News.objects.filter(pk=obj_id).first() + if not news: + raise BindingObjectNotFound() + if request.method == 'POST' and tag.news.filter(pk=news.pk).exists(): + raise ObjectAlreadyAdded() + if request.method == 'DELETE' and not tag.news.filter( + pk=news.pk).exists(): + raise RemovedBindingObjectNotFound() + attrs['related_object'] = news + return attrs + + +class TagCategoryBindObjectSerializer(serializers.Serializer): + """Serializer for binding tag category and objects""" + + ESTABLISHMENT_TYPE = 'establishment_type' + NEWS_TYPE = 'news_type' + + TYPE_CHOICES = ( + (ESTABLISHMENT_TYPE, 'Establishment type'), + (NEWS_TYPE, 'News type'), + ) + + type = serializers.ChoiceField(TYPE_CHOICES) + object_id = serializers.IntegerField() + + def validate(self, attrs): + obj_type = attrs.get('type') + obj_id = attrs.get('object_id') + view = self.context.get('view') + tag_category = view.get_object() + attrs['tag_category'] = tag_category + request = self.context.get('request') + + if obj_type == self.ESTABLISHMENT_TYPE: + establishment_type = EstablishmentType.objects.filter(pk=obj_id).\ + first() + if not establishment_type: + raise BindingObjectNotFound() + if request.method == 'POST' and tag_category.establishment_types.\ + filter(pk=establishment_type.pk).exists(): + raise ObjectAlreadyAdded() + if request.method == 'DELETE' and not tag_category.\ + establishment_types.filter(pk=establishment_type.pk).\ + exists(): + raise RemovedBindingObjectNotFound() + attrs['related_object'] = establishment_type + elif obj_type == self.NEWS: + news_type = NewsType.objects.filter(pk=obj_id).first() + if not news_type: + raise BindingObjectNotFound() + if request.method == 'POST' and tag_category.news_types.\ + filter(pk=news_type.pk).exists(): + raise ObjectAlreadyAdded() + if request.method == 'DELETE' and not tag_category.news_types.\ + filter(pk=news_type.pk).exists(): + raise RemovedBindingObjectNotFound() + attrs['related_object'] = news_type + return attrs diff --git a/apps/tag/urls/back.py b/apps/tag/urls/back.py index 9f03bb45..03733297 100644 --- a/apps/tag/urls/back.py +++ b/apps/tag/urls/back.py @@ -1,16 +1,11 @@ """Urlconf for app tag.""" -from django.urls import path from rest_framework.routers import SimpleRouter from tag import views app_name = 'tag' router = SimpleRouter() -router.register(r'', views.TagViewSet) +router.register(r'categories', views.TagCategoryBackOfficeViewSet) +router.register(r'', views.TagBackOfficeViewSet) - -urlpatterns = [ - path('category/', views.TagCategoryListCreateView.as_view(), name='category-list-create'), -] - -urlpatterns += router.urls +urlpatterns = router.urls diff --git a/apps/tag/views.py b/apps/tag/views.py index ea83f3d5..2a0ff0f5 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -1,19 +1,12 @@ """Tag views.""" -from rest_framework import viewsets, mixins +from rest_framework import viewsets, mixins, status +from rest_framework.decorators import action +from rest_framework.response import Response from tag import filters, models, serializers from rest_framework import permissions -class TagViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, - mixins.UpdateModelMixin, mixins.DestroyModelMixin, - viewsets.GenericViewSet): - """List/create tag view.""" - - pagination_class = None - queryset = models.Tag.objects.all() - serializer_class = serializers.TagBackOfficeSerializer - - +# User`s views & viewsets class TagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): """ViewSet for TagCategory model.""" @@ -23,3 +16,96 @@ class TagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): queryset = models.TagCategory.objects.with_tags().with_base_related().\ distinct() serializer_class = serializers.TagCategoryBaseSerializer + + +# BackOffice user`s views & viewsets +class BindObjectMixin: + """Bind object mixin.""" + + def get_serializer_class(self): + if self.action == 'bind_object': + return self.bind_object_serializer_class + return self.serializer_class + + def perform_binding(self, serializer): + raise NotImplemented + + def perform_unbinding(self, serializer): + raise NotImplemented + + @action(methods=['post', 'delete'], detail=True, url_path='bind-object') + def bind_object(self, request, pk=None): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + if request.method == 'POST': + self.perform_binding(serializer) + return Response(serializer.data, status=status.HTTP_201_CREATED) + elif request.method == 'DELETE': + self.perform_unbinding(serializer) + return Response(status=status.HTTP_204_NO_CONTENT) + + +class TagBackOfficeViewSet(mixins.ListModelMixin, mixins.CreateModelMixin, + mixins.UpdateModelMixin, mixins.DestroyModelMixin, + BindObjectMixin, viewsets.GenericViewSet): + """List/create tag view.""" + + pagination_class = None + permission_classes = (permissions.IsAuthenticated, ) + queryset = models.Tag.objects.all() + serializer_class = serializers.TagBackOfficeSerializer + bind_object_serializer_class = serializers.TagBindObjectSerializer + + def perform_binding(self, serializer): + data = serializer.validated_data + tag = data.pop('tag') + obj_type = data.get('type') + related_object = data.get('related_object') + if obj_type == self.bind_object_serializer_class.ESTABLISHMENT: + tag.establishments.add(related_object) + elif obj_type == self.bind_object_serializer_class.NEWS: + tag.news.add(related_object) + + def perform_unbinding(self, serializer): + data = serializer.validated_data + tag = data.pop('tag') + obj_type = data.get('type') + related_object = data.get('related_object') + if obj_type == self.bind_object_serializer_class.ESTABLISHMENT: + tag.establishments.remove(related_object) + elif obj_type == self.bind_object_serializer_class.NEWS: + tag.news.remove(related_object) + + +class TagCategoryBackOfficeViewSet(mixins.CreateModelMixin, + mixins.UpdateModelMixin, + mixins.DestroyModelMixin, + mixins.RetrieveModelMixin, + BindObjectMixin, + TagCategoryViewSet): + """ViewSet for TagCategory model for BackOffice users.""" + + permission_classes = (permissions.IsAuthenticated, ) + queryset = TagCategoryViewSet.queryset.with_extended_related() + serializer_class = serializers.TagCategoryBackOfficeDetailSerializer + bind_object_serializer_class = serializers.TagCategoryBindObjectSerializer + + def perform_binding(self, serializer): + data = serializer.validated_data + tag_category = data.pop('tag_category') + obj_type = data.get('type') + related_object = data.get('related_object') + if obj_type == self.bind_object_serializer_class.ESTABLISHMENT_TYPE: + tag_category.establishment_types.add(related_object) + elif obj_type == self.bind_object_serializer_class.NEWS_TYPE: + tag_category.news_types.add(related_object) + + def perform_unbinding(self, serializer): + data = serializer.validated_data + tag_category = data.pop('tag_category') + obj_type = data.get('type') + related_object = data.get('related_object') + if obj_type == self.bind_object_serializer_class.ESTABLISHMENT_TYPE: + tag_category.establishment_types.remove(related_object) + elif obj_type == self.bind_object_serializer_class.NEWS_TYPE: + tag_category.news_types.remove(related_object) diff --git a/apps/translation/migrations/0004_auto_20191018_0832.py b/apps/translation/migrations/0004_auto_20191018_0832.py new file mode 100644 index 00000000..d2d26a2b --- /dev/null +++ b/apps/translation/migrations/0004_auto_20191018_0832.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-10-18 08:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('translation', '0003_auto_20190901_1032'), + ] + + operations = [ + migrations.AlterField( + model_name='language', + name='locale', + field=models.CharField(max_length=10, unique=True, verbose_name='Locale identifier'), + ), + ] diff --git a/apps/translation/models.py b/apps/translation/models.py index 42530965..bc9fbfbf 100644 --- a/apps/translation/models.py +++ b/apps/translation/models.py @@ -22,7 +22,7 @@ class Language(models.Model): title = models.CharField(max_length=255, verbose_name=_('Language title')) - locale = models.CharField(max_length=10, + locale = models.CharField(max_length=10, unique=True, verbose_name=_('Locale identifier')) objects = LanguageQuerySet.as_manager() diff --git a/apps/utils/exceptions.py b/apps/utils/exceptions.py index 440f4ed4..37786ce7 100644 --- a/apps/utils/exceptions.py +++ b/apps/utils/exceptions.py @@ -1,5 +1,5 @@ from django.utils.translation import gettext_lazy as _ -from rest_framework import exceptions, status +from rest_framework import exceptions, serializers, status class ProjectBaseException(exceptions.APIException): @@ -142,3 +142,24 @@ class PasswordResetRequestExistedError(exceptions.APIException): """ status_code = status.HTTP_400_BAD_REQUEST default_detail = _('Password reset request is already exists and valid.') + + +class ObjectAlreadyAdded(serializers.ValidationError): + """ + The exception must be thrown if the object has already been added to the + list. + """ + + default_detail = _('Object has already been added.') + + +class BindingObjectNotFound(serializers.ValidationError): + """The exception must be thrown if the object not found.""" + + default_detail = _('Binding object not found.') + + +class RemovedBindingObjectNotFound(serializers.ValidationError): + """The exception must be thrown if the object not found.""" + + default_detail = _('Removed binding object not found.') diff --git a/project/urls/back.py b/project/urls/back.py index 5d221932..206d359c 100644 --- a/project/urls/back.py +++ b/project/urls/back.py @@ -3,11 +3,11 @@ from django.urls import path, include app_name = 'back' urlpatterns = [ - path('gallery/', include(('gallery.urls', 'gallery'), namespace='gallery')), - path('establishments/', include('establishment.urls.back')), - path('location/', include('location.urls.back')), - path('news/', include('news.urls.back')), - # path('tags/', include(('tag.urls.back', 'tag'), namespace='tag')), path('account/', include('account.urls.back')), path('comment/', include('comment.urls.back')), + path('establishments/', include('establishment.urls.back')), + path('gallery/', include(('gallery.urls', 'gallery'), namespace='gallery')), + path('location/', include('location.urls.back')), + path('news/', include('news.urls.back')), + path('tags/', include(('tag.urls.back', 'tag'), namespace='tag')), ] From f7facf826df3f01cceaed754f9486a0e17d96581 Mon Sep 17 00:00:00 2001 From: Semyon Date: Fri, 18 Oct 2019 18:37:24 +0300 Subject: [PATCH 098/223] 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 1cd586662ac52e85ce268b5d2dc5e48989af8751 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Mon, 21 Oct 2019 10:12:30 +0300 Subject: [PATCH 099/223] fix tagcategory binding serializer --- apps/tag/serializers.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/tag/serializers.py b/apps/tag/serializers.py index 8c994d8d..6ee55c84 100644 --- a/apps/tag/serializers.py +++ b/apps/tag/serializers.py @@ -83,12 +83,15 @@ class TagBindObjectSerializer(serializers.Serializer): object_id = serializers.IntegerField() def validate(self, attrs): + view = self.context.get('view') + request = self.context.get('request') + obj_type = attrs.get('type') obj_id = attrs.get('object_id') - request = self.context.get('request') - view = self.context.get('view') + tag = view.get_object() attrs['tag'] = tag + if obj_type == self.ESTABLISHMENT: establishment = Establishment.objects.filter(pk=obj_id).first() if not establishment: @@ -128,12 +131,14 @@ class TagCategoryBindObjectSerializer(serializers.Serializer): object_id = serializers.IntegerField() def validate(self, attrs): + view = self.context.get('view') + request = self.context.get('request') + obj_type = attrs.get('type') obj_id = attrs.get('object_id') - view = self.context.get('view') + tag_category = view.get_object() attrs['tag_category'] = tag_category - request = self.context.get('request') if obj_type == self.ESTABLISHMENT_TYPE: establishment_type = EstablishmentType.objects.filter(pk=obj_id).\ @@ -148,7 +153,7 @@ class TagCategoryBindObjectSerializer(serializers.Serializer): exists(): raise RemovedBindingObjectNotFound() attrs['related_object'] = establishment_type - elif obj_type == self.NEWS: + elif obj_type == self.NEWS_TYPE: news_type = NewsType.objects.filter(pk=obj_id).first() if not news_type: raise BindingObjectNotFound() From c8b3f922e9200e8188ba5844df85ab6a9045ce06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Mon, 21 Oct 2019 11:08:30 +0300 Subject: [PATCH 100/223] Fix location tests --- apps/location/tests.py | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/apps/location/tests.py b/apps/location/tests.py index 331a9e2b..0770227a 100644 --- a/apps/location/tests.py +++ b/apps/location/tests.py @@ -6,6 +6,7 @@ from rest_framework import status from http.cookies import SimpleCookie from location.models import City, Region, Country, Language +from django.contrib.gis.geos import Point from account.models import Role, UserRole class BaseTestCase(APITestCase): @@ -34,6 +35,23 @@ class BaseTestCase(APITestCase): ) self.lang.save() + self.country_ru = Country.objects.create( + name='{"ru-RU":"Russia"}', + code='23', + low_price=15, + high_price=150000, + ) + self.country_ru.languages.add(self.lang) + self.country_ru.save() + + self.role = Role.objects.create(role=Role.COUNTRY_ADMIN, + country=self.country_ru) + self.role.save() + + self.user_role = UserRole.objects.create(user=self.user, role=self.role) + + self.user_role.save() + # role = Role.objects.create(role=Role.COUNTRY_ADMIN) @@ -154,6 +172,7 @@ class AddressTests(BaseTestCase): def setUp(self): super().setUp() + self.country = Country.objects.create( name=json.dumps({"en-GB": "Test country"}), code="test" @@ -172,6 +191,13 @@ class AddressTests(BaseTestCase): country=self.country ) + role = Role.objects.create(role=Role.COUNTRY_ADMIN, country=self.country) + role.save() + + user_role = UserRole.objects.create(user=self.user, role=role) + + user_role.save() + def test_address_CRUD(self): response = self.client.get('/api/back/location/addresses/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -179,10 +205,8 @@ class AddressTests(BaseTestCase): data = { 'city_id': self.city.id, 'number': '+79999999', - "coordinates": { - "latitude": 37.0625, - "longitude": -95.677068 - }, + "latitude": 37.0625, + "longitude": -95.677068, "geo_lon": -95.677068, "geo_lat": 37.0625 } From 1332a8f65a3fefefaa241423193c11b6d2ba0bb9 Mon Sep 17 00:00:00 2001 From: Semyon Yekhmenin Date: Mon, 21 Oct 2019 08:10:32 +0000 Subject: [PATCH 101/223] Login validation error message fix --- project/locale/ru/LC_MESSAGES/django.po | 3091 +++++++++++++++++++++++ 1 file changed, 3091 insertions(+) create mode 100644 project/locale/ru/LC_MESSAGES/django.po diff --git a/project/locale/ru/LC_MESSAGES/django.po b/project/locale/ru/LC_MESSAGES/django.po new file mode 100644 index 00000000..8092593a --- /dev/null +++ b/project/locale/ru/LC_MESSAGES/django.po @@ -0,0 +1,3091 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-10-17 13:52+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n" +"%100>=11 && n%100<=14)? 2 : 3);\n" + +#: apps/account/admin.py:30 +msgid "Personal info" +msgstr "" + +#: apps/account/admin.py:34 +msgid "Subscription" +msgstr "" + +#: apps/account/admin.py:39 +msgid "Important dates" +msgstr "" + +#: apps/account/admin.py:40 +msgid "Permissions" +msgstr "" + +#: apps/account/admin.py:59 apps/location/models.py:18 +#: venv/lib/python3.6/site-packages/fcm_django/models.py:14 +msgid "Name" +msgstr "" + +#: apps/account/apps.py:7 +msgid "Account" +msgstr "" + +#: apps/account/forms.py:15 +msgid "The two password fields didn't match." +msgstr "" + +#: apps/account/forms.py:16 +msgid "Password already in use." +msgstr "" + +#: apps/account/forms.py:19 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_change_form.html:50 +msgid "New password" +msgstr "" + +#: apps/account/forms.py:25 +msgid "New password confirmation" +msgstr "" + +#: apps/account/models.py:31 apps/account/models.py:227 +msgid "Role" +msgstr "" + +#: apps/account/models.py:33 apps/location/models.py:28 apps/main/models.py:117 +msgid "Country" +msgstr "" + +#: apps/account/models.py:76 apps/news/models.py:126 apps/utils/models.py:194 +msgid "Image URL path" +msgstr "" + +#: apps/account/models.py:78 +msgid "Cropped image URL path" +msgstr "" + +#: apps/account/models.py:80 +msgid "email address" +msgstr "" + +#: apps/account/models.py:82 +msgid "unconfirmed email" +msgstr "" + +#: apps/account/models.py:83 +msgid "email status" +msgstr "" + +#: apps/account/models.py:90 +msgid "Roles" +msgstr "" + +#: apps/account/models.py:95 apps/account/models.py:226 +#: apps/comment/models.py:38 apps/establishment/models.py:435 +#: apps/favorites/models.py:23 apps/notification/models.py:79 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/object_history.html:30 +msgid "User" +msgstr "" + +#: apps/account/models.py:96 +msgid "Users" +msgstr "" + +#: apps/account/serializers/common.py:121 +msgid "Old password mismatch." +msgstr "" + +#: apps/account/serializers/common.py:124 apps/utils/exceptions.py:103 +msgid "Password is already in use" +msgstr "" + +#: apps/account/tasks.py:18 +msgid "Password resetting" +msgstr "" + +#: apps/account/tasks.py:31 apps/account/tasks.py:43 +msgid "Validate new email address" +msgstr "" + +#: apps/advertisement/apps.py:7 +msgid "advertisement" +msgstr "" + +#: apps/advertisement/models.py:15 +msgid "Ad URL" +msgstr "" + +#: apps/advertisement/models.py:16 +msgid "Block width" +msgstr "" + +#: apps/advertisement/models.py:17 +msgid "Block height" +msgstr "" + +#: apps/advertisement/models.py:18 +msgid "Block level" +msgstr "" + +#: apps/advertisement/models.py:22 apps/advertisement/models.py:23 +msgid "Advertisement" +msgstr "" + +#: apps/authorization/apps.py:8 +msgid "Authorization" +msgstr "" + +#: apps/authorization/models.py:86 +msgid "Expiration datetime" +msgstr "" + +#: apps/authorization/models.py:94 +msgid "Access token" +msgstr "" + +#: apps/authorization/models.py:95 +msgid "Access tokens" +msgstr "" + +#: apps/authorization/models.py:154 +msgid "Refresh token" +msgstr "" + +#: apps/authorization/models.py:155 +msgid "Refresh tokens" +msgstr "" + +#: apps/authorization/tasks.py:18 +msgid "Email confirmation" +msgstr "" + +#: apps/authorization/views/common.py:40 +msgid "Application is not found" +msgstr "" + +#: apps/authorization/views/common.py:50 +msgid "Not found an application with this source" +msgstr "" + +#: apps/booking/apps.py:7 apps/booking/models/models.py:66 +#: apps/booking/models/models.py:67 +msgid "Booking" +msgstr "" + +#: apps/booking/models/models.py:21 +msgid "Guestonline or Lastable" +msgstr "" + +#: apps/booking/models/models.py:22 +msgid "booking service establishment id" +msgstr "" + +#: apps/booking/models/models.py:23 +msgid "booking locale" +msgstr "" + +#: apps/booking/models/models.py:24 +msgid "external service pending booking" +msgstr "" + +#: apps/booking/models/models.py:25 +msgid "external service booking id" +msgstr "" + +#: apps/booking/models/models.py:28 +msgid "booking owner" +msgstr "" + +#: apps/collection/apps.py:7 apps/collection/models.py:80 +#: apps/collection/models.py:106 +msgid "collection" +msgstr "" + +#: apps/collection/models.py:17 apps/establishment/models.py:241 +#: apps/establishment/models.py:504 apps/location/models.py:34 +#: apps/location/models.py:55 apps/main/models.py:227 apps/main/models.py:278 +#: apps/news/models.py:14 +msgid "name" +msgstr "" + +#: apps/collection/models.py:26 +msgid "start" +msgstr "" + +#: apps/collection/models.py:27 +msgid "end" +msgstr "" + +#: apps/collection/models.py:54 +msgid "Ordinary" +msgstr "" + +#: apps/collection/models.py:55 +msgid "Pop" +msgstr "" + +#: apps/collection/models.py:60 +msgid "Collection type" +msgstr "" + +#: apps/collection/models.py:62 apps/establishment/models.py:280 +msgid "Publish status" +msgstr "" + +#: apps/collection/models.py:64 +msgid "Position on top" +msgstr "" + +#: apps/collection/models.py:66 apps/location/models.py:40 +#: apps/location/models.py:60 apps/main/models.py:226 apps/news/models.py:135 +msgid "country" +msgstr "" + +#: apps/collection/models.py:68 +msgid "collection block properties" +msgstr "" + +#: apps/collection/models.py:71 apps/establishment/models.py:245 +#: apps/establishment/models.py:507 apps/news/models.py:111 +msgid "description" +msgstr "" + +#: apps/collection/models.py:74 +msgid "Collection slug" +msgstr "" + +#: apps/collection/models.py:81 +msgid "collections" +msgstr "" + +#: apps/collection/models.py:99 +msgid "parent" +msgstr "" + +#: apps/collection/models.py:103 +msgid "advertorials" +msgstr "" + +#: apps/collection/models.py:112 +msgid "guide" +msgstr "" + +#: apps/collection/models.py:113 +msgid "guides" +msgstr "" + +#: apps/comment/apps.py:7 +msgid "comment" +msgstr "" + +#: apps/comment/apps.py:8 +msgid "comments" +msgstr "" + +#: apps/comment/models.py:32 +msgid "Comment text" +msgstr "" + +#: apps/comment/models.py:34 +msgid "Mark" +msgstr "" + +#: apps/comment/models.py:44 +msgid "Locale" +msgstr "" + +#: apps/comment/models.py:48 +msgid "Comment" +msgstr "" + +#: apps/comment/models.py:49 +msgid "Comments" +msgstr "" + +#: apps/configuration/apps.py:7 +msgid "configuration" +msgstr "" + +#: apps/configuration/models.py:9 +msgid "default language" +msgstr "" + +#: apps/establishment/admin.py:87 apps/establishment/models.py:529 +#: apps/main/models.py:248 +msgid "category" +msgstr "" + +#: apps/establishment/apps.py:8 apps/establishment/models.py:310 +#: apps/establishment/models.py:418 +msgid "Establishment" +msgstr "" + +#: apps/establishment/models.py:30 apps/establishment/models.py:54 +#: apps/establishment/models.py:391 apps/recipe/models.py:52 +msgid "Description" +msgstr "" + +#: apps/establishment/models.py:32 +msgid "Use subtypes" +msgstr "" + +#: apps/establishment/models.py:37 +msgid "Establishment type" +msgstr "" + +#: apps/establishment/models.py:38 +msgid "Establishment types" +msgstr "" + +#: apps/establishment/models.py:58 +msgid "Type" +msgstr "" + +#: apps/establishment/models.py:65 +msgid "Establishment subtype" +msgstr "" + +#: apps/establishment/models.py:66 +msgid "Establishment subtypes" +msgstr "" + +#: apps/establishment/models.py:70 +msgid "Establishment type is not use subtypes." +msgstr "" + +#: apps/establishment/models.py:242 +msgid "Transliterated name" +msgstr "" + +#: apps/establishment/models.py:249 +msgid "public mark" +msgstr "" + +#: apps/establishment/models.py:252 +msgid "toque number" +msgstr "" + +#: apps/establishment/models.py:256 +msgid "type" +msgstr "" + +#: apps/establishment/models.py:259 +msgid "subtype" +msgstr "" + +#: apps/establishment/models.py:262 apps/news/models.py:131 +msgid "address" +msgstr "" + +#: apps/establishment/models.py:265 +msgid "price level" +msgstr "" + +#: apps/establishment/models.py:267 +msgid "Web site URL" +msgstr "" + +#: apps/establishment/models.py:269 +msgid "Facebook URL" +msgstr "" + +#: apps/establishment/models.py:271 +msgid "Twitter URL" +msgstr "" + +#: apps/establishment/models.py:273 +msgid "Lafourchette URL" +msgstr "" + +#: apps/establishment/models.py:274 +msgid "guestonline id" +msgstr "" + +#: apps/establishment/models.py:276 +msgid "lastable id" +msgstr "" + +#: apps/establishment/models.py:279 +msgid "Booking URL" +msgstr "" + +#: apps/establishment/models.py:282 +msgid "Establishment schedule" +msgstr "" + +#: apps/establishment/models.py:289 +msgid "Transportation" +msgstr "" + +#: apps/establishment/models.py:293 +msgid "Collections" +msgstr "" + +#: apps/establishment/models.py:294 apps/news/models.py:128 +msgid "Preview image URL path" +msgstr "" + +#: apps/establishment/models.py:297 +msgid "Establishment slug" +msgstr "" + +#: apps/establishment/models.py:311 +msgid "Establishments" +msgstr "" + +#: apps/establishment/models.py:399 apps/establishment/models.py:425 +msgid "Position" +msgstr "" + +#: apps/establishment/models.py:400 +msgid "Positions" +msgstr "" + +#: apps/establishment/models.py:420 apps/establishment/models.py:445 +msgid "Employee" +msgstr "" + +#: apps/establishment/models.py:421 +msgid "From date" +msgstr "" + +#: apps/establishment/models.py:423 +msgid "To date" +msgstr "" + +#: apps/establishment/models.py:436 +msgid "Last name" +msgstr "" + +#: apps/establishment/models.py:446 +msgid "Employees" +msgstr "" + +#: apps/establishment/models.py:460 +msgid "contact phone" +msgstr "" + +#: apps/establishment/models.py:461 +msgid "contact phones" +msgstr "" + +#: apps/establishment/models.py:474 +msgid "contact email" +msgstr "" + +#: apps/establishment/models.py:475 +msgid "contact emails" +msgstr "" + +#: apps/establishment/models.py:510 +msgid "price" +msgstr "" + +#: apps/establishment/models.py:511 +msgid "is signature plate" +msgstr "" + +#: apps/establishment/models.py:513 apps/main/models.py:281 +msgid "currency" +msgstr "" + +#: apps/establishment/models.py:516 apps/establishment/models.py:536 +#: apps/establishment/models.py:537 +msgid "menu" +msgstr "" + +#: apps/establishment/models.py:519 +msgid "plate" +msgstr "" + +#: apps/establishment/models.py:520 +msgid "plates" +msgstr "" + +#: apps/establishment/models.py:532 apps/establishment/models.py:542 +msgid "establishment" +msgstr "" + +#: apps/establishment/models.py:544 apps/main/models.py:207 +#: apps/news/models.py:105 +msgid "title" +msgstr "" + +#: apps/establishment/models.py:545 +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2234 +msgid "URL" +msgstr "" + +#: apps/establishment/models.py:548 +msgid "social network" +msgstr "" + +#: apps/establishment/models.py:549 +msgid "social networks" +msgstr "" + +#: apps/establishment/serializers/common.py:237 +#: apps/timetable/serialziers.py:47 +msgid "Establishment not found." +msgstr "" + +#: apps/establishment/serializers/common.py:288 +msgid "Object not found." +msgstr "" + +#: apps/favorites/apps.py:7 apps/favorites/models.py:32 +#: apps/favorites/models.py:33 +msgid "Favorites" +msgstr "" + +#: apps/gallery/apps.py:7 +msgid "gallery" +msgstr "" + +#: apps/gallery/models.py:12 +msgid "Image file" +msgstr "" + +#: apps/gallery/models.py:16 apps/utils/models.py:147 apps/utils/models.py:176 +#: apps/utils/models.py:207 +#: venv/lib/python3.6/site-packages/django/db/models/fields/files.py:360 +msgid "Image" +msgstr "" + +#: apps/gallery/models.py:17 +msgid "Images" +msgstr "" + +#: apps/location/admin.py:28 apps/main/apps.py:8 apps/main/models.py:191 +msgid "Main" +msgstr "" + +#: apps/location/admin.py:31 +msgid "Location" +msgstr "" + +#: apps/location/admin.py:34 +msgid "Address detail" +msgstr "" + +#: apps/location/apps.py:7 +msgid "location" +msgstr "" + +#: apps/location/models.py:19 +msgid "Code" +msgstr "" + +#: apps/location/models.py:20 +msgid "Low price" +msgstr "" + +#: apps/location/models.py:21 +msgid "High price" +msgstr "" + +#: apps/location/models.py:22 apps/translation/models.py:34 +msgid "Languages" +msgstr "" + +#: apps/location/models.py:27 +msgid "Countries" +msgstr "" + +#: apps/location/models.py:35 apps/location/models.py:56 +msgid "code" +msgstr "" + +#: apps/location/models.py:37 apps/location/models.py:58 +msgid "parent region" +msgstr "" + +#: apps/location/models.py:45 +msgid "regions" +msgstr "" + +#: apps/location/models.py:46 +msgid "region" +msgstr "" + +#: apps/location/models.py:63 apps/location/models.py:85 +msgid "postal code" +msgstr "" + +#: apps/location/models.py:63 apps/location/models.py:86 +msgid "Ex.: 350018" +msgstr "" + +#: apps/location/models.py:65 +msgid "is island" +msgstr "" + +#: apps/location/models.py:68 +msgid "cities" +msgstr "" + +#: apps/location/models.py:69 apps/location/models.py:78 +msgid "city" +msgstr "" + +#: apps/location/models.py:80 +msgid "street name 1" +msgstr "" + +#: apps/location/models.py:82 +msgid "street name 2" +msgstr "" + +#: apps/location/models.py:83 +msgid "number" +msgstr "" + +#: apps/location/models.py:88 +msgid "Coordinates" +msgstr "" + +#: apps/location/models.py:93 apps/location/models.py:94 +msgid "Address" +msgstr "" + +#: apps/location/serializers/common.py:120 +#: apps/location/serializers/common.py:125 +msgid "Invalid value" +msgstr "" + +#: apps/main/models.py:114 +msgid "Subdomain" +msgstr "" + +#: apps/main/models.py:119 +msgid "Default site" +msgstr "" + +#: apps/main/models.py:121 +msgid "Pinterest page URL" +msgstr "" + +#: apps/main/models.py:123 +msgid "Twitter page URL" +msgstr "" + +#: apps/main/models.py:125 +msgid "Facebook page URL" +msgstr "" + +#: apps/main/models.py:127 +msgid "Instagram page URL" +msgstr "" + +#: apps/main/models.py:129 +msgid "Contact email" +msgstr "" + +#: apps/main/models.py:131 +msgid "Config" +msgstr "" + +#: apps/main/models.py:133 +msgid "AD config" +msgstr "" + +#: apps/main/models.py:140 +msgid "Site setting" +msgstr "" + +#: apps/main/models.py:141 +msgid "Site settings" +msgstr "" + +#: apps/main/models.py:171 +msgid "Feature" +msgstr "" + +#: apps/main/models.py:172 +msgid "Features" +msgstr "" + +#: apps/main/models.py:190 apps/news/models.py:98 apps/recipe/models.py:42 +msgid "Published" +msgstr "" + +#: apps/main/models.py:198 +msgid "Site feature" +msgstr "" + +#: apps/main/models.py:199 +msgid "Site features" +msgstr "" + +#: apps/main/models.py:209 +msgid "vintage year" +msgstr "" + +#: apps/main/models.py:245 +msgid "label" +msgstr "" + +#: apps/main/models.py:251 apps/main/models.py:252 +msgid "metadata" +msgstr "" + +#: apps/main/models.py:282 +msgid "currencies" +msgstr "" + +#: apps/main/models.py:302 apps/main/models.py:303 +msgid "Carousel" +msgstr "" + +#: apps/main/models.py:365 apps/translation/models.py:49 +msgid "Page" +msgstr "" + +#: apps/main/models.py:366 +msgid "Pages" +msgstr "" + +#: apps/news/apps.py:7 apps/news/models.py:145 apps/news/models.py:146 +msgid "news" +msgstr "" + +#: apps/news/models.py:19 +msgid "news types" +msgstr "" + +#: apps/news/models.py:20 apps/news/models.py:103 +msgid "news type" +msgstr "" + +#: apps/news/models.py:96 apps/recipe/models.py:40 +msgid "Waiting" +msgstr "" + +#: apps/news/models.py:97 apps/recipe/models.py:41 +msgid "Hidden" +msgstr "" + +#: apps/news/models.py:99 apps/recipe/models.py:43 +msgid "Published exclusive" +msgstr "" + +#: apps/news/models.py:108 +msgid "subtitle" +msgstr "" + +#: apps/news/models.py:113 +msgid "Start" +msgstr "" + +#: apps/news/models.py:115 +msgid "End" +msgstr "" + +#: apps/news/models.py:117 +msgid "News slug" +msgstr "" + +#: apps/news/models.py:118 +msgid "playlist" +msgstr "" + +#: apps/news/models.py:120 apps/notification/models.py:89 +#: apps/recipe/models.py:55 +msgid "State" +msgstr "" + +#: apps/news/models.py:122 +msgid "Is highlighted" +msgstr "" + +#: apps/notification/apps.py:7 +msgid "notification" +msgstr "" + +#: apps/notification/models.py:73 +msgid "Unusable" +msgstr "" + +#: apps/notification/models.py:74 +msgid "Usable" +msgstr "" + +#: apps/notification/models.py:81 +msgid "Email" +msgstr "" + +#: apps/notification/models.py:83 +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1891 +msgid "IP address" +msgstr "" + +#: apps/notification/models.py:85 +msgid "Country code" +msgstr "" + +#: apps/notification/models.py:87 apps/translation/models.py:26 +msgid "Locale identifier" +msgstr "" + +#: apps/notification/models.py:91 +msgid "Token" +msgstr "" + +#: apps/notification/models.py:98 +msgid "Subscriber" +msgstr "" + +#: apps/notification/models.py:99 +msgid "Subscribers" +msgstr "" + +#: apps/notification/serializers/common.py:29 +msgid "Does not match user email" +msgstr "" + +#: apps/notification/serializers/common.py:32 +#: venv/lib/python3.6/site-packages/django/forms/fields.py:53 +msgid "This field is required." +msgstr "" + +#: apps/partner/apps.py:7 apps/partner/models.py:12 +msgid "partner" +msgstr "" + +#: apps/partner/models.py:9 +msgid "Partner URL" +msgstr "" + +#: apps/partner/models.py:13 +msgid "partners" +msgstr "" + +#: apps/products/apps.py:8 +msgid "products" +msgstr "" + +#: apps/rating/models.py:11 +msgid "ip" +msgstr "" + +#: apps/recipe/apps.py:8 +msgid "RecipeConfig" +msgstr "" + +#: apps/recipe/models.py:48 +msgid "Title" +msgstr "" + +#: apps/recipe/models.py:50 +msgid "Subtitle" +msgstr "" + +#: apps/recipe/models.py:57 +msgid "Author" +msgstr "" + +#: apps/recipe/models.py:58 apps/recipe/models.py:60 +msgid "Published at" +msgstr "" + +#: apps/recipe/models.py:61 apps/recipe/models.py:63 +msgid "Published scheduled at" +msgstr "" + +#: apps/recipe/models.py:70 +msgid "Recipe" +msgstr "" + +#: apps/recipe/models.py:71 +msgid "Recipes" +msgstr "" + +#: apps/review/apps.py:7 +msgid "reviews" +msgstr "" + +#: apps/review/models.py:37 +msgid "To investigate" +msgstr "" + +#: apps/review/models.py:38 +msgid "To review" +msgstr "" + +#: apps/review/models.py:39 +msgid "Ready" +msgstr "" + +#: apps/review/models.py:45 +msgid "Reviewer" +msgstr "" + +#: apps/review/models.py:47 +msgid "text" +msgstr "" + +#: apps/review/models.py:55 +msgid "Review language" +msgstr "" + +#: apps/review/models.py:60 +msgid "Child review" +msgstr "" + +#: apps/review/models.py:61 +msgid "Publish datetime" +msgstr "" + +#: apps/review/models.py:63 +msgid "Review published datetime" +msgstr "" + +#: apps/review/models.py:64 +msgid "Year of review" +msgstr "" + +#: apps/review/models.py:72 +msgid "Review" +msgstr "" + +#: apps/review/models.py:73 +msgid "Reviews" +msgstr "" + +#: apps/search_indexes/apps.py:7 +msgid "Search indexes" +msgstr "" + +#: apps/timetable/apps.py:7 +msgid "timetable" +msgstr "" + +#: apps/timetable/models.py:18 +#: venv/lib/python3.6/site-packages/django/utils/dates.py:6 +msgid "Monday" +msgstr "" + +#: apps/timetable/models.py:19 +#: venv/lib/python3.6/site-packages/django/utils/dates.py:6 +msgid "Tuesday" +msgstr "" + +#: apps/timetable/models.py:20 +#: venv/lib/python3.6/site-packages/django/utils/dates.py:6 +msgid "Wednesday" +msgstr "" + +#: apps/timetable/models.py:21 +#: venv/lib/python3.6/site-packages/django/utils/dates.py:6 +msgid "Thursday" +msgstr "" + +#: apps/timetable/models.py:22 +#: venv/lib/python3.6/site-packages/django/utils/dates.py:6 +msgid "Friday" +msgstr "" + +#: apps/timetable/models.py:23 +#: venv/lib/python3.6/site-packages/django/utils/dates.py:7 +msgid "Saturday" +msgstr "" + +#: apps/timetable/models.py:24 +#: venv/lib/python3.6/site-packages/django/utils/dates.py:7 +msgid "Sunday" +msgstr "" + +#: apps/timetable/models.py:26 +msgid "Week day" +msgstr "" + +#: apps/timetable/models.py:28 +msgid "Lunch start time" +msgstr "" + +#: apps/timetable/models.py:29 +msgid "Lunch end time" +msgstr "" + +#: apps/timetable/models.py:30 +msgid "Dinner start time" +msgstr "" + +#: apps/timetable/models.py:31 +msgid "Dinner end time" +msgstr "" + +#: apps/timetable/models.py:32 +msgid "Opening time" +msgstr "" + +#: apps/timetable/models.py:33 +msgid "Closed time" +msgstr "" + +#: apps/timetable/models.py:37 +msgid "Timetable" +msgstr "" + +#: apps/timetable/models.py:38 +msgid "Timetables" +msgstr "" + +#: apps/translation/apps.py:7 +msgid "Translation" +msgstr "" + +#: apps/translation/models.py:24 +msgid "Language title" +msgstr "" + +#: apps/translation/models.py:33 +msgid "Language" +msgstr "" + +#: apps/translation/models.py:51 +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2075 +msgid "Text" +msgstr "" + +#: apps/translation/models.py:59 apps/translation/models.py:60 +msgid "Site interface dictionary" +msgstr "" + +#: apps/utils/exceptions.py:8 +msgid "Bad request" +msgstr "" + +#: apps/utils/exceptions.py:27 +msgid "Service is temporarily unavailable" +msgstr "" + +#: apps/utils/exceptions.py:32 +msgid "User not found" +msgstr "" + +#: apps/utils/exceptions.py:38 +#, python-format +msgid "Unable to send message to mailbox %s" +msgstr "" + +#: apps/utils/exceptions.py:53 +#, python-format +msgid "Locale not found in database (%s)" +msgstr "" + +#: apps/utils/exceptions.py:68 +msgid "Wrong username" +msgstr "" + +#: apps/utils/exceptions.py:76 +msgid "Not valid token" +msgstr "" + +#: apps/utils/exceptions.py:83 +msgid "Not valid access token" +msgstr "" + +#: apps/utils/exceptions.py:90 +msgid "Not valid refresh token" +msgstr "" + +#: apps/utils/exceptions.py:95 +msgid "OAuth2 Error" +msgstr "" + +#: apps/utils/exceptions.py:111 +msgid "Email address is already confirmed" +msgstr "" + +#: apps/utils/exceptions.py:119 +msgid "Image invalid input." +msgstr "" + +#: apps/utils/exceptions.py:126 +msgid "Incorrect login or password." +msgstr "Неправильный логин или пароль." + +#: apps/utils/exceptions.py:135 +msgid "Item is already in favorites." +msgstr "" + +#: apps/utils/exceptions.py:144 +msgid "Password reset request is already exists and valid." +msgstr "" + +#: apps/utils/models.py:21 +msgid "Date created" +msgstr "" + +#: apps/utils/models.py:23 +msgid "Date updated" +msgstr "" + +#: apps/utils/models.py:126 +msgid "created by" +msgstr "" + +#: apps/utils/models.py:130 +msgid "modified by" +msgstr "" + +#: apps/utils/models.py:186 +msgid "SVG image" +msgstr "" + +#: apps/utils/models.py:219 +msgid "Mobile" +msgstr "" + +#: apps/utils/models.py:220 +msgid "Web" +msgstr "" + +#: apps/utils/models.py:221 +msgid "All" +msgstr "" + +#: apps/utils/models.py:224 +msgid "Source" +msgstr "" + +#: project/templates/account/change_email.html:2 +#, python-format +msgid "" +"You're receiving this email because you want to change email address at " +"%(site_name)s." +msgstr "" + +#: project/templates/account/change_email.html:4 +msgid "Please go to the following page for confirmation new email address:" +msgstr "" + +#: project/templates/account/change_email.html:8 +#: project/templates/account/password_reset_email.html:8 +#: project/templates/authorization/confirm_email.html:7 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_email.html:10 +msgid "Thanks for using our site!" +msgstr "" + +#: project/templates/account/change_email.html:10 +#: project/templates/account/password_reset_email.html:10 +#: project/templates/authorization/confirm_email.html:9 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_email.html:12 +#, python-format +msgid "The %(site_name)s team" +msgstr "" + +#: project/templates/account/password_reset_email.html:2 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_email.html:2 +#, python-format +msgid "" +"You're receiving this email because you requested a password reset for your " +"user account at %(site_name)s." +msgstr "" + +#: project/templates/account/password_reset_email.html:4 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_email.html:4 +msgid "Please go to the following page and choose a new password:" +msgstr "" + +#: project/templates/authorization/confirm_email.html:2 +#, python-format +msgid "" +"You're receiving this email because you trying to register new account at " +"%(site_name)s." +msgstr "" + +#: project/templates/authorization/confirm_email.html:4 +msgid "Please confirm your email address to complete the registration:" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/404.html:4 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/500.html:6 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/app_index.html:10 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/auth/user/change_password.html:19 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base.html:163 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_form.html:22 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_list.html:32 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/delete_confirmation.html:14 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/delete_selected_confirmation.html:15 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/object_history.html:7 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_change_done.html:7 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_change_form.html:12 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_complete.html:7 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_confirm.html:7 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_done.html:7 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_form.html:7 +#: venv/lib/python3.6/site-packages/solo/templates/admin/solo/change_form.html:7 +#: venv/lib/python3.6/site-packages/solo/templates/admin/solo/object_history.html:6 +msgid "Home" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/500.html:7 +msgid "Server error" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/500.html:11 +msgid "Server error (500)" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/500.html:14 +msgid "Server Error (500)" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/500.html:15 +msgid "" +"There's been an error. It's been reported to the site administrators via " +"email and should be fixed shortly. Thanks for your patience." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/actions.html:7 +msgid "Run the selected action" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/actions.html:7 +msgid "Go" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/actions.html:19 +msgid "Click here to select the objects across all pages" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/actions.html:19 +#, python-format +msgid "Select all %(total_count)s %(module_name)s" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/actions.html:21 +msgid "Clear selection" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/auth/user/add_form.html:7 +msgid "" +"First, enter a username and password. Then, you'll be able to edit more user " +"options." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/auth/user/add_form.html:10 +msgid "Enter a username and password." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/auth/user/change_password.html:31 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/auth/user/change_password.html:75 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base.html:143 +msgid "Change password" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/auth/user/change_password.html:44 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_form.html:65 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_list.html:58 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/login.html:44 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_change_form.html:31 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/auth/user/change_password.html:44 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_form.html:65 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_change_form.html:31 +msgid "Please correct the errors below." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/auth/user/change_password.html:50 +#, python-format +msgid "Enter a new password for the user %(username)s." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/auth/user/change_password.html:60 +msgid "Password" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/auth/user/change_password.html:68 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_change_form.html:57 +msgid "Password (again)" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/auth/user/change_password.html:69 +msgid "Enter the same password as above, for verification." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base.html:72 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base.html:116 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base.html:119 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base_site.html:9 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/login.html:31 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/login.html:36 +msgid "Django administration" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base.html:81 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:52 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:58 +#, python-format +msgid "Models in the %(name)s application" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base.html:87 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base.html:93 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:143 +msgid "You don't have permission to edit anything." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base.html:138 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base.html:152 +msgid "Documentation" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base.html:147 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base.html:154 +msgid "Log out" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base.html:173 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_list.html:57 +msgid "Close" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_form.html:39 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:65 +msgid "Add" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_form.html:114 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_form.html:116 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/object_history.html:18 +#: venv/lib/python3.6/site-packages/solo/templates/admin/solo/change_form.html:14 +#: venv/lib/python3.6/site-packages/solo/templates/admin/solo/object_history.html:9 +msgid "History" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_form.html:121 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_form.html:123 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/edit_inline/stacked.html:24 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/edit_inline/tabular.html:37 +#: venv/lib/python3.6/site-packages/solo/templates/admin/solo/change_form.html:15 +msgid "View on site" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_list.html:47 +msgid "Filter" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_list.html:81 +#, python-format +msgid "Add %(name)s" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_list.html:143 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/submit_line.html:5 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/submit_line.html:7 +msgid "Save" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_list_results.html:13 +msgid "Remove from sorting" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_list_results.html:16 +#, python-format +msgid "Sorting priority: %(priority_number)s" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/change_list_results.html:17 +msgid "Toggle sorting" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/delete_confirmation.html:25 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/submit_line.html:34 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/submit_line.html:36 +#: venv/lib/python3.6/site-packages/django/forms/formsets.py:375 +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html:13 +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:38 +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/authorized-token-delete.html:7 +msgid "Delete" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/delete_confirmation.html:34 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting " +"related objects, but your account doesn't have permission to delete the " +"following types of objects:" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/delete_confirmation.html:48 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would require deleting the " +"following protected related objects:" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/delete_confirmation.html:62 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? " +"All of the following related items will be deleted:" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/delete_confirmation.html:70 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/delete_confirmation.html:72 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/delete_selected_confirmation.html:74 +msgid "Yes, I'm sure" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/delete_selected_confirmation.html:23 +msgid "Delete multiple objects" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/delete_selected_confirmation.html:32 +#, python-format +msgid "" +"Deleting the selected %(objects_name)s would result in deleting related " +"objects, but your account doesn't have permission to delete the following " +"types of objects:" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/delete_selected_confirmation.html:46 +#, python-format +msgid "" +"Deleting the selected %(objects_name)s would require deleting the following " +"protected related objects:" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/delete_selected_confirmation.html:60 +#, python-format +msgid "" +"Are you sure you want to delete the selected %(objects_name)s? All of the " +"following objects and their related items will be deleted:" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/edit_inline/tabular.html:22 +msgid "Delete?" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(filter_title)s " +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/includes/object_delete_summary.html:2 +msgid "Summary" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:18 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:21 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:22 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:31 +msgid "Apps" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:37 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:71 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/search_form.html:11 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/search_form.html:13 +msgid "Search" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:42 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/object_history.html:31 +msgid "Action" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:67 +msgid "View" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:69 +#: venv/lib/python3.6/site-packages/django/forms/widgets.py:397 +msgid "Change" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:84 +msgid "Recent actions" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:89 +msgid "None available" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/index.html:115 +msgid "Unknown content" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/invalid_setup.html:5 +msgid "" +"Something's wrong with your database installation. Make sure the appropriate " +"database tables have been created, and make sure the database is readable by " +"the appropriate user." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/login.html:21 +#, python-format +msgid "" +"You are authenticated as %(username)s, but are not authorized to access this " +"page. Would you like to login to a different account?" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/login.html:85 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_complete.html:23 +msgid "Log in" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/login.html:96 +msgid "Forgotten your password or username?" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/object_history.html:29 +msgid "Date/time" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/object_history.html:45 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/pagination.html:19 +msgid "Show all" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/popup_response.html:3 +msgid "Popup closing..." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/related_widget_wrapper.html:9 +#, python-format +msgid "Change selected %(model)s" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/related_widget_wrapper.html:16 +#, python-format +msgid "Add another %(model)s" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/related_widget_wrapper.html:23 +#, python-format +msgid "Delete selected %(model)s" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/search_form.html:4 +#, python-format +msgid "%(counter)s result" +msgid_plural "%(counter)s results" +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/search_form.html:4 +#, python-format +msgid "%(full_result_count)s total" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/submit_line.html:12 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/submit_line.html:14 +msgid "Save as new" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/submit_line.html:19 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/submit_line.html:21 +msgid "Save and add another" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/submit_line.html:26 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/admin/submit_line.html:28 +msgid "Save and continue editing" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/logged_out.html:18 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/logged_out.html:20 +msgid "Log in again" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_change_done.html:10 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_change_form.html:15 +msgid "Password change" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_change_done.html:20 +msgid "Your password was changed." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_change_form.html:37 +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_change_form.html:43 +msgid "Old password" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_change_form.html:66 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_confirm.html:46 +msgid "Change my password" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_complete.html:10 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_done.html:10 +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_form.html:10 +msgid "Password reset" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_complete.html:20 +msgid "Your password has been set. You may go ahead and log in now." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_confirm.html:10 +msgid "Password reset confirmation" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_confirm.html:24 +msgid "" +"Please enter your new password twice so we can verify you typed it in " +"correctly." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_confirm.html:30 +msgid "New password:" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_confirm.html:37 +msgid "Confirm password:" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_confirm.html:55 +msgid "" +"The password reset link was invalid, possibly because it has already been " +"used. Please request a new password reset." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_done.html:19 +msgid "" +"We've emailed you instructions for setting your password. You should be " +"receiving them shortly." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_done.html:21 +msgid "" +"If you don't receive an email, please make sure you've entered the address " +"you registered with, and check your spam folder." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_email.html:8 +msgid "Your username, in case you've forgotten:" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_form.html:23 +msgid "" +"Forgotten your password? Enter your email address below, and we'll email " +"instructions for setting a new one." +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_form.html:29 +msgid "Email address:" +msgstr "" + +#: venv/lib/python3.6/site-packages/bootstrap_admin/templates/registration/password_reset_form.html:38 +msgid "Reset my password" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/contrib/messages/apps.py:7 +msgid "Messages" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/contrib/sitemaps/apps.py:7 +msgid "Site Maps" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/contrib/staticfiles/apps.py:9 +msgid "Static Files" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/contrib/syndication/apps.py:7 +msgid "Syndication" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/paginator.py:45 +msgid "That page number is not an integer" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/paginator.py:47 +msgid "That page number is less than 1" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/paginator.py:52 +msgid "That page contains no results" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:31 +msgid "Enter a valid value." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:102 +#: venv/lib/python3.6/site-packages/django/forms/fields.py:658 +msgid "Enter a valid URL." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:154 +msgid "Enter a valid integer." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:165 +msgid "Enter a valid email address." +msgstr "" + +#. Translators: "letters" means latin letters: a-z and A-Z. +#: venv/lib/python3.6/site-packages/django/core/validators.py:239 +msgid "" +"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:246 +msgid "" +"Enter a valid 'slug' consisting of Unicode letters, numbers, underscores, or " +"hyphens." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:255 +#: venv/lib/python3.6/site-packages/django/core/validators.py:275 +msgid "Enter a valid IPv4 address." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:260 +#: venv/lib/python3.6/site-packages/django/core/validators.py:276 +msgid "Enter a valid IPv6 address." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:270 +#: venv/lib/python3.6/site-packages/django/core/validators.py:274 +msgid "Enter a valid IPv4 or IPv6 address." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:304 +msgid "Enter only digits separated by commas." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:310 +#, python-format +msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:342 +#, python-format +msgid "Ensure this value is less than or equal to %(limit_value)s." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:351 +#, python-format +msgid "Ensure this value is greater than or equal to %(limit_value)s." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:361 +#, python-format +msgid "" +"Ensure this value has at least %(limit_value)d character (it has " +"%(show_value)d)." +msgid_plural "" +"Ensure this value has at least %(limit_value)d characters (it has " +"%(show_value)d)." +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:376 +#, python-format +msgid "" +"Ensure this value has at most %(limit_value)d character (it has " +"%(show_value)d)." +msgid_plural "" +"Ensure this value has at most %(limit_value)d characters (it has " +"%(show_value)d)." +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:395 +#: venv/lib/python3.6/site-packages/django/forms/fields.py:290 +#: venv/lib/python3.6/site-packages/django/forms/fields.py:325 +msgid "Enter a number." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:397 +#, python-format +msgid "Ensure that there are no more than %(max)s digit in total." +msgid_plural "Ensure that there are no more than %(max)s digits in total." +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:402 +#, python-format +msgid "Ensure that there are no more than %(max)s decimal place." +msgid_plural "Ensure that there are no more than %(max)s decimal places." +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:407 +#, python-format +msgid "" +"Ensure that there are no more than %(max)s digit before the decimal point." +msgid_plural "" +"Ensure that there are no more than %(max)s digits before the decimal point." +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:469 +#, python-format +msgid "" +"File extension '%(extension)s' is not allowed. Allowed extensions are: " +"'%(allowed_extensions)s'." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/core/validators.py:521 +msgid "Null characters are not allowed." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/base.py:1162 +#: venv/lib/python3.6/site-packages/django/forms/models.py:756 +msgid "and" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/base.py:1164 +#, python-format +msgid "%(model_name)s with this %(field_labels)s already exists." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:104 +#, python-format +msgid "Value %(value)r is not a valid choice." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:105 +msgid "This field cannot be null." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:106 +msgid "This field cannot be blank." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:107 +#, python-format +msgid "%(model_name)s with this %(field_label)s already exists." +msgstr "" + +#. Translators: The 'lookup_type' is one of 'date', 'year' or 'month'. +#. Eg: "Title must be unique for pub_date year" +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:111 +#, python-format +msgid "" +"%(field_label)s must be unique for %(date_field_label)s %(lookup_type)s." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:128 +#, python-format +msgid "Field of type: %(field_type)s" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:899 +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1766 +msgid "Integer" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:903 +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1764 +#, python-format +msgid "'%(value)s' value must be an integer." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:978 +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1844 +msgid "Big (8 byte) integer" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:990 +#, python-format +msgid "'%(value)s' value must be either True or False." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:991 +#, python-format +msgid "'%(value)s' value must be either True, False, or None." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:993 +msgid "Boolean (Either True or False)" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1034 +#, python-format +msgid "String (up to %(max_length)s)" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1098 +msgid "Comma-separated integers" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1147 +#, python-format +msgid "" +"'%(value)s' value has an invalid date format. It must be in YYYY-MM-DD " +"format." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1149 +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1292 +#, python-format +msgid "" +"'%(value)s' value has the correct format (YYYY-MM-DD) but it is an invalid " +"date." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1152 +msgid "Date (without time)" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1290 +#, python-format +msgid "" +"'%(value)s' value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." +"uuuuuu]][TZ] format." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1294 +#, python-format +msgid "" +"'%(value)s' value has the correct format (YYYY-MM-DD HH:MM[:ss[.uuuuuu]]" +"[TZ]) but it is an invalid date/time." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1298 +msgid "Date (with time)" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1446 +#, python-format +msgid "'%(value)s' value must be a decimal number." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1448 +msgid "Decimal number" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1587 +#, python-format +msgid "" +"'%(value)s' value has an invalid format. It must be in [DD] [HH:[MM:]]ss[." +"uuuuuu] format." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1590 +msgid "Duration" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1640 +msgid "Email address" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1663 +msgid "File path" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1729 +#, python-format +msgid "'%(value)s' value must be a float." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1731 +msgid "Floating point number" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1860 +msgid "IPv4 address" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1971 +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1972 +#, python-format +msgid "'%(value)s' value must be either None, True or False." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1974 +msgid "Boolean (Either True, False or None)" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2009 +msgid "Positive integer" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2022 +msgid "Positive small integer" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2036 +#, python-format +msgid "Slug (up to %(max_length)s)" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2068 +msgid "Small integer" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2103 +#, python-format +msgid "" +"'%(value)s' value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " +"format." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2105 +#, python-format +msgid "" +"'%(value)s' value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an " +"invalid time." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2108 +msgid "Time" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2256 +msgid "Raw binary data" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2306 +#, python-format +msgid "'%(value)s' is not a valid UUID." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/__init__.py:2308 +msgid "Universally unique identifier" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/files.py:221 +msgid "File" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/related.py:778 +#, python-format +msgid "%(model)s instance with %(field)s %(value)r does not exist." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/related.py:780 +msgid "Foreign Key (type determined by related field)" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/related.py:1007 +msgid "One-to-one relationship" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/related.py:1057 +#, python-format +msgid "%(from)s-%(to)s relationship" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/related.py:1058 +#, python-format +msgid "%(from)s-%(to)s relationships" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/db/models/fields/related.py:1100 +msgid "Many-to-many relationship" +msgstr "" + +#. Translators: If found as last label character, these punctuation +#. characters will prevent the default label_suffix to be appended to the label +#: venv/lib/python3.6/site-packages/django/forms/boundfield.py:146 +msgid ":?.!" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:245 +msgid "Enter a whole number." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:396 +#: venv/lib/python3.6/site-packages/django/forms/fields.py:1126 +msgid "Enter a valid date." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:420 +#: venv/lib/python3.6/site-packages/django/forms/fields.py:1127 +msgid "Enter a valid time." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:442 +msgid "Enter a valid date/time." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:471 +msgid "Enter a valid duration." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:472 +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:532 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:533 +msgid "No file was submitted." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:534 +msgid "The submitted file is empty." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:536 +#, python-format +msgid "Ensure this filename has at most %(max)d character (it has %(length)d)." +msgid_plural "" +"Ensure this filename has at most %(max)d characters (it has %(length)d)." +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:539 +msgid "Please either submit a file or check the clear checkbox, not both." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:600 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:762 +#: venv/lib/python3.6/site-packages/django/forms/fields.py:852 +#: venv/lib/python3.6/site-packages/django/forms/models.py:1270 +#, python-format +msgid "Select a valid choice. %(value)s is not one of the available choices." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:853 +#: venv/lib/python3.6/site-packages/django/forms/fields.py:968 +#: venv/lib/python3.6/site-packages/django/forms/models.py:1269 +msgid "Enter a list of values." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:969 +msgid "Enter a complete value." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/fields.py:1185 +msgid "Enter a valid UUID." +msgstr "" + +#. Translators: This is the default suffix added to form field labels +#: venv/lib/python3.6/site-packages/django/forms/forms.py:86 +msgid ":" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/forms.py:212 +#, python-format +msgid "(Hidden field %(name)s) %(error)s" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/formsets.py:91 +msgid "ManagementForm data is missing or has been tampered with" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/formsets.py:338 +#, python-format +msgid "Please submit %d or fewer forms." +msgid_plural "Please submit %d or fewer forms." +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/forms/formsets.py:345 +#, python-format +msgid "Please submit %d or more forms." +msgid_plural "Please submit %d or more forms." +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/forms/formsets.py:371 +#: venv/lib/python3.6/site-packages/django/forms/formsets.py:373 +msgid "Order" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/models.py:751 +#, python-format +msgid "Please correct the duplicate data for %(field)s." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/models.py:755 +#, python-format +msgid "Please correct the duplicate data for %(field)s, which must be unique." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/models.py:761 +#, python-format +msgid "" +"Please correct the duplicate data for %(field_name)s which must be unique " +"for the %(lookup)s in %(date_field)s." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/models.py:770 +msgid "Please correct the duplicate values below." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/models.py:1091 +msgid "The inline value did not match the parent instance." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/models.py:1158 +msgid "Select a valid choice. That choice is not one of the available choices." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/models.py:1272 +#, python-format +msgid "\"%(pk)s\" is not a valid value." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/utils.py:162 +#, python-format +msgid "" +"%(datetime)s couldn't be interpreted in time zone %(current_timezone)s; it " +"may be ambiguous or it may not exist." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/widgets.py:395 +msgid "Clear" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/widgets.py:396 +msgid "Currently" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/widgets.py:711 +msgid "Unknown" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/widgets.py:712 +msgid "Yes" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/forms/widgets.py:713 +msgid "No" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/template/defaultfilters.py:788 +msgid "yes,no,maybe" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/template/defaultfilters.py:817 +#: venv/lib/python3.6/site-packages/django/template/defaultfilters.py:834 +#, python-format +msgid "%(size)d byte" +msgid_plural "%(size)d bytes" +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/template/defaultfilters.py:836 +#, python-format +msgid "%s KB" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/template/defaultfilters.py:838 +#, python-format +msgid "%s MB" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/template/defaultfilters.py:840 +#, python-format +msgid "%s GB" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/template/defaultfilters.py:842 +#, python-format +msgid "%s TB" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/template/defaultfilters.py:844 +#, python-format +msgid "%s PB" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dateformat.py:62 +msgid "p.m." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dateformat.py:63 +msgid "a.m." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dateformat.py:68 +msgid "PM" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dateformat.py:69 +msgid "AM" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dateformat.py:150 +msgid "midnight" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dateformat.py:152 +msgid "noon" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:10 +msgid "Mon" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:10 +msgid "Tue" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:10 +msgid "Wed" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:10 +msgid "Thu" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:10 +msgid "Fri" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:11 +msgid "Sat" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:11 +msgid "Sun" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:14 +msgid "January" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:14 +msgid "February" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:14 +msgid "March" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:14 +msgid "April" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:14 +msgid "May" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:14 +msgid "June" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:15 +msgid "July" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:15 +msgid "August" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:15 +msgid "September" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:15 +msgid "October" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:15 +msgid "November" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:16 +msgid "December" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:19 +msgid "jan" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:19 +msgid "feb" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:19 +msgid "mar" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:19 +msgid "apr" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:19 +msgid "may" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:19 +msgid "jun" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:20 +msgid "jul" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:20 +msgid "aug" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:20 +msgid "sep" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:20 +msgid "oct" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:20 +msgid "nov" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:20 +msgid "dec" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:23 +msgctxt "abbrev. month" +msgid "Jan." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:24 +msgctxt "abbrev. month" +msgid "Feb." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:25 +msgctxt "abbrev. month" +msgid "March" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:26 +msgctxt "abbrev. month" +msgid "April" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:27 +msgctxt "abbrev. month" +msgid "May" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:28 +msgctxt "abbrev. month" +msgid "June" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:29 +msgctxt "abbrev. month" +msgid "July" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:30 +msgctxt "abbrev. month" +msgid "Aug." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:31 +msgctxt "abbrev. month" +msgid "Sept." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:32 +msgctxt "abbrev. month" +msgid "Oct." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:33 +msgctxt "abbrev. month" +msgid "Nov." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:34 +msgctxt "abbrev. month" +msgid "Dec." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:37 +msgctxt "alt. month" +msgid "January" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:38 +msgctxt "alt. month" +msgid "February" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:39 +msgctxt "alt. month" +msgid "March" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:40 +msgctxt "alt. month" +msgid "April" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:41 +msgctxt "alt. month" +msgid "May" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:42 +msgctxt "alt. month" +msgid "June" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:43 +msgctxt "alt. month" +msgid "July" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:44 +msgctxt "alt. month" +msgid "August" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:45 +msgctxt "alt. month" +msgid "September" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:46 +msgctxt "alt. month" +msgid "October" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:47 +msgctxt "alt. month" +msgid "November" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/dates.py:48 +msgctxt "alt. month" +msgid "December" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/ipv6.py:8 +msgid "This is not a valid IPv6 address." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/text.py:67 +#, python-format +msgctxt "String to return when truncating text" +msgid "%(truncated_text)s…" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/text.py:233 +msgid "or" +msgstr "" + +#. Translators: This string is used as a separator between list elements +#: venv/lib/python3.6/site-packages/django/utils/text.py:252 +#: venv/lib/python3.6/site-packages/django/utils/timesince.py:83 +msgid ", " +msgstr "" + +#: venv/lib/python3.6/site-packages/django/utils/timesince.py:9 +#, python-format +msgid "%d year" +msgid_plural "%d years" +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/utils/timesince.py:10 +#, python-format +msgid "%d month" +msgid_plural "%d months" +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/utils/timesince.py:11 +#, python-format +msgid "%d week" +msgid_plural "%d weeks" +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/utils/timesince.py:12 +#, python-format +msgid "%d day" +msgid_plural "%d days" +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/utils/timesince.py:13 +#, python-format +msgid "%d hour" +msgid_plural "%d hours" +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/utils/timesince.py:14 +#, python-format +msgid "%d minute" +msgid_plural "%d minutes" +msgstr[0] "" +msgstr[1] "" + +#: venv/lib/python3.6/site-packages/django/utils/timesince.py:72 +msgid "0 minutes" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/csrf.py:110 +msgid "Forbidden" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/csrf.py:111 +msgid "CSRF verification failed. Request aborted." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/csrf.py:115 +msgid "" +"You are seeing this message because this HTTPS site requires a 'Referer " +"header' to be sent by your Web browser, but none was sent. This header is " +"required for security reasons, to ensure that your browser is not being " +"hijacked by third parties." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/csrf.py:120 +msgid "" +"If you have configured your browser to disable 'Referer' headers, please re-" +"enable them, at least for this site, or for HTTPS connections, or for 'same-" +"origin' requests." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/csrf.py:124 +msgid "" +"If you are using the tag or " +"including the 'Referrer-Policy: no-referrer' header, please remove them. The " +"CSRF protection requires the 'Referer' header to do strict referer checking. " +"If you're concerned about privacy, use alternatives like for links to third-party sites." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/csrf.py:132 +msgid "" +"You are seeing this message because this site requires a CSRF cookie when " +"submitting forms. This cookie is required for security reasons, to ensure " +"that your browser is not being hijacked by third parties." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/csrf.py:137 +msgid "" +"If you have configured your browser to disable cookies, please re-enable " +"them, at least for this site, or for 'same-origin' requests." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/csrf.py:142 +msgid "More information is available with DEBUG=True." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/generic/dates.py:41 +msgid "No year specified" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/generic/dates.py:61 +#: venv/lib/python3.6/site-packages/django/views/generic/dates.py:111 +#: venv/lib/python3.6/site-packages/django/views/generic/dates.py:208 +msgid "Date out of range" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/generic/dates.py:90 +msgid "No month specified" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/generic/dates.py:142 +msgid "No day specified" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/generic/dates.py:188 +msgid "No week specified" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/generic/dates.py:338 +#: venv/lib/python3.6/site-packages/django/views/generic/dates.py:367 +#, python-format +msgid "No %(verbose_name_plural)s available" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/generic/dates.py:589 +#, python-format +msgid "" +"Future %(verbose_name_plural)s not available because %(class_name)s." +"allow_future is False." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/generic/dates.py:623 +#, python-format +msgid "Invalid date string '%(datestr)s' given format '%(format)s'" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/generic/detail.py:54 +#, python-format +msgid "No %(verbose_name)s found matching the query" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/generic/list.py:67 +msgid "Page is not 'last', nor can it be converted to an int." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/generic/list.py:72 +#, python-format +msgid "Invalid page (%(page_number)s): %(message)s" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/generic/list.py:154 +#, python-format +msgid "Empty list and '%(class_name)s.allow_empty' is False." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/static.py:40 +msgid "Directory indexes are not allowed here." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/static.py:42 +#, python-format +msgid "\"%(path)s\" does not exist" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/static.py:80 +#, python-format +msgid "Index of %(directory)s" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/templates/default_urlconf.html:6 +msgid "Django: the Web framework for perfectionists with deadlines." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/templates/default_urlconf.html:345 +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/templates/default_urlconf.html:367 +msgid "The install worked successfully! Congratulations!" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/templates/default_urlconf.html:368 +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not configured any " +"URLs." +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/templates/default_urlconf.html:383 +msgid "Django Documentation" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/templates/default_urlconf.html:384 +msgid "Topics, references, & how-to's" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/templates/default_urlconf.html:395 +msgid "Tutorial: A Polling App" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/templates/default_urlconf.html:396 +msgid "Get started with Django" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/templates/default_urlconf.html:407 +msgid "Django Community" +msgstr "" + +#: venv/lib/python3.6/site-packages/django/views/templates/default_urlconf.html:408 +msgid "Connect, get help, or contribute" +msgstr "" + +#: venv/lib/python3.6/site-packages/easy_select2/forms.py:7 +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/admin.py:64 +#, python-format +msgid "Some messages were sent: %s" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/admin.py:66 +#, python-format +msgid "All messages were sent: %s" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/admin.py:72 +#, python-format +msgid "Some messages failed to send. %d devices were marked as inactive." +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/admin.py:80 +msgid "Send test notification" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/admin.py:85 +msgid "Send test notification in bulk" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/admin.py:90 +msgid "Send test data message" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/admin.py:96 +msgid "Send test data message in bulk" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/admin.py:101 +msgid "Enable selected devices" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/admin.py:106 +msgid "Disable selected devices" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/apps.py:7 +msgid "FCM Django" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/fields.py:52 +msgid "Enter a valid hexadecimal number" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/models.py:19 +msgid "Is active" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/models.py:20 +msgid "Inactive devices will not be sent notifications" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/models.py:25 +msgid "Creation date" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/models.py:161 +msgid "Device ID" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/models.py:162 +msgid "Unique device identifier" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/models.py:165 +msgid "Registration token" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/models.py:171 +#: venv/lib/python3.6/site-packages/fcm_django/models.py:255 +msgid "FCM device" +msgstr "" + +#: venv/lib/python3.6/site-packages/fcm_django/models.py:256 +msgid "FCM devices" +msgstr "" + +#: venv/lib/python3.6/site-packages/kombu/transport/qpid.py:1301 +#, python-format +msgid "Attempting to connect to qpid with SASL mechanism %s" +msgstr "" + +#: venv/lib/python3.6/site-packages/kombu/transport/qpid.py:1306 +#, python-format +msgid "Connected to qpid with SASL mechanism %s" +msgstr "" + +#: venv/lib/python3.6/site-packages/kombu/transport/qpid.py:1324 +#, python-format +msgid "Unable to connect to qpid with SASL mechanism %s" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/models.py:41 +msgid "Confidential" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/models.py:42 +msgid "Public" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/models.py:50 +msgid "Authorization code" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/models.py:51 +msgid "Implicit" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/models.py:52 +msgid "Resource owner password-based" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/models.py:53 +msgid "Client credentials" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/models.py:67 +msgid "Allowed URIs list, space separated" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/models.py:143 +#, python-brace-format +msgid "Unauthorized redirect scheme: {scheme}" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/models.py:148 +#, python-brace-format +msgid "redirect_uris cannot be empty with grant_type {grant_type}" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/oauth2_validators.py:166 +msgid "The access token is invalid." +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/oauth2_validators.py:171 +msgid "The access token has expired." +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/oauth2_validators.py:176 +msgid "The access token is valid but does not have enough scope." +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html:6 +msgid "Are you sure to delete the application" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html:12 +msgid "Cancel" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:10 +msgid "Client id" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:15 +msgid "Client secret" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:20 +msgid "Client type" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:25 +msgid "Authorization Grant Type" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:30 +msgid "Redirect Uris" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:36 +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_form.html:35 +msgid "Go Back" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:37 +msgid "Edit" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_form.html:9 +msgid "Edit application" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:6 +msgid "Your applications" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:16 +msgid "No applications defined" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:16 +msgid "Click here" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:16 +msgid "if you want to register a new one" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/application_registration_form.html:5 +msgid "Register a new application" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:8 +msgid "Authorize" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:17 +msgid "Application requires following permissions" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/authorized-token-delete.html:6 +msgid "Are you sure you want to delete this token?" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/authorized-tokens.html:6 +msgid "Tokens" +msgstr "" + +#: venv/lib/python3.6/site-packages/oauth2_provider/templates/oauth2_provider/authorized-tokens.html:19 +msgid "There are no authorized tokens yet." +msgstr "" + +#: venv/lib/python3.6/site-packages/solo/admin.py:53 +#, python-format +msgid "%(obj)s was changed successfully." +msgstr "" + +#: venv/lib/python3.6/site-packages/solo/admin.py:55 +msgid "You may edit it again below." +msgstr "" + +#: venv/lib/python3.6/site-packages/solo/templatetags/solo_tags.py:22 +#, python-format +msgid "" +"Templatetag requires the model dotted path: 'app_label.ModelName'. Received " +"'%s'." +msgstr "" + +#: venv/lib/python3.6/site-packages/solo/templatetags/solo_tags.py:28 +#, python-format +msgid "" +"Could not get the model name '%(model)s' from the application named '%(app)s'" +msgstr "" From f3389067360b70aea196c305b23048ca3b2aed0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Mon, 21 Oct 2019 11:44:47 +0300 Subject: [PATCH 102/223] Fix location tests --- apps/location/models.py | 4 ++++ apps/location/tests.py | 25 +++++++++++++++++++++++++ apps/location/views/back.py | 4 +++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/apps/location/models.py b/apps/location/models.py index 7f797811..2b7aa363 100644 --- a/apps/location/models.py +++ b/apps/location/models.py @@ -21,6 +21,10 @@ class Country(TranslatedFieldsMixin, SVGImageMixin, ProjectBaseMixin): high_price = models.IntegerField(default=50, verbose_name=_('High price')) languages = models.ManyToManyField(Language, verbose_name=_('Languages')) + @property + def country_id(self): + return self.id + class Meta: """Meta class.""" diff --git a/apps/location/tests.py b/apps/location/tests.py index 0770227a..edb719bd 100644 --- a/apps/location/tests.py +++ b/apps/location/tests.py @@ -59,15 +59,25 @@ class CountryTests(BaseTestCase): def setUp(self): super().setUp() + def test_country_CRUD(self): data = { 'name': {"ru-RU":"Russia"}, 'code': 'test' } + response = self.client.post('/api/back/location/countries/', data=data, format='json') response_data = response.json() self.assertEqual(response.status_code, status.HTTP_201_CREATED) + country = Country.objects.get(pk=response_data["id"]) + role = Role.objects.create(role=Role.COUNTRY_ADMIN, country=country) + role.save() + + user_role = UserRole.objects.create(user=self.user, role=role) + + user_role.save() + response = self.client.get('/api/back/location/countries/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -94,6 +104,14 @@ class RegionTests(BaseTestCase): code="test" ) + role = Role.objects.create(role=Role.COUNTRY_ADMIN, country=self.country) + role.save() + + user_role = UserRole.objects.create(user=self.user, role=role) + + user_role.save() + + def test_region_CRUD(self): response = self.client.get('/api/back/location/regions/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -138,6 +156,13 @@ class CityTests(BaseTestCase): country=self.country ) + role = Role.objects.create(role=Role.COUNTRY_ADMIN, country=self.country) + role.save() + + user_role = UserRole.objects.create(user=self.user, role=role) + + user_role.save() + def test_city_CRUD(self): response = self.client.get('/api/back/location/cities/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/apps/location/views/back.py b/apps/location/views/back.py index 3a2739b2..cb8246a4 100644 --- a/apps/location/views/back.py +++ b/apps/location/views/back.py @@ -47,6 +47,7 @@ class RegionRUDView(common.RegionViewMixin, generics.RetrieveUpdateDestroyAPIVie # Country class CountryListCreateView(generics.ListCreateAPIView): """List/Create view for model Country.""" + queryset = models.Country.objects.all() serializer_class = serializers.CountryBackSerializer pagination_class = None permission_classes = [IsCountryAdmin] @@ -54,4 +55,5 @@ class CountryListCreateView(generics.ListCreateAPIView): class CountryRUDView(generics.RetrieveUpdateDestroyAPIView): """RUD view for model Country.""" serializer_class = serializers.CountryBackSerializer - permission_classes = [IsCountryAdmin] \ No newline at end of file + permission_classes = [IsCountryAdmin] + queryset = models.Country.objects.all() \ No newline at end of file From 3720ac2b8e07e07c49d6623534c556dd2e435784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Mon, 21 Oct 2019 12:17:12 +0300 Subject: [PATCH 103/223] Fix collection test --- apps/collection/tests.py | 18 +++++++++++------- sdtout.txt | 4 ++++ 2 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 sdtout.txt diff --git a/apps/collection/tests.py b/apps/collection/tests.py index 72b40c37..b2b8231b 100644 --- a/apps/collection/tests.py +++ b/apps/collection/tests.py @@ -40,12 +40,13 @@ class CollectionDetailTests(BaseTestCase): def setUp(self): super().setUp() - country = Country.objects.first() - if not country: - country = Country.objects.create( - name=json.dumps({"en-GB": "Test country"}), - code="en" - ) + # country = Country.objects.first() + # if not country: + country = Country.objects.create( + name=json.dumps({"en-GB": "Test country"}), + code="en" + ) + country.save() self.collection = Collection.objects.create( name='Test collection', @@ -56,6 +57,8 @@ class CollectionDetailTests(BaseTestCase): slug='test-collection-slug', ) + self.collection.save() + def test_collection_detail_Read(self): response = self.client.get(f'/api/web/collections/{self.collection.slug}/establishments/?country_code=en', format='json') @@ -66,7 +69,7 @@ class CollectionGuideTests(CollectionDetailTests): def test_guide_list_Read(self): response = self.client.get('/api/web/collections/guides/', format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) class CollectionGuideDetailTests(CollectionDetailTests): @@ -78,6 +81,7 @@ class CollectionGuideDetailTests(CollectionDetailTests): start=datetime.now(pytz.utc), end=datetime.now(pytz.utc) ) + self.guide.save() def test_guide_detail_Read(self): response = self.client.get(f'/api/web/collections/guides/{self.guide.id}/', format='json') diff --git a/sdtout.txt b/sdtout.txt new file mode 100644 index 00000000..5f575e2c --- /dev/null +++ b/sdtout.txt @@ -0,0 +1,4 @@ +System check identified no issues (0 silenced). +{'count': 1, 'next': None, 'previous': None, 'results': [{'id': 28, 'name': 'test establishment', 'name_translated': '', 'price_level': None, 'toque_number': None, 'public_mark': None, 'slug': None, 'preview_image': None, 'in_favorites': None, 'address': None, 'tags': []}]} +1 +{'id': 1, 'title_translated': 'test title', 'subtitle_translated': None, 'author': 'Test Author', 'published_at': None, 'in_favorites': False, 'description_translated': 'test description'} From 6dd66275ef3520d61a8f0c0ecc2fc200db325843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Mon, 21 Oct 2019 12:18:57 +0300 Subject: [PATCH 104/223] Fix trash --- sdtout.txt | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 sdtout.txt diff --git a/sdtout.txt b/sdtout.txt deleted file mode 100644 index 5f575e2c..00000000 --- a/sdtout.txt +++ /dev/null @@ -1,4 +0,0 @@ -System check identified no issues (0 silenced). -{'count': 1, 'next': None, 'previous': None, 'results': [{'id': 28, 'name': 'test establishment', 'name_translated': '', 'price_level': None, 'toque_number': None, 'public_mark': None, 'slug': None, 'preview_image': None, 'in_favorites': None, 'address': None, 'tags': []}]} -1 -{'id': 1, 'title_translated': 'test title', 'subtitle_translated': None, 'author': 'Test Author', 'published_at': None, 'in_favorites': False, 'description_translated': 'test description'} From 71ec593ecf26db24722a06f865727bd2e1d20c76 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 21 Oct 2019 14:09:08 +0300 Subject: [PATCH 105/223] 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 019c22a0c886461878ee8debfcf942ebbad68778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Mon, 21 Oct 2019 14:39:04 +0300 Subject: [PATCH 106/223] Fix test --- apps/news/serializers.py | 2 +- apps/news/views.py | 1 + apps/utils/tests/tests_translated.py | 7 +++++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index c473be1d..f2e307e4 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -22,7 +22,7 @@ class NewsBaseSerializer(ProjectModelSerializer): """Base serializer for News model.""" # read only fields - title_translated = TranslatedField() + title_translated = TranslatedField(source='title') subtitle_translated = TranslatedField() # related fields diff --git a/apps/news/views.py b/apps/news/views.py index 105b9064..7e272657 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -35,6 +35,7 @@ class NewsDetailView(NewsMixinView, generics.RetrieveAPIView): """Override get_queryset method.""" return super().get_queryset().with_extended_related() + class NewsTypeListView(generics.ListAPIView): """NewsType list view.""" diff --git a/apps/utils/tests/tests_translated.py b/apps/utils/tests/tests_translated.py index 8f5caaaf..c6a990c0 100644 --- a/apps/utils/tests/tests_translated.py +++ b/apps/utils/tests/tests_translated.py @@ -37,6 +37,7 @@ class TranslateFieldTests(BaseTestCase): super().setUp() self.news_type = NewsType.objects.create(name="Test news type") + self.news_type.save() self.news_item = News.objects.create( created_by=self.user, @@ -53,9 +54,11 @@ class TranslateFieldTests(BaseTestCase): slug='test', state=News.PUBLISHED, ) + self.news_item.save() def test_model_field(self): - self.assertIsNotNone(getattr(self.news_item, "title_translated", None)) + self.assertTrue(hasattr(self.news_item, "title_translated")) + def test_read_locale(self): response = self.client.get(f"/api/web/news/slug/{self.news_item.slug}/", format='json') @@ -64,7 +67,7 @@ class TranslateFieldTests(BaseTestCase): self.assertIn("title_translated", news_data) - self.assertEqual(news_data['title_translated'], "Test news item") + self.assertIn("Test news item", news_data['title_translated']) class BaseAttributeTests(BaseTestCase): From cc5249674d4a3c0696f258aad5e3b07651b54c47 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Mon, 21 Oct 2019 14:39:23 +0300 Subject: [PATCH 107/223] 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 ee93cf9ff6771c7f4976456f767dce2d113a7f26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Mon, 21 Oct 2019 15:15:19 +0300 Subject: [PATCH 108/223] Fix migrate --- apps/location/migrations/0012_data_migrate.py | 26 +- apps/location/migrations/migrate_lang.sql | 5 +- .../migrations/0005_auto_20191021_1201.py | 22 + .../migrations/006_data_migrate.py | 25 ++ apps/translation/migrations/migrate_lang.sql | 383 +++++++++++++++++ .../translation/migrations/remigrate_lang.sql | 391 ++++++++++++++++++ apps/translation/models.py | 3 +- project/urls/back.py | 2 - 8 files changed, 839 insertions(+), 18 deletions(-) create mode 100644 apps/translation/migrations/0005_auto_20191021_1201.py create mode 100644 apps/translation/migrations/006_data_migrate.py create mode 100644 apps/translation/migrations/migrate_lang.sql create mode 100644 apps/translation/migrations/remigrate_lang.sql diff --git a/apps/location/migrations/0012_data_migrate.py b/apps/location/migrations/0012_data_migrate.py index 511990db..de75c8ad 100644 --- a/apps/location/migrations/0012_data_migrate.py +++ b/apps/location/migrations/0012_data_migrate.py @@ -3,23 +3,23 @@ import os class Migration(migrations.Migration): - - def load_data_from_sql(apps, schema_editor): - file_path = os.path.join(os.path.dirname(__file__), 'migrate_lang.sql') - sql_statement = open(file_path).read() - with connection.cursor() as c: - c.execute(sql_statement) - - def revert_data(apps, schema_editor): - file_path = os.path.join(os.path.dirname(__file__), 'remigrate_lang.sql') - sql_statement = open(file_path).read() - with connection.cursor() as c: - c.execute(sql_statement) + # Check migration + # def load_data_from_sql(apps, schema_editor): + # file_path = os.path.join(os.path.dirname(__file__), 'migrate_lang.sql') + # sql_statement = open(file_path).read() + # with connection.cursor() as c: + # c.execute(sql_statement) + # + # def revert_data(apps, schema_editor): + # file_path = os.path.join(os.path.dirname(__file__), 'remigrate_lang.sql') + # sql_statement = open(file_path).read() + # with connection.cursor() as c: + # c.execute(sql_statement) dependencies = [ ('location', '0011_country_languages'), ] operations = [ - migrations.RunPython(load_data_from_sql, revert_data), + # migrations.RunPython(load_data_from_sql, revert_data), ] diff --git a/apps/location/migrations/migrate_lang.sql b/apps/location/migrations/migrate_lang.sql index 11c93573..9f15e9c6 100644 --- a/apps/location/migrations/migrate_lang.sql +++ b/apps/location/migrations/migrate_lang.sql @@ -326,7 +326,7 @@ commit; INSERT INTO location_country (code, "name", low_price, high_price, created, modified) -select +select distinct lpad((row_number() over (order by t.country asc))::text, 3, '0') as code, jsonb_build_object('en-GB', t.country), 0 as low_price, @@ -335,7 +335,7 @@ select now() as modified from ( - select + select distinct c.country from country_code c ) t @@ -348,6 +348,7 @@ commit; INSERT INTO translation_language (title, locale) select + distinct t.country as title, t.code as locale from diff --git a/apps/translation/migrations/0005_auto_20191021_1201.py b/apps/translation/migrations/0005_auto_20191021_1201.py new file mode 100644 index 00000000..61cc3294 --- /dev/null +++ b/apps/translation/migrations/0005_auto_20191021_1201.py @@ -0,0 +1,22 @@ +# Generated by Django 2.2.4 on 2019-10-21 12:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('translation', '0004_auto_20191018_0832'), + ] + + operations = [ + migrations.AlterField( + model_name='language', + name='locale', + field=models.CharField(max_length=10, verbose_name='Locale identifier'), + ), + migrations.AlterUniqueTogether( + name='language', + unique_together={('title', 'locale')}, + ), + ] diff --git a/apps/translation/migrations/006_data_migrate.py b/apps/translation/migrations/006_data_migrate.py new file mode 100644 index 00000000..d8142773 --- /dev/null +++ b/apps/translation/migrations/006_data_migrate.py @@ -0,0 +1,25 @@ +from django.db import migrations, connection +import os + + +class Migration(migrations.Migration): + + def load_data_from_sql(apps, schema_editor): + file_path = os.path.join(os.path.dirname(__file__), 'migrate_lang.sql') + sql_statement = open(file_path).read() + with connection.cursor() as c: + c.execute(sql_statement) + + def revert_data(apps, schema_editor): + file_path = os.path.join(os.path.dirname(__file__), 'remigrate_lang.sql') + sql_statement = open(file_path).read() + with connection.cursor() as c: + c.execute(sql_statement) + + dependencies = [ + ('translation', '0005_auto_20191021_1201'), + ] + + operations = [ + migrations.RunPython(load_data_from_sql, revert_data), + ] diff --git a/apps/translation/migrations/migrate_lang.sql b/apps/translation/migrations/migrate_lang.sql new file mode 100644 index 00000000..9f15e9c6 --- /dev/null +++ b/apps/translation/migrations/migrate_lang.sql @@ -0,0 +1,383 @@ +SET search_path TO gm, public; + +CREATE TABLE codelang ( + code varchar(100) NULL, + country varchar(10000) NULL +); + + +INSERT INTO codelang (code,country) VALUES +('af','Afrikaans') +,('af-ZA','Afrikaans (South Africa)') +,('ar','Arabic') +,('ar-AE','Arabic (U.A.E.)') +,('ar-BH','Arabic (Bahrain)') +,('ar-DZ','Arabic (Algeria)') +,('ar-EG','Arabic (Egypt)') +,('ar-IQ','Arabic (Iraq)') +,('ar-JO','Arabic (Jordan)') +,('ar-KW','Arabic (Kuwait)') +; +INSERT INTO codelang (code,country) VALUES +('ar-LB','Arabic (Lebanon)') +,('ar-LY','Arabic (Libya)') +,('ar-MA','Arabic (Morocco)') +,('ar-OM','Arabic (Oman)') +,('ar-QA','Arabic (Qatar)') +,('ar-SA','Arabic (Saudi Arabia)') +,('ar-SY','Arabic (Syria)') +,('ar-TN','Arabic (Tunisia)') +,('ar-YE','Arabic (Yemen)') +,('az','Azeri (Latin)') +; +INSERT INTO codelang (code,country) VALUES +('az-AZ','Azeri (Latin) (Azerbaijan)') +,('az-AZ','Azeri (Cyrillic) (Azerbaijan)') +,('be','Belarusian') +,('be-BY','Belarusian (Belarus)') +,('bg','Bulgarian') +,('bg-BG','Bulgarian (Bulgaria)') +,('bs-BA','Bosnian (Bosnia and Herzegovina)') +,('ca','Catalan') +,('ca-ES','Catalan (Spain)') +,('cs','Czech') +; +INSERT INTO codelang (code,country) VALUES +('cs-CZ','Czech (Czech Republic)') +,('cy','Welsh') +,('cy-GB','Welsh (United Kingdom)') +,('da','Danish') +,('da-DK','Danish (Denmark)') +,('de','German') +,('de-AT','German (Austria)') +,('de-CH','German (Switzerland)') +,('de-DE','German (Germany)') +,('de-LI','German (Liechtenstein)') +; +INSERT INTO codelang (code,country) VALUES +('de-LU','German (Luxembourg)') +,('dv','Divehi') +,('dv-MV','Divehi (Maldives)') +,('el','Greek') +,('el-GR','Greek (Greece)') +,('en','English') +,('en-AU','English (Australia)') +,('en-BZ','English (Belize)') +,('en-CA','English (Canada)') +,('en-CB','English (Caribbean)') +; +INSERT INTO codelang (code,country) VALUES +('en-GB','English (United Kingdom)') +,('en-IE','English (Ireland)') +,('en-JM','English (Jamaica)') +,('en-NZ','English (New Zealand)') +,('en-PH','English (Republic of the Philippines)') +,('en-TT','English (Trinidad and Tobago)') +,('en-US','English (United States)') +,('en-ZA','English (South Africa)') +,('en-ZW','English (Zimbabwe)') +,('eo','Esperanto') +; +INSERT INTO codelang (code,country) VALUES +('es','Spanish') +,('es-AR','Spanish (Argentina)') +,('es-BO','Spanish (Bolivia)') +,('es-CL','Spanish (Chile)') +,('es-CO','Spanish (Colombia)') +,('es-CR','Spanish (Costa Rica)') +,('es-DO','Spanish (Dominican Republic)') +,('es-EC','Spanish (Ecuador)') +,('es-ES','Spanish (Castilian)') +,('es-ES','Spanish (Spain)') +; +INSERT INTO codelang (code,country) VALUES +('es-GT','Spanish (Guatemala)') +,('es-HN','Spanish (Honduras)') +,('es-MX','Spanish (Mexico)') +,('es-NI','Spanish (Nicaragua)') +,('es-PA','Spanish (Panama)') +,('es-PE','Spanish (Peru)') +,('es-PR','Spanish (Puerto Rico)') +,('es-PY','Spanish (Paraguay)') +,('es-SV','Spanish (El Salvador)') +,('es-UY','Spanish (Uruguay)') +; +INSERT INTO codelang (code,country) VALUES +('es-VE','Spanish (Venezuela)') +,('et','Estonian') +,('et-EE','Estonian (Estonia)') +,('eu','Basque') +,('eu-ES','Basque (Spain)') +,('fa','Farsi') +,('fa-IR','Farsi (Iran)') +,('fi','Finnish') +,('fi-FI','Finnish (Finland)') +,('fo','Faroese') +; +INSERT INTO codelang (code,country) VALUES +('fo-FO','Faroese (Faroe Islands)') +,('fr','French') +,('fr-BE','French (Belgium)') +,('fr-CA','French (Canada)') +,('fr-CH','French (Switzerland)') +,('fr-FR','French (France)') +,('fr-LU','French (Luxembourg)') +,('fr-MC','French (Principality of Monaco)') +,('gl','Galician') +,('gl-ES','Galician (Spain)') +; +INSERT INTO codelang (code,country) VALUES +('gu','Gujarati') +,('gu-IN','Gujarati (India)') +,('he','Hebrew') +,('he-IL','Hebrew (Israel)') +,('hi','Hindi') +,('hi-IN','Hindi (India)') +,('hr','Croatian') +,('hr-BA','Croatian (Bosnia and Herzegovina)') +,('hr-HR','Croatian (Croatia)') +,('hu','Hungarian') +; +INSERT INTO codelang (code,country) VALUES +('hu-HU','Hungarian (Hungary)') +,('hy','Armenian') +,('hy-AM','Armenian (Armenia)') +,('id','Indonesian') +,('id-ID','Indonesian (Indonesia)') +,('is','Icelandic') +,('is-IS','Icelandic (Iceland)') +,('it','Italian') +,('it-CH','Italian (Switzerland)') +,('it-IT','Italian (Italy)') +; +INSERT INTO codelang (code,country) VALUES +('ja','Japanese') +,('ja-JP','Japanese (Japan)') +,('ka','Georgian') +,('ka-GE','Georgian (Georgia)') +,('kk','Kazakh') +,('kk-KZ','Kazakh (Kazakhstan)') +,('kn','Kannada') +,('kn-IN','Kannada (India)') +,('ko','Korean') +,('ko-KR','Korean (Korea)') +; +INSERT INTO codelang (code,country) VALUES +('kok','Konkani') +,('kok-IN','Konkani (India)') +,('ky','Kyrgyz') +,('ky-KG','Kyrgyz (Kyrgyzstan)') +,('lt','Lithuanian') +,('lt-LT','Lithuanian (Lithuania)') +,('lv','Latvian') +,('lv-LV','Latvian (Latvia)') +,('mi','Maori') +,('mi-NZ','Maori (New Zealand)') +; +INSERT INTO codelang (code,country) VALUES +('mk','FYRO Macedonian') +,('mk-MK','FYRO Macedonian (Former Yugoslav Republic of Macedonia)') +,('mn','Mongolian') +,('mn-MN','Mongolian (Mongolia)') +,('mr','Marathi') +,('mr-IN','Marathi (India)') +,('ms','Malay') +,('ms-BN','Malay (Brunei Darussalam)') +,('ms-MY','Malay (Malaysia)') +,('mt','Maltese') +; +INSERT INTO codelang (code,country) VALUES +('mt-MT','Maltese (Malta)') +,('nb','Norwegian (Bokm?l)') +,('nb-NO','Norwegian (Bokm?l) (Norway)') +,('nl','Dutch') +,('nl-BE','Dutch (Belgium)') +,('nl-NL','Dutch (Netherlands)') +,('nn-NO','Norwegian (Nynorsk) (Norway)') +,('ns','Northern Sotho') +,('ns-ZA','Northern Sotho (South Africa)') +,('pa','Punjabi') +; +INSERT INTO codelang (code,country) VALUES +('pa-IN','Punjabi (India)') +,('pl','Polish') +,('pl-PL','Polish (Poland)') +,('ps','Pashto') +,('ps-AR','Pashto (Afghanistan)') +,('pt','Portuguese') +,('pt-BR','Portuguese (Brazil)') +,('pt-PT','Portuguese (Portugal)') +,('qu','Quechua') +,('qu-BO','Quechua (Bolivia)') +; +INSERT INTO codelang (code,country) VALUES +('qu-EC','Quechua (Ecuador)') +,('qu-PE','Quechua (Peru)') +,('ro','Romanian') +,('ro-RO','Romanian (Romania)') +,('ru','Russian') +,('ru-RU','Russian (Russia)') +,('sa','Sanskrit') +,('sa-IN','Sanskrit (India)') +,('se','Sami (Northern)') +,('se-FI','Sami (Northern) (Finland)') +; +INSERT INTO codelang (code,country) VALUES +('se-FI','Sami (Skolt) (Finland)') +,('se-FI','Sami (Inari) (Finland)') +,('se-NO','Sami (Northern) (Norway)') +,('se-NO','Sami (Lule) (Norway)') +,('se-NO','Sami (Southern) (Norway)') +,('se-SE','Sami (Northern) (Sweden)') +,('se-SE','Sami (Lule) (Sweden)') +,('se-SE','Sami (Southern) (Sweden)') +,('sk','Slovak') +,('sk-SK','Slovak (Slovakia)') +; +INSERT INTO codelang (code,country) VALUES +('sl','Slovenian') +,('sl-SI','Slovenian (Slovenia)') +,('sq','Albanian') +,('sq-AL','Albanian (Albania)') +,('sr-BA','Serbian (Latin) (Bosnia and Herzegovina)') +,('sr-BA','Serbian (Cyrillic) (Bosnia and Herzegovina)') +,('sr-SP','Serbian (Latin) (Serbia and Montenegro)') +,('sr-SP','Serbian (Cyrillic) (Serbia and Montenegro)') +,('sv','Swedish') +,('sv-FI','Swedish (Finland)') +; +INSERT INTO codelang (code,country) VALUES +('sv-SE','Swedish (Sweden)') +,('sw','Swahili') +,('sw-KE','Swahili (Kenya)') +,('syr','Syriac') +,('syr-SY','Syriac (Syria)') +,('ta','Tamil') +,('ta-IN','Tamil (India)') +,('te','Telugu') +,('te-IN','Telugu (India)') +,('th','Thai') +; +INSERT INTO codelang (code,country) VALUES +('th-TH','Thai (Thailand)') +,('tl','Tagalog') +,('tl-PH','Tagalog (Philippines)') +,('tn','Tswana') +,('tn-ZA','Tswana (South Africa)') +,('tr','Turkish') +,('tr-TR','Turkish (Turkey)') +,('tt','Tatar') +,('tt-RU','Tatar (Russia)') +,('ts','Tsonga') +; +INSERT INTO codelang (code,country) VALUES +('uk','Ukrainian') +,('uk-UA','Ukrainian (Ukraine)') +,('ur','Urdu') +,('ur-PK','Urdu (Islamic Republic of Pakistan)') +,('uz','Uzbek (Latin)') +,('uz-UZ','Uzbek (Latin) (Uzbekistan)') +,('uz-UZ','Uzbek (Cyrillic) (Uzbekistan)') +,('vi','Vietnamese') +,('vi-VN','Vietnamese (Viet Nam)') +,('xh','Xhosa') +; +INSERT INTO codelang (code,country) VALUES +('xh-ZA','Xhosa (South Africa)') +,('zh','Chinese') +,('zh-CN','Chinese (S)') +,('zh-HK','Chinese (Hong Kong)') +,('zh-MO','Chinese (Macau)') +,('zh-SG','Chinese (Singapore)') +,('zh-TW','Chinese (T)') +,('zu','Zulu') +,('zu-ZA','Zulu (South Africa)') +; +/***************************/ +-- Manual migrate + +CREATE TABLE country_code ( + code varchar(100) NULL, + country varchar(10000) NULL +); + +insert into country_code(code, country) +select distinct + t.code, + coalesce( + case when length(t.country_name2) = 1 then null else t.country_name2 end, + case when length(t.contry_name1) = 1 then null else t.contry_name1 end, + t.country + ) as country +from +( + select trim(c.code) as code, + substring(trim(c.country) from '\((.+)\)') as contry_name1, + substring( + substring(trim(c.country) from '\((.+)\)') + from '\((.*)$') as country_name2, + trim(c.country) as country + from codelang as c +) t; + +commit; + +--delete from location_country as lc + +INSERT INTO location_country +(code, "name", low_price, high_price, created, modified) +select distinct + lpad((row_number() over (order by t.country asc))::text, 3, '0') as code, + jsonb_build_object('en-GB', t.country), + 0 as low_price, + 100 as high_price, + now() as created, + now() as modified +from +( + select + distinct c.country + from country_code c +) t +; + +commit; + +--delete from translation_language as tl; + +INSERT INTO translation_language +(title, locale) +select + distinct + t.country as title, + t.code as locale +from +( + select + distinct c.country, c.code + from country_code c +) t +; + +commit; + + +--delete from location_country_languages + +INSERT INTO location_country_languages +(country_id, language_id) +select lc.id as country_id, + l.id as language_id +from location_country as lc +join ( + select tl.*, '"'||tl.title||'"' as country + from translation_language as tl +) l on l.country = (lc."name"::json->'en-GB')::text +; + +commit; + +drop table country_code; +drop table codelang; + +commit; \ No newline at end of file diff --git a/apps/translation/migrations/remigrate_lang.sql b/apps/translation/migrations/remigrate_lang.sql new file mode 100644 index 00000000..160ac93e --- /dev/null +++ b/apps/translation/migrations/remigrate_lang.sql @@ -0,0 +1,391 @@ +SET search_path TO gm, public; + +CREATE TABLE codelang ( + code varchar(100) NULL, + country varchar(10000) NULL +); + + +INSERT INTO codelang (code,country) VALUES +('af','Afrikaans') +,('af-ZA','Afrikaans (South Africa)') +,('ar','Arabic') +,('ar-AE','Arabic (U.A.E.)') +,('ar-BH','Arabic (Bahrain)') +,('ar-DZ','Arabic (Algeria)') +,('ar-EG','Arabic (Egypt)') +,('ar-IQ','Arabic (Iraq)') +,('ar-JO','Arabic (Jordan)') +,('ar-KW','Arabic (Kuwait)') +; +INSERT INTO codelang (code,country) VALUES +('ar-LB','Arabic (Lebanon)') +,('ar-LY','Arabic (Libya)') +,('ar-MA','Arabic (Morocco)') +,('ar-OM','Arabic (Oman)') +,('ar-QA','Arabic (Qatar)') +,('ar-SA','Arabic (Saudi Arabia)') +,('ar-SY','Arabic (Syria)') +,('ar-TN','Arabic (Tunisia)') +,('ar-YE','Arabic (Yemen)') +,('az','Azeri (Latin)') +; +INSERT INTO codelang (code,country) VALUES +('az-AZ','Azeri (Latin) (Azerbaijan)') +,('az-AZ','Azeri (Cyrillic) (Azerbaijan)') +,('be','Belarusian') +,('be-BY','Belarusian (Belarus)') +,('bg','Bulgarian') +,('bg-BG','Bulgarian (Bulgaria)') +,('bs-BA','Bosnian (Bosnia and Herzegovina)') +,('ca','Catalan') +,('ca-ES','Catalan (Spain)') +,('cs','Czech') +; +INSERT INTO codelang (code,country) VALUES +('cs-CZ','Czech (Czech Republic)') +,('cy','Welsh') +,('cy-GB','Welsh (United Kingdom)') +,('da','Danish') +,('da-DK','Danish (Denmark)') +,('de','German') +,('de-AT','German (Austria)') +,('de-CH','German (Switzerland)') +,('de-DE','German (Germany)') +,('de-LI','German (Liechtenstein)') +; +INSERT INTO codelang (code,country) VALUES +('de-LU','German (Luxembourg)') +,('dv','Divehi') +,('dv-MV','Divehi (Maldives)') +,('el','Greek') +,('el-GR','Greek (Greece)') +,('en','English') +,('en-AU','English (Australia)') +,('en-BZ','English (Belize)') +,('en-CA','English (Canada)') +,('en-CB','English (Caribbean)') +; +INSERT INTO codelang (code,country) VALUES +('en-GB','English (United Kingdom)') +,('en-IE','English (Ireland)') +,('en-JM','English (Jamaica)') +,('en-NZ','English (New Zealand)') +,('en-PH','English (Republic of the Philippines)') +,('en-TT','English (Trinidad and Tobago)') +,('en-US','English (United States)') +,('en-ZA','English (South Africa)') +,('en-ZW','English (Zimbabwe)') +,('eo','Esperanto') +; +INSERT INTO codelang (code,country) VALUES +('es','Spanish') +,('es-AR','Spanish (Argentina)') +,('es-BO','Spanish (Bolivia)') +,('es-CL','Spanish (Chile)') +,('es-CO','Spanish (Colombia)') +,('es-CR','Spanish (Costa Rica)') +,('es-DO','Spanish (Dominican Republic)') +,('es-EC','Spanish (Ecuador)') +,('es-ES','Spanish (Castilian)') +,('es-ES','Spanish (Spain)') +; +INSERT INTO codelang (code,country) VALUES +('es-GT','Spanish (Guatemala)') +,('es-HN','Spanish (Honduras)') +,('es-MX','Spanish (Mexico)') +,('es-NI','Spanish (Nicaragua)') +,('es-PA','Spanish (Panama)') +,('es-PE','Spanish (Peru)') +,('es-PR','Spanish (Puerto Rico)') +,('es-PY','Spanish (Paraguay)') +,('es-SV','Spanish (El Salvador)') +,('es-UY','Spanish (Uruguay)') +; +INSERT INTO codelang (code,country) VALUES +('es-VE','Spanish (Venezuela)') +,('et','Estonian') +,('et-EE','Estonian (Estonia)') +,('eu','Basque') +,('eu-ES','Basque (Spain)') +,('fa','Farsi') +,('fa-IR','Farsi (Iran)') +,('fi','Finnish') +,('fi-FI','Finnish (Finland)') +,('fo','Faroese') +; +INSERT INTO codelang (code,country) VALUES +('fo-FO','Faroese (Faroe Islands)') +,('fr','French') +,('fr-BE','French (Belgium)') +,('fr-CA','French (Canada)') +,('fr-CH','French (Switzerland)') +,('fr-FR','French (France)') +,('fr-LU','French (Luxembourg)') +,('fr-MC','French (Principality of Monaco)') +,('gl','Galician') +,('gl-ES','Galician (Spain)') +; +INSERT INTO codelang (code,country) VALUES +('gu','Gujarati') +,('gu-IN','Gujarati (India)') +,('he','Hebrew') +,('he-IL','Hebrew (Israel)') +,('hi','Hindi') +,('hi-IN','Hindi (India)') +,('hr','Croatian') +,('hr-BA','Croatian (Bosnia and Herzegovina)') +,('hr-HR','Croatian (Croatia)') +,('hu','Hungarian') +; +INSERT INTO codelang (code,country) VALUES +('hu-HU','Hungarian (Hungary)') +,('hy','Armenian') +,('hy-AM','Armenian (Armenia)') +,('id','Indonesian') +,('id-ID','Indonesian (Indonesia)') +,('is','Icelandic') +,('is-IS','Icelandic (Iceland)') +,('it','Italian') +,('it-CH','Italian (Switzerland)') +,('it-IT','Italian (Italy)') +; +INSERT INTO codelang (code,country) VALUES +('ja','Japanese') +,('ja-JP','Japanese (Japan)') +,('ka','Georgian') +,('ka-GE','Georgian (Georgia)') +,('kk','Kazakh') +,('kk-KZ','Kazakh (Kazakhstan)') +,('kn','Kannada') +,('kn-IN','Kannada (India)') +,('ko','Korean') +,('ko-KR','Korean (Korea)') +; +INSERT INTO codelang (code,country) VALUES +('kok','Konkani') +,('kok-IN','Konkani (India)') +,('ky','Kyrgyz') +,('ky-KG','Kyrgyz (Kyrgyzstan)') +,('lt','Lithuanian') +,('lt-LT','Lithuanian (Lithuania)') +,('lv','Latvian') +,('lv-LV','Latvian (Latvia)') +,('mi','Maori') +,('mi-NZ','Maori (New Zealand)') +; +INSERT INTO codelang (code,country) VALUES +('mk','FYRO Macedonian') +,('mk-MK','FYRO Macedonian (Former Yugoslav Republic of Macedonia)') +,('mn','Mongolian') +,('mn-MN','Mongolian (Mongolia)') +,('mr','Marathi') +,('mr-IN','Marathi (India)') +,('ms','Malay') +,('ms-BN','Malay (Brunei Darussalam)') +,('ms-MY','Malay (Malaysia)') +,('mt','Maltese') +; +INSERT INTO codelang (code,country) VALUES +('mt-MT','Maltese (Malta)') +,('nb','Norwegian (Bokm?l)') +,('nb-NO','Norwegian (Bokm?l) (Norway)') +,('nl','Dutch') +,('nl-BE','Dutch (Belgium)') +,('nl-NL','Dutch (Netherlands)') +,('nn-NO','Norwegian (Nynorsk) (Norway)') +,('ns','Northern Sotho') +,('ns-ZA','Northern Sotho (South Africa)') +,('pa','Punjabi') +; +INSERT INTO codelang (code,country) VALUES +('pa-IN','Punjabi (India)') +,('pl','Polish') +,('pl-PL','Polish (Poland)') +,('ps','Pashto') +,('ps-AR','Pashto (Afghanistan)') +,('pt','Portuguese') +,('pt-BR','Portuguese (Brazil)') +,('pt-PT','Portuguese (Portugal)') +,('qu','Quechua') +,('qu-BO','Quechua (Bolivia)') +; +INSERT INTO codelang (code,country) VALUES +('qu-EC','Quechua (Ecuador)') +,('qu-PE','Quechua (Peru)') +,('ro','Romanian') +,('ro-RO','Romanian (Romania)') +,('ru','Russian') +,('ru-RU','Russian (Russia)') +,('sa','Sanskrit') +,('sa-IN','Sanskrit (India)') +,('se','Sami (Northern)') +,('se-FI','Sami (Northern) (Finland)') +; +INSERT INTO codelang (code,country) VALUES +('se-FI','Sami (Skolt) (Finland)') +,('se-FI','Sami (Inari) (Finland)') +,('se-NO','Sami (Northern) (Norway)') +,('se-NO','Sami (Lule) (Norway)') +,('se-NO','Sami (Southern) (Norway)') +,('se-SE','Sami (Northern) (Sweden)') +,('se-SE','Sami (Lule) (Sweden)') +,('se-SE','Sami (Southern) (Sweden)') +,('sk','Slovak') +,('sk-SK','Slovak (Slovakia)') +; +INSERT INTO codelang (code,country) VALUES +('sl','Slovenian') +,('sl-SI','Slovenian (Slovenia)') +,('sq','Albanian') +,('sq-AL','Albanian (Albania)') +,('sr-BA','Serbian (Latin) (Bosnia and Herzegovina)') +,('sr-BA','Serbian (Cyrillic) (Bosnia and Herzegovina)') +,('sr-SP','Serbian (Latin) (Serbia and Montenegro)') +,('sr-SP','Serbian (Cyrillic) (Serbia and Montenegro)') +,('sv','Swedish') +,('sv-FI','Swedish (Finland)') +; +INSERT INTO codelang (code,country) VALUES +('sv-SE','Swedish (Sweden)') +,('sw','Swahili') +,('sw-KE','Swahili (Kenya)') +,('syr','Syriac') +,('syr-SY','Syriac (Syria)') +,('ta','Tamil') +,('ta-IN','Tamil (India)') +,('te','Telugu') +,('te-IN','Telugu (India)') +,('th','Thai') +; +INSERT INTO codelang (code,country) VALUES +('th-TH','Thai (Thailand)') +,('tl','Tagalog') +,('tl-PH','Tagalog (Philippines)') +,('tn','Tswana') +,('tn-ZA','Tswana (South Africa)') +,('tr','Turkish') +,('tr-TR','Turkish (Turkey)') +,('tt','Tatar') +,('tt-RU','Tatar (Russia)') +,('ts','Tsonga') +; +INSERT INTO codelang (code,country) VALUES +('uk','Ukrainian') +,('uk-UA','Ukrainian (Ukraine)') +,('ur','Urdu') +,('ur-PK','Urdu (Islamic Republic of Pakistan)') +,('uz','Uzbek (Latin)') +,('uz-UZ','Uzbek (Latin) (Uzbekistan)') +,('uz-UZ','Uzbek (Cyrillic) (Uzbekistan)') +,('vi','Vietnamese') +,('vi-VN','Vietnamese (Viet Nam)') +,('xh','Xhosa') +; +INSERT INTO codelang (code,country) VALUES +('xh-ZA','Xhosa (South Africa)') +,('zh','Chinese') +,('zh-CN','Chinese (S)') +,('zh-HK','Chinese (Hong Kong)') +,('zh-MO','Chinese (Macau)') +,('zh-SG','Chinese (Singapore)') +,('zh-TW','Chinese (T)') +,('zu','Zulu') +,('zu-ZA','Zulu (South Africa)') +; +/***************************/ +-- Manual migrate + +CREATE TABLE country_code ( + code varchar(100) NULL, + country varchar(10000) NULL +); + +insert into country_code(code, country) +select distinct + t.code, + coalesce( + case when length(t.country_name2) = 1 then null else t.country_name2 end, + case when length(t.contry_name1) = 1 then null else t.contry_name1 end, + t.country + ) as country +from +( + select trim(c.code) as code, + substring(trim(c.country) from '\((.+)\)') as contry_name1, + substring( + substring(trim(c.country) from '\((.+)\)') + from '\((.*)$') as country_name2, + trim(c.country) as country + from codelang as c +) t; + +commit; + + +delete from location_country_languages as lcl +where lcl.country_id in +( + select + lc.id + from + ( + select + lpad((row_number() over (order by t.country asc))::text, 3, '0') as code, + jsonb_build_object('en-GB', t.country) as "name" + from + ( + select + distinct c.country + from country_code c + ) t + ) d + join location_country lc on lc.code = d.code and d."name"=lc."name" +) +; +commit; + + +delete from location_country as lcl +where lcl.id in +( + select + lc.id + from + ( + select + lpad((row_number() over (order by t.country asc))::text, 3, '0') as code, + jsonb_build_object('en-GB', t.country) as "name" + from + ( + select + distinct c.country + from country_code c + ) t + ) d + join location_country lc on lc.code = d.code and d."name"=lc."name" +) +; + +commit; + + +delete from translation_language tl +where tl.id in +( + SELECT tl.id + FROM + ( + select + distinct c.country, c.code + from country_code c + ) t + JOIN translation_language tl ON tl.locale = t.code and tl.title = t.country +); + +commit; + +drop table country_code; +drop table codelang; + +commit; \ No newline at end of file diff --git a/apps/translation/models.py b/apps/translation/models.py index bc9fbfbf..cb0729ea 100644 --- a/apps/translation/models.py +++ b/apps/translation/models.py @@ -22,7 +22,7 @@ class Language(models.Model): title = models.CharField(max_length=255, verbose_name=_('Language title')) - locale = models.CharField(max_length=10, unique=True, + locale = models.CharField(max_length=10, verbose_name=_('Locale identifier')) objects = LanguageQuerySet.as_manager() @@ -32,6 +32,7 @@ class Language(models.Model): verbose_name = _('Language') verbose_name_plural = _('Languages') + unique_together = ('title', 'locale') def __str__(self): """String method""" diff --git a/project/urls/back.py b/project/urls/back.py index 9194a44b..40b3415a 100644 --- a/project/urls/back.py +++ b/project/urls/back.py @@ -9,8 +9,6 @@ urlpatterns = [ path('gallery/', include(('gallery.urls', 'gallery'), namespace='gallery')), path('location/', include('location.urls.back')), path('news/', include('news.urls.back')), - path('account/', include('account.urls.back')), - path('comment/', include('comment.urls.back')), path('review/', include('review.urls.back')), path('tags/', include(('tag.urls.back', 'tag'), namespace='tag')), ] From 1ed48a43eb6ac25bc34b21313ad73050d6b01999 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Mon, 21 Oct 2019 15:31:19 +0300 Subject: [PATCH 109/223] Fix url --- project/urls/back.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/project/urls/back.py b/project/urls/back.py index 9194a44b..40b3415a 100644 --- a/project/urls/back.py +++ b/project/urls/back.py @@ -9,8 +9,6 @@ urlpatterns = [ path('gallery/', include(('gallery.urls', 'gallery'), namespace='gallery')), path('location/', include('location.urls.back')), path('news/', include('news.urls.back')), - path('account/', include('account.urls.back')), - path('comment/', include('comment.urls.back')), path('review/', include('review.urls.back')), path('tags/', include(('tag.urls.back', 'tag'), namespace='tag')), ] From 4f02fef4568677eb3190f4eac4bc104b548c84b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Mon, 21 Oct 2019 15:44:20 +0300 Subject: [PATCH 110/223] Fix migrate --- apps/location/migrations/migrate_lang.sql | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/location/migrations/migrate_lang.sql b/apps/location/migrations/migrate_lang.sql index 11c93573..021d251e 100644 --- a/apps/location/migrations/migrate_lang.sql +++ b/apps/location/migrations/migrate_lang.sql @@ -87,7 +87,6 @@ INSERT INTO codelang (code,country) VALUES ,('es-CR','Spanish (Costa Rica)') ,('es-DO','Spanish (Dominican Republic)') ,('es-EC','Spanish (Ecuador)') -,('es-ES','Spanish (Castilian)') ,('es-ES','Spanish (Spain)') ; INSERT INTO codelang (code,country) VALUES From 196d335213a63100b5aeba74d49e18d7f784cff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Mon, 21 Oct 2019 15:47:06 +0300 Subject: [PATCH 111/223] Fix --- apps/location/migrations/0012_data_migrate.py | 24 +- .../migrations/006_data_migrate.py | 25 -- apps/translation/migrations/migrate_lang.sql | 383 ----------------- .../translation/migrations/remigrate_lang.sql | 391 ------------------ 4 files changed, 12 insertions(+), 811 deletions(-) delete mode 100644 apps/translation/migrations/006_data_migrate.py delete mode 100644 apps/translation/migrations/migrate_lang.sql delete mode 100644 apps/translation/migrations/remigrate_lang.sql diff --git a/apps/location/migrations/0012_data_migrate.py b/apps/location/migrations/0012_data_migrate.py index de75c8ad..b61c43df 100644 --- a/apps/location/migrations/0012_data_migrate.py +++ b/apps/location/migrations/0012_data_migrate.py @@ -4,22 +4,22 @@ import os class Migration(migrations.Migration): # Check migration - # def load_data_from_sql(apps, schema_editor): - # file_path = os.path.join(os.path.dirname(__file__), 'migrate_lang.sql') - # sql_statement = open(file_path).read() - # with connection.cursor() as c: - # c.execute(sql_statement) - # - # def revert_data(apps, schema_editor): - # file_path = os.path.join(os.path.dirname(__file__), 'remigrate_lang.sql') - # sql_statement = open(file_path).read() - # with connection.cursor() as c: - # c.execute(sql_statement) + def load_data_from_sql(apps, schema_editor): + file_path = os.path.join(os.path.dirname(__file__), 'migrate_lang.sql') + sql_statement = open(file_path).read() + with connection.cursor() as c: + c.execute(sql_statement) + + def revert_data(apps, schema_editor): + file_path = os.path.join(os.path.dirname(__file__), 'remigrate_lang.sql') + sql_statement = open(file_path).read() + with connection.cursor() as c: + c.execute(sql_statement) dependencies = [ ('location', '0011_country_languages'), ] operations = [ - # migrations.RunPython(load_data_from_sql, revert_data), + migrations.RunPython(load_data_from_sql, revert_data), ] diff --git a/apps/translation/migrations/006_data_migrate.py b/apps/translation/migrations/006_data_migrate.py deleted file mode 100644 index d8142773..00000000 --- a/apps/translation/migrations/006_data_migrate.py +++ /dev/null @@ -1,25 +0,0 @@ -from django.db import migrations, connection -import os - - -class Migration(migrations.Migration): - - def load_data_from_sql(apps, schema_editor): - file_path = os.path.join(os.path.dirname(__file__), 'migrate_lang.sql') - sql_statement = open(file_path).read() - with connection.cursor() as c: - c.execute(sql_statement) - - def revert_data(apps, schema_editor): - file_path = os.path.join(os.path.dirname(__file__), 'remigrate_lang.sql') - sql_statement = open(file_path).read() - with connection.cursor() as c: - c.execute(sql_statement) - - dependencies = [ - ('translation', '0005_auto_20191021_1201'), - ] - - operations = [ - migrations.RunPython(load_data_from_sql, revert_data), - ] diff --git a/apps/translation/migrations/migrate_lang.sql b/apps/translation/migrations/migrate_lang.sql deleted file mode 100644 index 9f15e9c6..00000000 --- a/apps/translation/migrations/migrate_lang.sql +++ /dev/null @@ -1,383 +0,0 @@ -SET search_path TO gm, public; - -CREATE TABLE codelang ( - code varchar(100) NULL, - country varchar(10000) NULL -); - - -INSERT INTO codelang (code,country) VALUES -('af','Afrikaans') -,('af-ZA','Afrikaans (South Africa)') -,('ar','Arabic') -,('ar-AE','Arabic (U.A.E.)') -,('ar-BH','Arabic (Bahrain)') -,('ar-DZ','Arabic (Algeria)') -,('ar-EG','Arabic (Egypt)') -,('ar-IQ','Arabic (Iraq)') -,('ar-JO','Arabic (Jordan)') -,('ar-KW','Arabic (Kuwait)') -; -INSERT INTO codelang (code,country) VALUES -('ar-LB','Arabic (Lebanon)') -,('ar-LY','Arabic (Libya)') -,('ar-MA','Arabic (Morocco)') -,('ar-OM','Arabic (Oman)') -,('ar-QA','Arabic (Qatar)') -,('ar-SA','Arabic (Saudi Arabia)') -,('ar-SY','Arabic (Syria)') -,('ar-TN','Arabic (Tunisia)') -,('ar-YE','Arabic (Yemen)') -,('az','Azeri (Latin)') -; -INSERT INTO codelang (code,country) VALUES -('az-AZ','Azeri (Latin) (Azerbaijan)') -,('az-AZ','Azeri (Cyrillic) (Azerbaijan)') -,('be','Belarusian') -,('be-BY','Belarusian (Belarus)') -,('bg','Bulgarian') -,('bg-BG','Bulgarian (Bulgaria)') -,('bs-BA','Bosnian (Bosnia and Herzegovina)') -,('ca','Catalan') -,('ca-ES','Catalan (Spain)') -,('cs','Czech') -; -INSERT INTO codelang (code,country) VALUES -('cs-CZ','Czech (Czech Republic)') -,('cy','Welsh') -,('cy-GB','Welsh (United Kingdom)') -,('da','Danish') -,('da-DK','Danish (Denmark)') -,('de','German') -,('de-AT','German (Austria)') -,('de-CH','German (Switzerland)') -,('de-DE','German (Germany)') -,('de-LI','German (Liechtenstein)') -; -INSERT INTO codelang (code,country) VALUES -('de-LU','German (Luxembourg)') -,('dv','Divehi') -,('dv-MV','Divehi (Maldives)') -,('el','Greek') -,('el-GR','Greek (Greece)') -,('en','English') -,('en-AU','English (Australia)') -,('en-BZ','English (Belize)') -,('en-CA','English (Canada)') -,('en-CB','English (Caribbean)') -; -INSERT INTO codelang (code,country) VALUES -('en-GB','English (United Kingdom)') -,('en-IE','English (Ireland)') -,('en-JM','English (Jamaica)') -,('en-NZ','English (New Zealand)') -,('en-PH','English (Republic of the Philippines)') -,('en-TT','English (Trinidad and Tobago)') -,('en-US','English (United States)') -,('en-ZA','English (South Africa)') -,('en-ZW','English (Zimbabwe)') -,('eo','Esperanto') -; -INSERT INTO codelang (code,country) VALUES -('es','Spanish') -,('es-AR','Spanish (Argentina)') -,('es-BO','Spanish (Bolivia)') -,('es-CL','Spanish (Chile)') -,('es-CO','Spanish (Colombia)') -,('es-CR','Spanish (Costa Rica)') -,('es-DO','Spanish (Dominican Republic)') -,('es-EC','Spanish (Ecuador)') -,('es-ES','Spanish (Castilian)') -,('es-ES','Spanish (Spain)') -; -INSERT INTO codelang (code,country) VALUES -('es-GT','Spanish (Guatemala)') -,('es-HN','Spanish (Honduras)') -,('es-MX','Spanish (Mexico)') -,('es-NI','Spanish (Nicaragua)') -,('es-PA','Spanish (Panama)') -,('es-PE','Spanish (Peru)') -,('es-PR','Spanish (Puerto Rico)') -,('es-PY','Spanish (Paraguay)') -,('es-SV','Spanish (El Salvador)') -,('es-UY','Spanish (Uruguay)') -; -INSERT INTO codelang (code,country) VALUES -('es-VE','Spanish (Venezuela)') -,('et','Estonian') -,('et-EE','Estonian (Estonia)') -,('eu','Basque') -,('eu-ES','Basque (Spain)') -,('fa','Farsi') -,('fa-IR','Farsi (Iran)') -,('fi','Finnish') -,('fi-FI','Finnish (Finland)') -,('fo','Faroese') -; -INSERT INTO codelang (code,country) VALUES -('fo-FO','Faroese (Faroe Islands)') -,('fr','French') -,('fr-BE','French (Belgium)') -,('fr-CA','French (Canada)') -,('fr-CH','French (Switzerland)') -,('fr-FR','French (France)') -,('fr-LU','French (Luxembourg)') -,('fr-MC','French (Principality of Monaco)') -,('gl','Galician') -,('gl-ES','Galician (Spain)') -; -INSERT INTO codelang (code,country) VALUES -('gu','Gujarati') -,('gu-IN','Gujarati (India)') -,('he','Hebrew') -,('he-IL','Hebrew (Israel)') -,('hi','Hindi') -,('hi-IN','Hindi (India)') -,('hr','Croatian') -,('hr-BA','Croatian (Bosnia and Herzegovina)') -,('hr-HR','Croatian (Croatia)') -,('hu','Hungarian') -; -INSERT INTO codelang (code,country) VALUES -('hu-HU','Hungarian (Hungary)') -,('hy','Armenian') -,('hy-AM','Armenian (Armenia)') -,('id','Indonesian') -,('id-ID','Indonesian (Indonesia)') -,('is','Icelandic') -,('is-IS','Icelandic (Iceland)') -,('it','Italian') -,('it-CH','Italian (Switzerland)') -,('it-IT','Italian (Italy)') -; -INSERT INTO codelang (code,country) VALUES -('ja','Japanese') -,('ja-JP','Japanese (Japan)') -,('ka','Georgian') -,('ka-GE','Georgian (Georgia)') -,('kk','Kazakh') -,('kk-KZ','Kazakh (Kazakhstan)') -,('kn','Kannada') -,('kn-IN','Kannada (India)') -,('ko','Korean') -,('ko-KR','Korean (Korea)') -; -INSERT INTO codelang (code,country) VALUES -('kok','Konkani') -,('kok-IN','Konkani (India)') -,('ky','Kyrgyz') -,('ky-KG','Kyrgyz (Kyrgyzstan)') -,('lt','Lithuanian') -,('lt-LT','Lithuanian (Lithuania)') -,('lv','Latvian') -,('lv-LV','Latvian (Latvia)') -,('mi','Maori') -,('mi-NZ','Maori (New Zealand)') -; -INSERT INTO codelang (code,country) VALUES -('mk','FYRO Macedonian') -,('mk-MK','FYRO Macedonian (Former Yugoslav Republic of Macedonia)') -,('mn','Mongolian') -,('mn-MN','Mongolian (Mongolia)') -,('mr','Marathi') -,('mr-IN','Marathi (India)') -,('ms','Malay') -,('ms-BN','Malay (Brunei Darussalam)') -,('ms-MY','Malay (Malaysia)') -,('mt','Maltese') -; -INSERT INTO codelang (code,country) VALUES -('mt-MT','Maltese (Malta)') -,('nb','Norwegian (Bokm?l)') -,('nb-NO','Norwegian (Bokm?l) (Norway)') -,('nl','Dutch') -,('nl-BE','Dutch (Belgium)') -,('nl-NL','Dutch (Netherlands)') -,('nn-NO','Norwegian (Nynorsk) (Norway)') -,('ns','Northern Sotho') -,('ns-ZA','Northern Sotho (South Africa)') -,('pa','Punjabi') -; -INSERT INTO codelang (code,country) VALUES -('pa-IN','Punjabi (India)') -,('pl','Polish') -,('pl-PL','Polish (Poland)') -,('ps','Pashto') -,('ps-AR','Pashto (Afghanistan)') -,('pt','Portuguese') -,('pt-BR','Portuguese (Brazil)') -,('pt-PT','Portuguese (Portugal)') -,('qu','Quechua') -,('qu-BO','Quechua (Bolivia)') -; -INSERT INTO codelang (code,country) VALUES -('qu-EC','Quechua (Ecuador)') -,('qu-PE','Quechua (Peru)') -,('ro','Romanian') -,('ro-RO','Romanian (Romania)') -,('ru','Russian') -,('ru-RU','Russian (Russia)') -,('sa','Sanskrit') -,('sa-IN','Sanskrit (India)') -,('se','Sami (Northern)') -,('se-FI','Sami (Northern) (Finland)') -; -INSERT INTO codelang (code,country) VALUES -('se-FI','Sami (Skolt) (Finland)') -,('se-FI','Sami (Inari) (Finland)') -,('se-NO','Sami (Northern) (Norway)') -,('se-NO','Sami (Lule) (Norway)') -,('se-NO','Sami (Southern) (Norway)') -,('se-SE','Sami (Northern) (Sweden)') -,('se-SE','Sami (Lule) (Sweden)') -,('se-SE','Sami (Southern) (Sweden)') -,('sk','Slovak') -,('sk-SK','Slovak (Slovakia)') -; -INSERT INTO codelang (code,country) VALUES -('sl','Slovenian') -,('sl-SI','Slovenian (Slovenia)') -,('sq','Albanian') -,('sq-AL','Albanian (Albania)') -,('sr-BA','Serbian (Latin) (Bosnia and Herzegovina)') -,('sr-BA','Serbian (Cyrillic) (Bosnia and Herzegovina)') -,('sr-SP','Serbian (Latin) (Serbia and Montenegro)') -,('sr-SP','Serbian (Cyrillic) (Serbia and Montenegro)') -,('sv','Swedish') -,('sv-FI','Swedish (Finland)') -; -INSERT INTO codelang (code,country) VALUES -('sv-SE','Swedish (Sweden)') -,('sw','Swahili') -,('sw-KE','Swahili (Kenya)') -,('syr','Syriac') -,('syr-SY','Syriac (Syria)') -,('ta','Tamil') -,('ta-IN','Tamil (India)') -,('te','Telugu') -,('te-IN','Telugu (India)') -,('th','Thai') -; -INSERT INTO codelang (code,country) VALUES -('th-TH','Thai (Thailand)') -,('tl','Tagalog') -,('tl-PH','Tagalog (Philippines)') -,('tn','Tswana') -,('tn-ZA','Tswana (South Africa)') -,('tr','Turkish') -,('tr-TR','Turkish (Turkey)') -,('tt','Tatar') -,('tt-RU','Tatar (Russia)') -,('ts','Tsonga') -; -INSERT INTO codelang (code,country) VALUES -('uk','Ukrainian') -,('uk-UA','Ukrainian (Ukraine)') -,('ur','Urdu') -,('ur-PK','Urdu (Islamic Republic of Pakistan)') -,('uz','Uzbek (Latin)') -,('uz-UZ','Uzbek (Latin) (Uzbekistan)') -,('uz-UZ','Uzbek (Cyrillic) (Uzbekistan)') -,('vi','Vietnamese') -,('vi-VN','Vietnamese (Viet Nam)') -,('xh','Xhosa') -; -INSERT INTO codelang (code,country) VALUES -('xh-ZA','Xhosa (South Africa)') -,('zh','Chinese') -,('zh-CN','Chinese (S)') -,('zh-HK','Chinese (Hong Kong)') -,('zh-MO','Chinese (Macau)') -,('zh-SG','Chinese (Singapore)') -,('zh-TW','Chinese (T)') -,('zu','Zulu') -,('zu-ZA','Zulu (South Africa)') -; -/***************************/ --- Manual migrate - -CREATE TABLE country_code ( - code varchar(100) NULL, - country varchar(10000) NULL -); - -insert into country_code(code, country) -select distinct - t.code, - coalesce( - case when length(t.country_name2) = 1 then null else t.country_name2 end, - case when length(t.contry_name1) = 1 then null else t.contry_name1 end, - t.country - ) as country -from -( - select trim(c.code) as code, - substring(trim(c.country) from '\((.+)\)') as contry_name1, - substring( - substring(trim(c.country) from '\((.+)\)') - from '\((.*)$') as country_name2, - trim(c.country) as country - from codelang as c -) t; - -commit; - ---delete from location_country as lc - -INSERT INTO location_country -(code, "name", low_price, high_price, created, modified) -select distinct - lpad((row_number() over (order by t.country asc))::text, 3, '0') as code, - jsonb_build_object('en-GB', t.country), - 0 as low_price, - 100 as high_price, - now() as created, - now() as modified -from -( - select - distinct c.country - from country_code c -) t -; - -commit; - ---delete from translation_language as tl; - -INSERT INTO translation_language -(title, locale) -select - distinct - t.country as title, - t.code as locale -from -( - select - distinct c.country, c.code - from country_code c -) t -; - -commit; - - ---delete from location_country_languages - -INSERT INTO location_country_languages -(country_id, language_id) -select lc.id as country_id, - l.id as language_id -from location_country as lc -join ( - select tl.*, '"'||tl.title||'"' as country - from translation_language as tl -) l on l.country = (lc."name"::json->'en-GB')::text -; - -commit; - -drop table country_code; -drop table codelang; - -commit; \ No newline at end of file diff --git a/apps/translation/migrations/remigrate_lang.sql b/apps/translation/migrations/remigrate_lang.sql deleted file mode 100644 index 160ac93e..00000000 --- a/apps/translation/migrations/remigrate_lang.sql +++ /dev/null @@ -1,391 +0,0 @@ -SET search_path TO gm, public; - -CREATE TABLE codelang ( - code varchar(100) NULL, - country varchar(10000) NULL -); - - -INSERT INTO codelang (code,country) VALUES -('af','Afrikaans') -,('af-ZA','Afrikaans (South Africa)') -,('ar','Arabic') -,('ar-AE','Arabic (U.A.E.)') -,('ar-BH','Arabic (Bahrain)') -,('ar-DZ','Arabic (Algeria)') -,('ar-EG','Arabic (Egypt)') -,('ar-IQ','Arabic (Iraq)') -,('ar-JO','Arabic (Jordan)') -,('ar-KW','Arabic (Kuwait)') -; -INSERT INTO codelang (code,country) VALUES -('ar-LB','Arabic (Lebanon)') -,('ar-LY','Arabic (Libya)') -,('ar-MA','Arabic (Morocco)') -,('ar-OM','Arabic (Oman)') -,('ar-QA','Arabic (Qatar)') -,('ar-SA','Arabic (Saudi Arabia)') -,('ar-SY','Arabic (Syria)') -,('ar-TN','Arabic (Tunisia)') -,('ar-YE','Arabic (Yemen)') -,('az','Azeri (Latin)') -; -INSERT INTO codelang (code,country) VALUES -('az-AZ','Azeri (Latin) (Azerbaijan)') -,('az-AZ','Azeri (Cyrillic) (Azerbaijan)') -,('be','Belarusian') -,('be-BY','Belarusian (Belarus)') -,('bg','Bulgarian') -,('bg-BG','Bulgarian (Bulgaria)') -,('bs-BA','Bosnian (Bosnia and Herzegovina)') -,('ca','Catalan') -,('ca-ES','Catalan (Spain)') -,('cs','Czech') -; -INSERT INTO codelang (code,country) VALUES -('cs-CZ','Czech (Czech Republic)') -,('cy','Welsh') -,('cy-GB','Welsh (United Kingdom)') -,('da','Danish') -,('da-DK','Danish (Denmark)') -,('de','German') -,('de-AT','German (Austria)') -,('de-CH','German (Switzerland)') -,('de-DE','German (Germany)') -,('de-LI','German (Liechtenstein)') -; -INSERT INTO codelang (code,country) VALUES -('de-LU','German (Luxembourg)') -,('dv','Divehi') -,('dv-MV','Divehi (Maldives)') -,('el','Greek') -,('el-GR','Greek (Greece)') -,('en','English') -,('en-AU','English (Australia)') -,('en-BZ','English (Belize)') -,('en-CA','English (Canada)') -,('en-CB','English (Caribbean)') -; -INSERT INTO codelang (code,country) VALUES -('en-GB','English (United Kingdom)') -,('en-IE','English (Ireland)') -,('en-JM','English (Jamaica)') -,('en-NZ','English (New Zealand)') -,('en-PH','English (Republic of the Philippines)') -,('en-TT','English (Trinidad and Tobago)') -,('en-US','English (United States)') -,('en-ZA','English (South Africa)') -,('en-ZW','English (Zimbabwe)') -,('eo','Esperanto') -; -INSERT INTO codelang (code,country) VALUES -('es','Spanish') -,('es-AR','Spanish (Argentina)') -,('es-BO','Spanish (Bolivia)') -,('es-CL','Spanish (Chile)') -,('es-CO','Spanish (Colombia)') -,('es-CR','Spanish (Costa Rica)') -,('es-DO','Spanish (Dominican Republic)') -,('es-EC','Spanish (Ecuador)') -,('es-ES','Spanish (Castilian)') -,('es-ES','Spanish (Spain)') -; -INSERT INTO codelang (code,country) VALUES -('es-GT','Spanish (Guatemala)') -,('es-HN','Spanish (Honduras)') -,('es-MX','Spanish (Mexico)') -,('es-NI','Spanish (Nicaragua)') -,('es-PA','Spanish (Panama)') -,('es-PE','Spanish (Peru)') -,('es-PR','Spanish (Puerto Rico)') -,('es-PY','Spanish (Paraguay)') -,('es-SV','Spanish (El Salvador)') -,('es-UY','Spanish (Uruguay)') -; -INSERT INTO codelang (code,country) VALUES -('es-VE','Spanish (Venezuela)') -,('et','Estonian') -,('et-EE','Estonian (Estonia)') -,('eu','Basque') -,('eu-ES','Basque (Spain)') -,('fa','Farsi') -,('fa-IR','Farsi (Iran)') -,('fi','Finnish') -,('fi-FI','Finnish (Finland)') -,('fo','Faroese') -; -INSERT INTO codelang (code,country) VALUES -('fo-FO','Faroese (Faroe Islands)') -,('fr','French') -,('fr-BE','French (Belgium)') -,('fr-CA','French (Canada)') -,('fr-CH','French (Switzerland)') -,('fr-FR','French (France)') -,('fr-LU','French (Luxembourg)') -,('fr-MC','French (Principality of Monaco)') -,('gl','Galician') -,('gl-ES','Galician (Spain)') -; -INSERT INTO codelang (code,country) VALUES -('gu','Gujarati') -,('gu-IN','Gujarati (India)') -,('he','Hebrew') -,('he-IL','Hebrew (Israel)') -,('hi','Hindi') -,('hi-IN','Hindi (India)') -,('hr','Croatian') -,('hr-BA','Croatian (Bosnia and Herzegovina)') -,('hr-HR','Croatian (Croatia)') -,('hu','Hungarian') -; -INSERT INTO codelang (code,country) VALUES -('hu-HU','Hungarian (Hungary)') -,('hy','Armenian') -,('hy-AM','Armenian (Armenia)') -,('id','Indonesian') -,('id-ID','Indonesian (Indonesia)') -,('is','Icelandic') -,('is-IS','Icelandic (Iceland)') -,('it','Italian') -,('it-CH','Italian (Switzerland)') -,('it-IT','Italian (Italy)') -; -INSERT INTO codelang (code,country) VALUES -('ja','Japanese') -,('ja-JP','Japanese (Japan)') -,('ka','Georgian') -,('ka-GE','Georgian (Georgia)') -,('kk','Kazakh') -,('kk-KZ','Kazakh (Kazakhstan)') -,('kn','Kannada') -,('kn-IN','Kannada (India)') -,('ko','Korean') -,('ko-KR','Korean (Korea)') -; -INSERT INTO codelang (code,country) VALUES -('kok','Konkani') -,('kok-IN','Konkani (India)') -,('ky','Kyrgyz') -,('ky-KG','Kyrgyz (Kyrgyzstan)') -,('lt','Lithuanian') -,('lt-LT','Lithuanian (Lithuania)') -,('lv','Latvian') -,('lv-LV','Latvian (Latvia)') -,('mi','Maori') -,('mi-NZ','Maori (New Zealand)') -; -INSERT INTO codelang (code,country) VALUES -('mk','FYRO Macedonian') -,('mk-MK','FYRO Macedonian (Former Yugoslav Republic of Macedonia)') -,('mn','Mongolian') -,('mn-MN','Mongolian (Mongolia)') -,('mr','Marathi') -,('mr-IN','Marathi (India)') -,('ms','Malay') -,('ms-BN','Malay (Brunei Darussalam)') -,('ms-MY','Malay (Malaysia)') -,('mt','Maltese') -; -INSERT INTO codelang (code,country) VALUES -('mt-MT','Maltese (Malta)') -,('nb','Norwegian (Bokm?l)') -,('nb-NO','Norwegian (Bokm?l) (Norway)') -,('nl','Dutch') -,('nl-BE','Dutch (Belgium)') -,('nl-NL','Dutch (Netherlands)') -,('nn-NO','Norwegian (Nynorsk) (Norway)') -,('ns','Northern Sotho') -,('ns-ZA','Northern Sotho (South Africa)') -,('pa','Punjabi') -; -INSERT INTO codelang (code,country) VALUES -('pa-IN','Punjabi (India)') -,('pl','Polish') -,('pl-PL','Polish (Poland)') -,('ps','Pashto') -,('ps-AR','Pashto (Afghanistan)') -,('pt','Portuguese') -,('pt-BR','Portuguese (Brazil)') -,('pt-PT','Portuguese (Portugal)') -,('qu','Quechua') -,('qu-BO','Quechua (Bolivia)') -; -INSERT INTO codelang (code,country) VALUES -('qu-EC','Quechua (Ecuador)') -,('qu-PE','Quechua (Peru)') -,('ro','Romanian') -,('ro-RO','Romanian (Romania)') -,('ru','Russian') -,('ru-RU','Russian (Russia)') -,('sa','Sanskrit') -,('sa-IN','Sanskrit (India)') -,('se','Sami (Northern)') -,('se-FI','Sami (Northern) (Finland)') -; -INSERT INTO codelang (code,country) VALUES -('se-FI','Sami (Skolt) (Finland)') -,('se-FI','Sami (Inari) (Finland)') -,('se-NO','Sami (Northern) (Norway)') -,('se-NO','Sami (Lule) (Norway)') -,('se-NO','Sami (Southern) (Norway)') -,('se-SE','Sami (Northern) (Sweden)') -,('se-SE','Sami (Lule) (Sweden)') -,('se-SE','Sami (Southern) (Sweden)') -,('sk','Slovak') -,('sk-SK','Slovak (Slovakia)') -; -INSERT INTO codelang (code,country) VALUES -('sl','Slovenian') -,('sl-SI','Slovenian (Slovenia)') -,('sq','Albanian') -,('sq-AL','Albanian (Albania)') -,('sr-BA','Serbian (Latin) (Bosnia and Herzegovina)') -,('sr-BA','Serbian (Cyrillic) (Bosnia and Herzegovina)') -,('sr-SP','Serbian (Latin) (Serbia and Montenegro)') -,('sr-SP','Serbian (Cyrillic) (Serbia and Montenegro)') -,('sv','Swedish') -,('sv-FI','Swedish (Finland)') -; -INSERT INTO codelang (code,country) VALUES -('sv-SE','Swedish (Sweden)') -,('sw','Swahili') -,('sw-KE','Swahili (Kenya)') -,('syr','Syriac') -,('syr-SY','Syriac (Syria)') -,('ta','Tamil') -,('ta-IN','Tamil (India)') -,('te','Telugu') -,('te-IN','Telugu (India)') -,('th','Thai') -; -INSERT INTO codelang (code,country) VALUES -('th-TH','Thai (Thailand)') -,('tl','Tagalog') -,('tl-PH','Tagalog (Philippines)') -,('tn','Tswana') -,('tn-ZA','Tswana (South Africa)') -,('tr','Turkish') -,('tr-TR','Turkish (Turkey)') -,('tt','Tatar') -,('tt-RU','Tatar (Russia)') -,('ts','Tsonga') -; -INSERT INTO codelang (code,country) VALUES -('uk','Ukrainian') -,('uk-UA','Ukrainian (Ukraine)') -,('ur','Urdu') -,('ur-PK','Urdu (Islamic Republic of Pakistan)') -,('uz','Uzbek (Latin)') -,('uz-UZ','Uzbek (Latin) (Uzbekistan)') -,('uz-UZ','Uzbek (Cyrillic) (Uzbekistan)') -,('vi','Vietnamese') -,('vi-VN','Vietnamese (Viet Nam)') -,('xh','Xhosa') -; -INSERT INTO codelang (code,country) VALUES -('xh-ZA','Xhosa (South Africa)') -,('zh','Chinese') -,('zh-CN','Chinese (S)') -,('zh-HK','Chinese (Hong Kong)') -,('zh-MO','Chinese (Macau)') -,('zh-SG','Chinese (Singapore)') -,('zh-TW','Chinese (T)') -,('zu','Zulu') -,('zu-ZA','Zulu (South Africa)') -; -/***************************/ --- Manual migrate - -CREATE TABLE country_code ( - code varchar(100) NULL, - country varchar(10000) NULL -); - -insert into country_code(code, country) -select distinct - t.code, - coalesce( - case when length(t.country_name2) = 1 then null else t.country_name2 end, - case when length(t.contry_name1) = 1 then null else t.contry_name1 end, - t.country - ) as country -from -( - select trim(c.code) as code, - substring(trim(c.country) from '\((.+)\)') as contry_name1, - substring( - substring(trim(c.country) from '\((.+)\)') - from '\((.*)$') as country_name2, - trim(c.country) as country - from codelang as c -) t; - -commit; - - -delete from location_country_languages as lcl -where lcl.country_id in -( - select - lc.id - from - ( - select - lpad((row_number() over (order by t.country asc))::text, 3, '0') as code, - jsonb_build_object('en-GB', t.country) as "name" - from - ( - select - distinct c.country - from country_code c - ) t - ) d - join location_country lc on lc.code = d.code and d."name"=lc."name" -) -; -commit; - - -delete from location_country as lcl -where lcl.id in -( - select - lc.id - from - ( - select - lpad((row_number() over (order by t.country asc))::text, 3, '0') as code, - jsonb_build_object('en-GB', t.country) as "name" - from - ( - select - distinct c.country - from country_code c - ) t - ) d - join location_country lc on lc.code = d.code and d."name"=lc."name" -) -; - -commit; - - -delete from translation_language tl -where tl.id in -( - SELECT tl.id - FROM - ( - select - distinct c.country, c.code - from country_code c - ) t - JOIN translation_language tl ON tl.locale = t.code and tl.title = t.country -); - -commit; - -drop table country_code; -drop table codelang; - -commit; \ No newline at end of file From adbc22f1f6ad6e635f2f96a5a2cfac27c9ce4676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Mon, 21 Oct 2019 16:03:19 +0300 Subject: [PATCH 112/223] Fix comment test --- apps/utils/tests/tests_permissions.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/apps/utils/tests/tests_permissions.py b/apps/utils/tests/tests_permissions.py index ccb202ab..edc1a5d7 100644 --- a/apps/utils/tests/tests_permissions.py +++ b/apps/utils/tests/tests_permissions.py @@ -5,21 +5,14 @@ from translation.models import Language class BasePermissionTests(APITestCase): def setUp(self): - - self.lang = Language.objects.create( + self.lang = Language.objects.get( title='Russia', locale='ru-RU' ) - self.lang.save() - self.country_ru = Country.objects.create( - name='{"ru-RU":"Russia"}', - code='23', - low_price=15, - high_price=150000, + self.country_ru = Country.objects.get( + name={"en-GB": "Russian"} ) - self.country_ru.languages.add(self.lang) - self.country_ru.save() From 87708df08446c5b0a0d76a8a971f984bfe58d8be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Mon, 21 Oct 2019 16:11:28 +0300 Subject: [PATCH 113/223] Fix test establishment --- apps/establishment/tests.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index 9eb8c987..a1d8fcb5 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -28,20 +28,14 @@ class BaseTestCase(APITestCase): self.establishment_type = EstablishmentType.objects.create(name="Test establishment type") # Create lang object - self.lang = Language.objects.create( - title='English', - locale='en-GB' + self.lang = Language.objects.get( + title='Russia', + locale='ru-RU' ) - self.lang.save() - self.country_ru = Country.objects.create( - name='{"ru-RU":"Russia"}', - code='23', - low_price=15, - high_price=150000, + self.country_ru = Country.objects.get( + name={"en-GB": "Russian"} ) - self.country_ru.languages.add(self.lang) - self.country_ru.save() self.region = Region.objects.create(name='Moscow area', code='01', country=self.country_ru) @@ -331,7 +325,7 @@ class EstablishmentWebTagTests(BaseTestCase): def test_tag_Read(self): response = self.client.get('/api/web/establishments/tags/', format='json') - self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) class EstablishmentWebSlugTests(ChildTestCase): From d1d092e49dfd751d3515a545128247ee7e65f73c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Mon, 21 Oct 2019 16:21:14 +0300 Subject: [PATCH 114/223] Fix news tests --- apps/news/tests.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/apps/news/tests.py b/apps/news/tests.py index dd256bac..b4e2b296 100644 --- a/apps/news/tests.py +++ b/apps/news/tests.py @@ -25,20 +25,14 @@ class BaseTestCase(APITestCase): 'refresh_token': tokkens.get('refresh_token')}) self.test_news_type = NewsType.objects.create(name="Test news type") - self.lang = Language.objects.create( + self.lang = Language.objects.get( title='Russia', locale='ru-RU' ) - self.lang.save() - self.country_ru = Country.objects.create( - name='{"ru-RU":"Russia"}', - code='23', - low_price=15, - high_price=150000, + self.country_ru = Country.objects.get( + name={"en-GB": "Russian"} ) - self.country_ru.languages.add(self.lang) - self.country_ru.save() role = Role.objects.create( role=Role.CONTENT_PAGE_MANAGER, From 23b46e69edd5a1bf08a0d29c7997d0a5b675db32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Mon, 21 Oct 2019 16:28:32 +0300 Subject: [PATCH 115/223] Fix test location --- apps/location/tests.py | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/apps/location/tests.py b/apps/location/tests.py index edb719bd..cb574036 100644 --- a/apps/location/tests.py +++ b/apps/location/tests.py @@ -9,8 +9,8 @@ from location.models import City, Region, Country, Language from django.contrib.gis.geos import Point from account.models import Role, UserRole -class BaseTestCase(APITestCase): +class BaseTestCase(APITestCase): def setUp(self): self.username = 'sedragurda' self.password = 'sedragurdaredips19' @@ -29,20 +29,14 @@ class BaseTestCase(APITestCase): {'access_token': tokkens.get('access_token'), 'refresh_token': tokkens.get('refresh_token')}) - self.lang = Language.objects.create( + self.lang = Language.objects.get( title='Russia', locale='ru-RU' ) - self.lang.save() - self.country_ru = Country.objects.create( - name='{"ru-RU":"Russia"}', - code='23', - low_price=15, - high_price=150000, + self.country_ru = Country.objects.get( + name={"en-GB": "Russian"} ) - self.country_ru.languages.add(self.lang) - self.country_ru.save() self.role = Role.objects.create(role=Role.COUNTRY_ADMIN, country=self.country_ru) @@ -52,18 +46,15 @@ class BaseTestCase(APITestCase): self.user_role.save() - # role = Role.objects.create(role=Role.COUNTRY_ADMIN) - class CountryTests(BaseTestCase): def setUp(self): super().setUp() - def test_country_CRUD(self): data = { - 'name': {"ru-RU":"Russia"}, - 'code': 'test' + 'name': {"ru-RU": "NewCountry"}, + 'code': 'test1' } response = self.client.post('/api/back/location/countries/', data=data, format='json') From f96a1d4aa24309b97b6be5d67f711ded310fc663 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Mon, 21 Oct 2019 16:42:08 +0300 Subject: [PATCH 116/223] 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 94502cfc736e1bc177d08fd12c57a83cb1c23088 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Mon, 21 Oct 2019 16:47:01 +0300 Subject: [PATCH 117/223] added priority field to tag model (f7facf826df3f01cceaed754f9486a0e17d96581) --- apps/tag/migrations/0004_tag_priority.py | 18 ++++++++++++++++++ apps/tag/models.py | 1 + 2 files changed, 19 insertions(+) create mode 100644 apps/tag/migrations/0004_tag_priority.py diff --git a/apps/tag/migrations/0004_tag_priority.py b/apps/tag/migrations/0004_tag_priority.py new file mode 100644 index 00000000..3e7a6d7f --- /dev/null +++ b/apps/tag/migrations/0004_tag_priority.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-10-21 13:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tag', '0003_auto_20191018_0758'), + ] + + 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 85d86e74..44eacddc 100644 --- a/apps/tag/models.py +++ b/apps/tag/models.py @@ -14,6 +14,7 @@ class Tag(TranslatedFieldsMixin, models.Model): category = models.ForeignKey('TagCategory', on_delete=models.CASCADE, null=True, related_name='tags', verbose_name=_('Category')) + 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 118/223] 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 afc212764687f3afd13d68c22f4a8e54f3b6c0b6 Mon Sep 17 00:00:00 2001 From: Semyon Yekhmenin Date: Mon, 21 Oct 2019 14:45:34 +0000 Subject: [PATCH 119/223] Updated news model with agenda and banner --- .../migrations/0022_auto_20191021_1306.py | 57 +++++++++++++++++++ apps/news/models.py | 42 ++++++++++++-- apps/news/serializers.py | 51 ++++++++++++++--- 3 files changed, 138 insertions(+), 12 deletions(-) create mode 100644 apps/news/migrations/0022_auto_20191021_1306.py diff --git a/apps/news/migrations/0022_auto_20191021_1306.py b/apps/news/migrations/0022_auto_20191021_1306.py new file mode 100644 index 00000000..de8747f5 --- /dev/null +++ b/apps/news/migrations/0022_auto_20191021_1306.py @@ -0,0 +1,57 @@ +# Generated by Django 2.2.4 on 2019-10-21 13:06 + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import utils.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('location', '0012_data_migrate'), + ('news', '0021_auto_20191009_1408'), + ] + + operations = [ + migrations.CreateModel( + name='NewsBanner', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')), + ('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('title', utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='title')), + ('image_url', models.URLField(blank=True, default=None, null=True, verbose_name='Image URL path')), + ('content_url', models.URLField(blank=True, default=None, null=True, verbose_name='Content URL path')), + ], + options={ + 'abstract': False, + }, + bases=(models.Model, utils.models.TranslatedFieldsMixin), + ), + migrations.CreateModel( + name='Agenda', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')), + ('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')), + ('event_datetime', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Event datetime')), + ('content', utils.models.TJSONField(blank=True, default=None, help_text='{"en-GB":"some text"}', null=True, verbose_name='content')), + ('address', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='location.Address', verbose_name='address')), + ], + options={ + 'abstract': False, + }, + bases=(models.Model, utils.models.TranslatedFieldsMixin), + ), + migrations.AddField( + model_name='news', + name='agenda', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='news.Agenda', verbose_name='agenda'), + ), + migrations.AddField( + model_name='news', + name='banner', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='news.NewsBanner', verbose_name='banner'), + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index 8a6a89f4..0460174a 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -4,7 +4,7 @@ from django.contrib.contenttypes import fields as generic 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 utils.models import BaseAttributes, TJSONField, TranslatedFieldsMixin, ProjectBaseMixin from rating.models import Rating @@ -38,7 +38,7 @@ class NewsQuerySet(models.QuerySet): def with_extended_related(self): """Return qs with related objects.""" - return self.select_related('created_by') + return self.select_related('created_by', 'agenda', 'banner') def by_type(self, news_type): """Filter News by type""" @@ -61,15 +61,39 @@ class NewsQuerySet(models.QuerySet): # todo: filter by best score # todo: filter by country? def should_read(self, news): - return self.model.objects.exclude(pk=news.pk).published().\ + return self.model.objects.exclude(pk=news.pk).published(). \ with_base_related().by_type(news.news_type).distinct().order_by('?') def same_theme(self, news): - return self.model.objects.exclude(pk=news.pk).published().\ - with_base_related().by_type(news.news_type).\ + return self.model.objects.exclude(pk=news.pk).published(). \ + with_base_related().by_type(news.news_type). \ by_tags(news.tags.all()).distinct().order_by('-start') +class Agenda(ProjectBaseMixin, TranslatedFieldsMixin): + """News agenda model""" + + event_datetime = models.DateTimeField(default=timezone.now, editable=False, + verbose_name=_('Event datetime')) + address = models.ForeignKey('location.Address', blank=True, null=True, + default=None, verbose_name=_('address'), + on_delete=models.SET_NULL) + content = TJSONField(blank=True, null=True, default=None, + verbose_name=_('content'), + help_text='{"en-GB":"some text"}') + + +class NewsBanner(ProjectBaseMixin, TranslatedFieldsMixin): + """News banner model""" + title = TJSONField(blank=True, null=True, default=None, + verbose_name=_('title'), + help_text='{"en-GB":"some text"}') + image_url = models.URLField(verbose_name=_('Image URL path'), + blank=True, null=True, default=None) + content_url = models.URLField(verbose_name=_('Content URL path'), + blank=True, null=True, default=None) + + class News(BaseAttributes, TranslatedFieldsMixin): """News model.""" @@ -139,6 +163,14 @@ class News(BaseAttributes, TranslatedFieldsMixin): verbose_name=_('Tags')) ratings = generic.GenericRelation(Rating) + agenda = models.ForeignKey('news.Agenda', blank=True, null=True, + on_delete=models.SET_NULL, + verbose_name=_('agenda')) + + banner = models.ForeignKey('news.NewsBanner', blank=True, null=True, + on_delete=models.SET_NULL, + verbose_name=_('banner')) + objects = NewsQuerySet.as_manager() class Meta: diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 6f0b73b6..c730de0d 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -2,12 +2,46 @@ from rest_framework import serializers from account.serializers.common import UserBaseSerializer from location import models as location_models -from location.serializers import CountrySimpleSerializer +from location.serializers import CountrySimpleSerializer, AddressBaseSerializer from news import models from tag.serializers import TagBaseSerializer from utils.serializers import TranslatedField, ProjectModelSerializer +class AgendaSerializer(ProjectModelSerializer): + event_datetime = serializers.DateTimeField() + address = AddressBaseSerializer() + content_translated = TranslatedField() + + class Meta: + """Meta class.""" + + model = models.Agenda + fields = ( + 'id', + 'event_datetime', + 'address', + 'content_translated' + ) + + +class NewsBannerSerializer(ProjectModelSerializer): + title_translated = TranslatedField() + image_url = serializers.URLField() + content_url = serializers.URLField() + + class Meta: + """Meta class.""" + + model = models.NewsBanner + fields = ( + 'id', + 'title_translated', + 'image_url', + 'content_url' + ) + + class NewsTypeSerializer(serializers.ModelSerializer): """News type serializer.""" @@ -76,6 +110,8 @@ class NewsDetailWebSerializer(NewsDetailSerializer): same_theme = NewsBaseSerializer(many=True, read_only=True) should_read = NewsBaseSerializer(many=True, read_only=True) + agenda = AgendaSerializer() + banner = NewsBannerSerializer() class Meta(NewsDetailSerializer.Meta): """Meta class.""" @@ -83,6 +119,8 @@ class NewsDetailWebSerializer(NewsDetailSerializer): fields = NewsDetailSerializer.Meta.fields + ( 'same_theme', 'should_read', + 'agenda', + 'banner' ) @@ -116,10 +154,9 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer, fields = NewsBackOfficeBaseSerializer.Meta.fields + \ NewsDetailSerializer.Meta.fields + ( - 'description', - 'news_type_id', - 'country_id', - 'template', - 'template_display', + 'description', + 'news_type_id', + 'country_id', + 'template', + 'template_display', ) - From 4ceb0e4d86aeba8a88879a8773a3c08096dcf35d Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Mon, 21 Oct 2019 20:22:47 +0300 Subject: [PATCH 120/223] fix establishment migration --- .../0039_establishmentsubtype_index_name.py | 2 +- apps/search_indexes/signals.py | 20 +++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/apps/establishment/migrations/0039_establishmentsubtype_index_name.py b/apps/establishment/migrations/0039_establishmentsubtype_index_name.py index 5473600a..a29b1ae0 100644 --- a/apps/establishment/migrations/0039_establishmentsubtype_index_name.py +++ b/apps/establishment/migrations/0039_establishmentsubtype_index_name.py @@ -24,7 +24,7 @@ class Migration(migrations.Migration): name='index_name', field=models.CharField(blank=True, db_index=True, max_length=50, null=True, unique=True, default=None, verbose_name='Index name'), ), - migrations.RunPython(fill_establishment_subtype), + migrations.RunPython(fill_establishment_subtype, migrations.RunPython.noop), migrations.AlterField( model_name='establishmentsubtype', name='index_name', diff --git a/apps/search_indexes/signals.py b/apps/search_indexes/signals.py index 0f6a071f..77660a2c 100644 --- a/apps/search_indexes/signals.py +++ b/apps/search_indexes/signals.py @@ -29,16 +29,20 @@ def update_document(sender, **kwargs): registry.update(establishment) if app_label == 'establishment': + # todo: remove after migration + from establishment import models as establishment_models if model_name == 'establishmenttype': - establishments = Establishment.objects.filter( - establishment_type=instance) - for establishment in establishments: - registry.update(establishment) + if isinstance(instance, establishment_models.EstablishmentType): + establishments = Establishment.objects.filter( + establishment_type=instance) + for establishment in establishments: + registry.update(establishment) if model_name == 'establishmentsubtype': - establishments = Establishment.objects.filter( - establishment_subtypes=instance) - for establishment in establishments: - registry.update(establishment) + if instance(instance, establishment_models.EstablishmentSubType): + establishments = Establishment.objects.filter( + establishment_subtypes=instance) + for establishment in establishments: + registry.update(establishment) if app_label == 'tag': if model_name == 'tag': From 386e7d1bf88f67ef15661681aa97c44a1f59d9b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Tue, 22 Oct 2019 15:39:51 +0300 Subject: [PATCH 121/223] First CI --- compose_ci.yml | 101 +++++++++++++++++++++++++++++++++++++++++++++++++ fabfile.py | 75 ++++++++++++++++++++++++++++++++++++ gitlab-ci.yml | 60 +++++++++++++++++++++++++++++ 3 files changed, 236 insertions(+) create mode 100644 compose_ci.yml create mode 100644 fabfile.py create mode 100644 gitlab-ci.yml diff --git a/compose_ci.yml b/compose_ci.yml new file mode 100644 index 00000000..3c41f200 --- /dev/null +++ b/compose_ci.yml @@ -0,0 +1,101 @@ +version: '3.5' +services: + # PostgreSQL database + db: + build: + context: ./_dockerfiles/db + dockerfile: Dockerfile + hostname: db + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + - POSTGRES_DB=postgres + ports: + - "5436:5432" + volumes: + - gm-db:/var/lib/postgresql/data/ + + elasticsearch: + image: elasticsearch:7.3.1 + volumes: + - gm-esdata:/usr/share/elasticsearch/data + hostname: elasticsearch + ports: + - 9200:9200 + - 9300:9300 + environment: + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + - discovery.type=single-node + - xpack.security.enabled=false + + # Redis + redis: + image: redis:2.8.23 + ports: + - "6379:6379" + + # Celery + worker: + build: . + command: ./run_celery.sh + environment: + - SETTINGS_CONFIGURATION=local + - DB_NAME=postgres + - DB_USERNAME=postgres + - DB_HOSTNAME=db + - DB_PORT=5432 + - DB_PASSWORD=postgres + volumes: + - .:/code + links: + - db + - redis + + worker_beat: + build: . + command: ./run_celery_beat.sh + environment: + - SETTINGS_CONFIGURATION=local + - DB_NAME=postgres + - DB_USERNAME=postgres + - DB_HOSTNAME=db + - DB_PORT=5432 + - DB_PASSWORD=postgres + volumes: + - .:/code + links: + - db + - redis + + + # App: G&M + gm_app: + build: . + command: python manage.py runserver 0.0.0.0:8000 + environment: + - SETTINGS_CONFIGURATION=local + - DB_HOSTNAME=db + - DB_PORT=5432 + - DB_NAME=postgres + - DB_USERNAME=postgres + - DB_PASSWORD=postgres + depends_on: + - db + - redis + - worker + - worker_beat + - elasticsearch + volumes: + - .:/code + - gm-media:/media-data + ports: + - "8000:8000" + +volumes: + gm-db: + name: gm-db + + gm-media: + name: gm-media + + gm-esdata: \ No newline at end of file diff --git a/fabfile.py b/fabfile.py new file mode 100644 index 00000000..643080be --- /dev/null +++ b/fabfile.py @@ -0,0 +1,75 @@ +import os # NOQA +from fabric.api import * # NOQA + +user = 'gm' + +env.root = '~/' +env.src = '~/project' + +env.default_branch = 'feature/develop_ci' +env.tmpdir = '~/tmp' + + +env.roledefs = { + 'develop': { + 'branch': env.default_branch, + 'hosts': ['%s@rock.spider.ru:31' % user, ] + }, + 'staging': { + 'hosts': ['%s@5.200.53.99' % user, ] + }, + 'production': { + 'branch': 'master', + 'hosts': ['%s@87.226.166.80' % user, ] + } +} + + +def fetch(branch=None): + with cd(env.src): + role = env.roles[0] + run('git pull origin {}'.format(env.roledefs[role]['branch'])) + run('git submodule update') + + +def migrate(): + with cd(env.src): + run('./manage.py migrate') + + +def install_requirements(): + with cd(env.src): + run('pip install -r requirements/base.txt') + + +def touch(): + with cd(env.src): + run('touch ~/%s.touch' % user) + + +def kill_celery(): + """Kill celery workers for $user.""" + with cd(env.src): + run('ps -u %s -o pid,fname | grep celery | (while read a b; do kill -9 $a; done;)' % user) + + +def collectstatic(): + with cd(env.src): + run('./manage.py collectstatic --noinput') + + +def deploy(branch=None): + fetch() + install_requirements() + migrate() + collectstatic() + touch() + kill_celery() + + +def rev(): + """Show head commit.""" + with hide('running', 'stdout'): + with cd(env.src): + commit = run('git rev-parse HEAD') + return local('git show -q %s' % commit) diff --git a/gitlab-ci.yml b/gitlab-ci.yml new file mode 100644 index 00000000..0de48055 --- /dev/null +++ b/gitlab-ci.yml @@ -0,0 +1,60 @@ +image: docker:latest + +stages: + - hello + +#stages: +# - build +# - test +# - deploy +# - clean +# + +before_script: + - apk add --update python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make + - pip install docker-compose + + +hello: + - echo 'Test GitLab CI' + +#clean: +# stage: clean +# script: +# - docker-compose -f compose-ci.yml stop +# - docker-compose -f compose-ci.yml rm --force gm_app +# when: always +# +# +#buid: +# stage: build +# script: +# - docker-compose -f compose-ci.yml build gm_app +# when: always +# +# +#test: +# stage: test +# script: +# - docker-compose -f compose-ci.yml run agro python manage.py test -v 3 --noinput +# when: always +# +# +#deploy-develop: +# stage: deploy +# only: +# - feature/develop_ci +# script: +# - fab --roles=develop deploy +# environment: +# name: Develop +# +# +##deploy-staging: +## stage: deploy +## only: +## - master +## script: +## - fab --roles=staging deploy +## environment: +## name: Staging From 580081f76d6a107300d8ff26c4646988b4ade911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Tue, 22 Oct 2019 15:41:10 +0300 Subject: [PATCH 122/223] 1 --- gitlab-ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gitlab-ci.yml b/gitlab-ci.yml index 0de48055..aaa3fb35 100644 --- a/gitlab-ci.yml +++ b/gitlab-ci.yml @@ -16,7 +16,10 @@ before_script: hello: - - echo 'Test GitLab CI' + script: + - echo 'Test GitLab CI' + only: + - feature/develop_ci #clean: # stage: clean From 307abddbbf12cc383ee0d59774114a232d8a94ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Tue, 22 Oct 2019 15:43:26 +0300 Subject: [PATCH 123/223] 1 --- gitlab-ci.yml => .gitlab-ci.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename gitlab-ci.yml => .gitlab-ci.yml (100%) diff --git a/gitlab-ci.yml b/.gitlab-ci.yml similarity index 100% rename from gitlab-ci.yml rename to .gitlab-ci.yml From ba58b74fdff200711fb106830b9cd5905652e0ff Mon Sep 17 00:00:00 2001 From: "v.gladkikh" Date: Tue, 22 Oct 2019 12:44:48 +0000 Subject: [PATCH 124/223] Update .gitlab-ci.yml --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index aaa3fb35..162d90bd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,8 +18,8 @@ before_script: hello: script: - echo 'Test GitLab CI' - only: - - feature/develop_ci + only: + - feature/develop_ci #clean: # stage: clean From e70efe36094ba63329c3f0efa9ca429a6e8b5dc6 Mon Sep 17 00:00:00 2001 From: "v.gladkikh" Date: Tue, 22 Oct 2019 12:45:53 +0000 Subject: [PATCH 125/223] Update .gitlab-ci.yml --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 162d90bd..9995a2e4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -16,6 +16,7 @@ before_script: hello: + stage: hello script: - echo 'Test GitLab CI' only: From 4c9ea2e0e7a7187c0b0b5f36cc319f0aab9b03c2 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Tue, 22 Oct 2019 15:45:27 +0300 Subject: [PATCH 126/223] 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 e1e3799c5f68887dd3eddacc240c523e0beb1d04 Mon Sep 17 00:00:00 2001 From: "v.gladkikh" Date: Tue, 22 Oct 2019 12:49:47 +0000 Subject: [PATCH 127/223] Update .gitlab-ci.yml --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9995a2e4..7bcfe10c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,9 +10,9 @@ stages: # - clean # -before_script: - - apk add --update python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make - - pip install docker-compose +# before_script: +# - apk add --update python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make +# - pip install docker-compose hello: From 071b63465942391e02ddfcba114101a3dc80d33a Mon Sep 17 00:00:00 2001 From: Anatoly Date: Tue, 22 Oct 2019 15:58:00 +0300 Subject: [PATCH 128/223] 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 129/223] 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 130/223] 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 131/223] 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 132/223] 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 778162922e16049bae9ab56158f8c277332efef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 10:39:10 +0300 Subject: [PATCH 133/223] Test runner --- .gitignore | 4 +++- .gitlab-ci.yml | 57 +++++++++++++++++++------------------------- celerybeat-schedule | Bin 12845 -> 0 bytes 3 files changed, 27 insertions(+), 34 deletions(-) delete mode 100644 celerybeat-schedule diff --git a/.gitignore b/.gitignore index a32ff3df..5d44eda8 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,6 @@ logs/ /geoip_db/ # dev -./docker-compose.override.yml \ No newline at end of file +./docker-compose.override.yml + +celerybeat-schedule diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index aaa3fb35..b43dc9dc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,48 +1,39 @@ image: docker:latest stages: - - hello - -#stages: -# - build -# - test + - build + - test # - deploy -# - clean -# + - clean + before_script: - apk add --update python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make - pip install docker-compose -hello: +clean: + stage: clean script: - - echo 'Test GitLab CI' - only: - - feature/develop_ci + - docker-compose -f compose-ci.yml stop + - docker-compose -f compose-ci.yml rm --force gm_app + when: always + + +buid: + stage: build + script: + - docker-compose -f compose-ci.yml build gm_app + when: always + + +test: + stage: test + script: + - docker-compose -f compose-ci.yml run agro python manage.py test -v 3 --noinput + when: always + -#clean: -# stage: clean -# script: -# - docker-compose -f compose-ci.yml stop -# - docker-compose -f compose-ci.yml rm --force gm_app -# when: always -# -# -#buid: -# stage: build -# script: -# - docker-compose -f compose-ci.yml build gm_app -# when: always -# -# -#test: -# stage: test -# script: -# - docker-compose -f compose-ci.yml run agro python manage.py test -v 3 --noinput -# when: always -# -# #deploy-develop: # stage: deploy # only: diff --git a/celerybeat-schedule b/celerybeat-schedule deleted file mode 100644 index 3efe528a0f0c9bb6e563876fdc4c1a02541e62f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12845 zcmeI#O=}ZD7zgl4H!sbkY1*c~KvBH46}rS41i^z)JQYQ@6$66HW;3wCxXqKDhzSI8 zD*6@t0OC>n0($l0#iJ+jD0uZE=uBo(69^u1C@TLfyU)z-W@h)dGY`8TUfeIU6~;!Z z3OeR(e5vD2yxPdjEOT_n(NO&36UWZAq>0KOKKK!`TSH0S=P_ zro8d+&Bd+^{hf*YjQ7dkPRO6e0uJ*z1kb;fMJzka&aa$mMt>8+b3V_vuMf}5K4Y8q zdwD@Mqd%GdHc$P`n<5!H17faz0q!KtquBR{PA#J@x9VPXE9!{s51l)%PQW4#>MhY2 zI?vq6|JKf`vqRAmot|3{!bc)*_+d)~akovWaC|@nk{^VTXmnfRmg>33X<;i$MX9h6 zNFke#1wSc}v&VB^a_0&cuCu!jC>^$Yvd{f{hkKkd+a6_)Dd*ARJ{z~5*oG4xc8+2h z)H&t1YAJPH72=~K{oMk_QYagRb&t*@?k}_khc*BJ From 9383c806b0b6467f48665d1a08a71e99baa9ecf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 10:45:29 +0300 Subject: [PATCH 134/223] Fix --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9c5694f4..7414f21f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,9 +7,9 @@ stages: - clean -# before_script: -# - apk add --update python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make -# - pip install docker-compose + before_script: + - apk add --update python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make + - pip install docker-compose clean: stage: clean From 19facc3b94c47aefa7f8b4ea9abab4a5192a2a56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 10:46:56 +0300 Subject: [PATCH 135/223] Fix --- .gitlab-ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7414f21f..a364ad5a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,8 +6,7 @@ stages: # - deploy - clean - - before_script: +before_script: - apk add --update python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make - pip install docker-compose From f11a194eb2f9db7101834f1fc48e0ed0c7a3d9a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 10:49:41 +0300 Subject: [PATCH 136/223] Fix --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a364ad5a..b4d0e418 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,7 +7,8 @@ stages: - clean before_script: - - apk add --update python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make + - apt install python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make +# - apk add --update python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make - pip install docker-compose clean: From fea2764bde617193a3dce57d7af4f99a22d2afe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 10:51:09 +0300 Subject: [PATCH 137/223] Fix --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b4d0e418..a04a6a03 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,7 +7,7 @@ stages: - clean before_script: - - apt install python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make + - sudo apt install -y python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make # - apk add --update python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make - pip install docker-compose From 0961d3ee7360061b50bfaa26f2043d3cf4fe29c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 10:57:08 +0300 Subject: [PATCH 138/223] Fix --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a04a6a03..1e64cf22 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,7 +7,7 @@ stages: - clean before_script: - - sudo apt install -y python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make + - sudo apt install python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make # - apk add --update python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make - pip install docker-compose From 89c949c12c2200d4a1e66d0b423cfa0ebcd8620d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 10:59:04 +0300 Subject: [PATCH 139/223] Fix --- .gitlab-ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1e64cf22..d36fdf49 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,10 +6,10 @@ stages: # - deploy - clean -before_script: - - sudo apt install python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make -# - apk add --update python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make - - pip install docker-compose +#before_script: +# - sudo apt install python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make +## - apk add --update python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make +# - pip install docker-compose clean: stage: clean From 78209a11015a9bacacc8d8e98e57fdfae77b3f06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 11:02:43 +0300 Subject: [PATCH 140/223] pwd --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d36fdf49..f730977d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,6 +14,7 @@ stages: clean: stage: clean script: + - pwd - docker-compose -f compose-ci.yml stop - docker-compose -f compose-ci.yml rm --force gm_app when: always From dd7a578a12c497d983e37e7409f93a2166afbca2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 11:04:52 +0300 Subject: [PATCH 141/223] pwd --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f730977d..e3163c20 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,7 +14,6 @@ stages: clean: stage: clean script: - - pwd - docker-compose -f compose-ci.yml stop - docker-compose -f compose-ci.yml rm --force gm_app when: always @@ -23,6 +22,7 @@ clean: buid: stage: build script: + - pwd - docker-compose -f compose-ci.yml build gm_app when: always From cddab519e4dba53ba38954e850230b71ea2b927f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 11:07:01 +0300 Subject: [PATCH 142/223] pwd --- .gitlab-ci.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e3163c20..88ec7752 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,21 +2,21 @@ image: docker:latest stages: - build - - test +# - test # - deploy - - clean +# - clean #before_script: # - sudo apt install python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make ## - apk add --update python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make # - pip install docker-compose -clean: - stage: clean - script: - - docker-compose -f compose-ci.yml stop - - docker-compose -f compose-ci.yml rm --force gm_app - when: always +#clean: +# stage: clean +# script: +# - docker-compose -f compose-ci.yml stop +# - docker-compose -f compose-ci.yml rm --force gm_app +# when: always buid: @@ -27,11 +27,11 @@ buid: when: always -test: - stage: test - script: - - docker-compose -f compose-ci.yml run agro python manage.py test -v 3 --noinput - when: always +#test: +# stage: test +# script: +# - docker-compose -f compose-ci.yml run agro python manage.py test -v 3 --noinput +# when: always #clean: From 5c7af176fdaced52c170911c661afe43656a7bd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 11:09:11 +0300 Subject: [PATCH 143/223] pwd --- .gitlab-ci.yml | 26 +++++++++++++------------- compose_ci.yml => compose-ci.yml | 0 2 files changed, 13 insertions(+), 13 deletions(-) rename compose_ci.yml => compose-ci.yml (100%) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 88ec7752..e3163c20 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,21 +2,21 @@ image: docker:latest stages: - build -# - test + - test # - deploy -# - clean + - clean #before_script: # - sudo apt install python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make ## - apk add --update python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make # - pip install docker-compose -#clean: -# stage: clean -# script: -# - docker-compose -f compose-ci.yml stop -# - docker-compose -f compose-ci.yml rm --force gm_app -# when: always +clean: + stage: clean + script: + - docker-compose -f compose-ci.yml stop + - docker-compose -f compose-ci.yml rm --force gm_app + when: always buid: @@ -27,11 +27,11 @@ buid: when: always -#test: -# stage: test -# script: -# - docker-compose -f compose-ci.yml run agro python manage.py test -v 3 --noinput -# when: always +test: + stage: test + script: + - docker-compose -f compose-ci.yml run agro python manage.py test -v 3 --noinput + when: always #clean: diff --git a/compose_ci.yml b/compose-ci.yml similarity index 100% rename from compose_ci.yml rename to compose-ci.yml From 1755c192b6f71c0a8eee14a9e915a14d76db7f9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 11:10:15 +0300 Subject: [PATCH 144/223] pwd --- compose-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose-ci.yml b/compose-ci.yml index 3c41f200..3920500f 100644 --- a/compose-ci.yml +++ b/compose-ci.yml @@ -1,4 +1,4 @@ -version: '3.5' +version: '2.0' services: # PostgreSQL database db: From fd65c986604c4ba024539ffaf1cb64cabbfa3abe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 11:12:53 +0300 Subject: [PATCH 145/223] pwd --- compose-ci.yml | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/compose-ci.yml b/compose-ci.yml index 3920500f..fdab41b2 100644 --- a/compose-ci.yml +++ b/compose-ci.yml @@ -1,6 +1,5 @@ -version: '2.0' +version: '2' services: - # PostgreSQL database db: build: context: ./_dockerfiles/db @@ -91,11 +90,11 @@ services: ports: - "8000:8000" -volumes: - gm-db: - name: gm-db - - gm-media: - name: gm-media - - gm-esdata: \ No newline at end of file +#volumes: +# gm-db: +# name: gm-db +# +# gm-media: +# name: gm-media +# +# gm-esdata: \ No newline at end of file From d6a9fa6ac0864bcc0b627b5bbba96c15ab226d85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 11:18:26 +0300 Subject: [PATCH 146/223] pwd --- compose-ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compose-ci.yml b/compose-ci.yml index fdab41b2..83b20a44 100644 --- a/compose-ci.yml +++ b/compose-ci.yml @@ -90,11 +90,11 @@ services: ports: - "8000:8000" -#volumes: -# gm-db: +volumes: + gm-db: # name: gm-db # -# gm-media: + gm-media: # name: gm-media # -# gm-esdata: \ No newline at end of file + gm-esdata: \ No newline at end of file From 97823ea6012cb660495c34d998e7b881add06417 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 23 Oct 2019 11:25:04 +0300 Subject: [PATCH 147/223] 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 148/223] 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 7ed853e7a4b6559358d39fde780c5acd07f4cab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 11:44:59 +0300 Subject: [PATCH 149/223] Fix fab --- .gitlab-ci.yml | 24 ------------------------ fabfile.py | 21 +++++++++++---------- 2 files changed, 11 insertions(+), 34 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e3163c20..7668f645 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,7 +22,6 @@ clean: buid: stage: build script: - - pwd - docker-compose -f compose-ci.yml build gm_app when: always @@ -34,29 +33,6 @@ test: when: always -#clean: -# stage: clean -# script: -# - docker-compose -f compose-ci.yml stop -# - docker-compose -f compose-ci.yml rm --force gm_app -# when: always -# -# -#buid: -# stage: build -# script: -# - docker-compose -f compose-ci.yml build gm_app -# when: always -# -# -#test: -# stage: test -# script: -# - docker-compose -f compose-ci.yml run agro python manage.py test -v 3 --noinput -# when: always -# -# - #deploy-develop: # stage: deploy # only: diff --git a/fabfile.py b/fabfile.py index 643080be..90052b4c 100644 --- a/fabfile.py +++ b/fabfile.py @@ -1,18 +1,12 @@ -import os # NOQA +import os # NOQA from fabric.api import * # NOQA + user = 'gm' -env.root = '~/' -env.src = '~/project' - -env.default_branch = 'feature/develop_ci' -env.tmpdir = '~/tmp' - - env.roledefs = { 'develop': { - 'branch': env.default_branch, + 'branch': 'develop', 'hosts': ['%s@rock.spider.ru:31' % user, ] }, 'staging': { @@ -25,6 +19,13 @@ env.roledefs = { } +env.root = '~/' +env.src = '~/project' + +env.default_branch = 'develop' +env.tmpdir = '~/tmp' + + def fetch(branch=None): with cd(env.src): role = env.roles[0] @@ -72,4 +73,4 @@ def rev(): with hide('running', 'stdout'): with cd(env.src): commit = run('git rev-parse HEAD') - return local('git show -q %s' % commit) + return local('git show -q %s' % commit) \ No newline at end of file From 1b218a5c0754893c7d4343722e6f1ad841e72bb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 11:51:25 +0300 Subject: [PATCH 150/223] Fix fab --- .gitlab-ci.yml | 15 ++++++++++----- fabfile.py | 3 +++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7668f645..76a23eaf 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,13 +1,18 @@ image: docker:latest stages: - - build - - test -# - deploy - - clean + - fabtest +# - build +# - test +## - deploy +# - clean + +fabtest: + stage: fabtest + script: + - fab test #before_script: -# - sudo apt install python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make ## - apk add --update python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make # - pip install docker-compose diff --git a/fabfile.py b/fabfile.py index 90052b4c..c06e42d0 100644 --- a/fabfile.py +++ b/fabfile.py @@ -2,6 +2,9 @@ import os # NOQA from fabric.api import * # NOQA +def test(): + print('Fab test') + user = 'gm' env.roledefs = { From 4d1d16637e6329bd0fe5e87928ae4032ed7249cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 12:01:46 +0300 Subject: [PATCH 151/223] add deploy --- .gitlab-ci.yml | 50 ++++++++++++++++++++++---------------------------- fabfile.py | 4 +--- 2 files changed, 23 insertions(+), 31 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 76a23eaf..78bcc748 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,16 +1,10 @@ image: docker:latest stages: - - fabtest -# - build -# - test -## - deploy -# - clean - -fabtest: - stage: fabtest - script: - - fab test + - build + - test + - deploy + - clean #before_script: ## - apk add --update python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make @@ -38,21 +32,21 @@ test: when: always -#deploy-develop: -# stage: deploy -# only: -# - feature/develop_ci -# script: -# - fab --roles=develop deploy -# environment: -# name: Develop -# -# -##deploy-staging: -## stage: deploy -## only: -## - master -## script: -## - fab --roles=staging deploy -## environment: -## name: Staging +deploy-develop: + stage: deploy + only: + - feature/develop_ci + script: + - fab --roles=develop deploy + environment: + name: Develop + + +deploy-staging: + stage: deploy + only: + - master + script: + - fab --roles=staging deploy + environment: + name: Staging diff --git a/fabfile.py b/fabfile.py index c06e42d0..d49b2e03 100644 --- a/fabfile.py +++ b/fabfile.py @@ -2,10 +2,8 @@ import os # NOQA from fabric.api import * # NOQA -def test(): - print('Fab test') -user = 'gm' +user = 'gitlab-runner' env.roledefs = { 'develop': { From 64692ae4b371b4f61c6a28d3e31b0f7732fc4b7c Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Wed, 23 Oct 2019 12:03:50 +0300 Subject: [PATCH 152/223] 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 ed551ea1ab216e0bea679073b018dc29c05a3747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 12:14:21 +0300 Subject: [PATCH 153/223] add deploy --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 78bcc748..a4d335b5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -28,7 +28,7 @@ buid: test: stage: test script: - - docker-compose -f compose-ci.yml run agro python manage.py test -v 3 --noinput + - docker-compose -f compose-ci.yml run gm_app python manage.py test -v 3 --noinput when: always From c1b51745186625cdf1a48a5e94b7cf6a76e0b775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 12:15:00 +0300 Subject: [PATCH 154/223] add deploy --- fabfile.py | 1 - 1 file changed, 1 deletion(-) diff --git a/fabfile.py b/fabfile.py index d49b2e03..6f300c37 100644 --- a/fabfile.py +++ b/fabfile.py @@ -31,7 +31,6 @@ def fetch(branch=None): with cd(env.src): role = env.roles[0] run('git pull origin {}'.format(env.roledefs[role]['branch'])) - run('git submodule update') def migrate(): From 9c9c49d9008c5ca190de4bbd7b1c262a849d9bc4 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Wed, 23 Oct 2019 12:25:50 +0300 Subject: [PATCH 155/223] 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 156/223] 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 157/223] 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 158/223] 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 239ebc1dd6e7fa6d9cfa1f7c75efb218ada6533a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 14:11:02 +0300 Subject: [PATCH 159/223] add deploy --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a4d335b5..a617263b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -28,6 +28,8 @@ buid: test: stage: test script: + - docker-compose -f compose-ci.yml run db up -d + - docker-compose -f compose-ci.yml run db down - docker-compose -f compose-ci.yml run gm_app python manage.py test -v 3 --noinput when: always From 05cccf85eab1e3d0702fabf06b0b86f90cc9e7c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 14:22:03 +0300 Subject: [PATCH 160/223] add deploy --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a617263b..36bfb0f9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -34,6 +34,7 @@ test: when: always + deploy-develop: stage: deploy only: From 81bd7b491230855d42551c7d7e15322445f8ba49 Mon Sep 17 00:00:00 2001 From: Semyon Yekhmenin Date: Wed, 23 Oct 2019 11:58:33 +0000 Subject: [PATCH 161/223] 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 46fdf3e55d94a2efff19734656d9cc8210882517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 15:15:03 +0300 Subject: [PATCH 162/223] delete celery, redis --- compose-ci.yml | 63 +------------------------------------------------- 1 file changed, 1 insertion(+), 62 deletions(-) diff --git a/compose-ci.yml b/compose-ci.yml index 83b20a44..b412e7a5 100644 --- a/compose-ci.yml +++ b/compose-ci.yml @@ -14,59 +14,6 @@ services: volumes: - gm-db:/var/lib/postgresql/data/ - elasticsearch: - image: elasticsearch:7.3.1 - volumes: - - gm-esdata:/usr/share/elasticsearch/data - hostname: elasticsearch - ports: - - 9200:9200 - - 9300:9300 - environment: - - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - - discovery.type=single-node - - xpack.security.enabled=false - - # Redis - redis: - image: redis:2.8.23 - ports: - - "6379:6379" - - # Celery - worker: - build: . - command: ./run_celery.sh - environment: - - SETTINGS_CONFIGURATION=local - - DB_NAME=postgres - - DB_USERNAME=postgres - - DB_HOSTNAME=db - - DB_PORT=5432 - - DB_PASSWORD=postgres - volumes: - - .:/code - links: - - db - - redis - - worker_beat: - build: . - command: ./run_celery_beat.sh - environment: - - SETTINGS_CONFIGURATION=local - - DB_NAME=postgres - - DB_USERNAME=postgres - - DB_HOSTNAME=db - - DB_PORT=5432 - - DB_PASSWORD=postgres - volumes: - - .:/code - links: - - db - - redis - - # App: G&M gm_app: build: . @@ -80,10 +27,6 @@ services: - DB_PASSWORD=postgres depends_on: - db - - redis - - worker - - worker_beat - - elasticsearch volumes: - .:/code - gm-media:/media-data @@ -92,9 +35,5 @@ services: volumes: gm-db: -# name: gm-db -# + gm-media: -# name: gm-media -# - gm-esdata: \ No newline at end of file From 6a9f11e98960892d3e9a0d1eb6792a0760b3452f Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 23 Oct 2019 15:31:04 +0300 Subject: [PATCH 163/223] 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 9588c7cd881a61d92f85f7035d2ad31c09b669f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 15:35:23 +0300 Subject: [PATCH 164/223] delete celery, redis --- compose-ci.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/compose-ci.yml b/compose-ci.yml index b412e7a5..f5697f6b 100644 --- a/compose-ci.yml +++ b/compose-ci.yml @@ -11,8 +11,8 @@ services: - POSTGRES_DB=postgres ports: - "5436:5432" - volumes: - - gm-db:/var/lib/postgresql/data/ +# volumes: +# - gm-db:/var/lib/postgresql/data/ # App: G&M gm_app: @@ -27,13 +27,13 @@ services: - DB_PASSWORD=postgres depends_on: - db - volumes: - - .:/code - - gm-media:/media-data +# volumes: +# - .:/code +# - gm-media:/media-data ports: - "8000:8000" -volumes: - gm-db: - - gm-media: +#volumes: +# gm-db: +# +# gm-media: From 7613f67f912571a2d7e54df950107ad38557148d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 15:44:58 +0300 Subject: [PATCH 165/223] delete celery, redis --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 36bfb0f9..758a52f9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -30,7 +30,7 @@ test: script: - docker-compose -f compose-ci.yml run db up -d - docker-compose -f compose-ci.yml run db down - - docker-compose -f compose-ci.yml run gm_app python manage.py test -v 3 --noinput + - docker-compose -f compose-ci.yml run gm_app python -B manage.py test -v 3 --noinput when: always From 052ff07ac7b8b9a825e53ce6e88f3c462a221a7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 15:47:51 +0300 Subject: [PATCH 166/223] delete celery, redis --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 758a52f9..3c60aac4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: docker:latest +#image: docker:latest stages: - build From 5668da8e79e5da76ba1d10848997ea43b725389c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 16:03:50 +0300 Subject: [PATCH 167/223] delete celery, redis --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3c60aac4..758a52f9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -#image: docker:latest +image: docker:latest stages: - build From 649fcc67935e42ec2c991585d76c21be7906d86c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 16:25:57 +0300 Subject: [PATCH 168/223] delete celery, redis --- compose-ci.yml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/compose-ci.yml b/compose-ci.yml index f5697f6b..a322ed56 100644 --- a/compose-ci.yml +++ b/compose-ci.yml @@ -11,8 +11,6 @@ services: - POSTGRES_DB=postgres ports: - "5436:5432" -# volumes: -# - gm-db:/var/lib/postgresql/data/ # App: G&M gm_app: @@ -27,13 +25,5 @@ services: - DB_PASSWORD=postgres depends_on: - db -# volumes: -# - .:/code -# - gm-media:/media-data ports: - "8000:8000" - -#volumes: -# gm-db: -# -# gm-media: From d8c99062ca480fe2040ed97025fb0bd09efd1041 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Wed, 23 Oct 2019 16:28:42 +0300 Subject: [PATCH 169/223] 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 170/223] 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 1f44f21ede8e8f0a2394519a7b854dd864eb736f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 16:52:12 +0300 Subject: [PATCH 171/223] delete celery, redis --- .gitlab-ci.yml | 31 ++++++++++--------------------- fabfile.py | 11 ++--------- 2 files changed, 12 insertions(+), 30 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 758a52f9..6d852c07 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,7 +3,7 @@ image: docker:latest stages: - build - test - - deploy +# - deploy - clean #before_script: @@ -28,28 +28,17 @@ buid: test: stage: test script: - - docker-compose -f compose-ci.yml run db up -d - - docker-compose -f compose-ci.yml run db down + - docker-compose -f compose-ci.yml run db - docker-compose -f compose-ci.yml run gm_app python -B manage.py test -v 3 --noinput when: always -deploy-develop: - stage: deploy - only: - - feature/develop_ci - script: - - fab --roles=develop deploy - environment: - name: Develop - - -deploy-staging: - stage: deploy - only: - - master - script: - - fab --roles=staging deploy - environment: - name: Staging +#deploy-develop: +# stage: deploy +# only: +# - feature/develop_ci +# script: +# - fab --roles=develop deploy +# environment: +# name: Develop diff --git a/fabfile.py b/fabfile.py index 6f300c37..9ad7f871 100644 --- a/fabfile.py +++ b/fabfile.py @@ -3,19 +3,12 @@ from fabric.api import * # NOQA -user = 'gitlab-runner' +user = 'gm' env.roledefs = { 'develop': { 'branch': 'develop', - 'hosts': ['%s@rock.spider.ru:31' % user, ] - }, - 'staging': { - 'hosts': ['%s@5.200.53.99' % user, ] - }, - 'production': { - 'branch': 'master', - 'hosts': ['%s@87.226.166.80' % user, ] + 'hosts': ['%s@95.213.204.126' % user, ] } } From d1e40101b44f57f6953f508ada5bdd39558df0f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 16:59:11 +0300 Subject: [PATCH 172/223] delete celery, redis --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6d852c07..75e0f8d4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -28,7 +28,7 @@ buid: test: stage: test script: - - docker-compose -f compose-ci.yml run db + - docker-compose -f compose-ci.yml up db - docker-compose -f compose-ci.yml run gm_app python -B manage.py test -v 3 --noinput when: always From b1169675a8c9915d7a2aadfb621780e7ca8e93fa Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Wed, 23 Oct 2019 17:03:24 +0300 Subject: [PATCH 173/223] 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 174/223] 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 175/223] 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 40e64eafe71e146e6ff646eddc37895cec817030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 17:08:44 +0300 Subject: [PATCH 176/223] delete celery, redis --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 75e0f8d4..fe878f7f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -28,8 +28,8 @@ buid: test: stage: test script: - - docker-compose -f compose-ci.yml up db - - docker-compose -f compose-ci.yml run gm_app python -B manage.py test -v 3 --noinput +# - docker-compose -f compose-ci.yml build + - docker-compose -f compose-ci.yml run gm_app python manage.py test -v 3 --noinput when: always From 1092ec18847d15ac50227819a7077a8448638d00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 17:24:21 +0300 Subject: [PATCH 177/223] delete celery, redis --- compose-ci.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/compose-ci.yml b/compose-ci.yml index a322ed56..c2a154cd 100644 --- a/compose-ci.yml +++ b/compose-ci.yml @@ -12,6 +12,19 @@ services: ports: - "5436:5432" + elasticsearch: + image: elasticsearch:7.3.1 + volumes: + - gm-esdata:/usr/share/elasticsearch/data + hostname: elasticsearch + ports: + - 9200:9200 + - 9300:9300 + environment: + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + - discovery.type=single-node + - xpack.security.enabled=false + # App: G&M gm_app: build: . @@ -25,5 +38,6 @@ services: - DB_PASSWORD=postgres depends_on: - db + - elasticsearch ports: - "8000:8000" From 2e99431bdd1d0a5eff78b561bd39dc31e94037ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 17:25:28 +0300 Subject: [PATCH 178/223] elastic --- compose-ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/compose-ci.yml b/compose-ci.yml index c2a154cd..53ede064 100644 --- a/compose-ci.yml +++ b/compose-ci.yml @@ -14,8 +14,6 @@ services: elasticsearch: image: elasticsearch:7.3.1 - volumes: - - gm-esdata:/usr/share/elasticsearch/data hostname: elasticsearch ports: - 9200:9200 From 40357c20b6403e0efed74b0ccd329372101f08e4 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 14:11:07 +0300 Subject: [PATCH 179/223] 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 180/223] 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 181/223] 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 182/223] 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 183/223] 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 184/223] 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 5a0ea251caa0ea69f7e5971f48bd5eae7adc0d61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 17:35:33 +0300 Subject: [PATCH 185/223] elastic --- compose-ci.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/compose-ci.yml b/compose-ci.yml index 53ede064..66c767c4 100644 --- a/compose-ci.yml +++ b/compose-ci.yml @@ -23,6 +23,41 @@ services: - discovery.type=single-node - xpack.security.enabled=false + # Redis + redis: + image: redis:2.8.23 + ports: + - "6379:6379" + + # Celery + worker: + build: . + command: ./run_celery.sh + environment: + - SETTINGS_CONFIGURATION=local + - DB_NAME=postgres + - DB_USERNAME=postgres + - DB_HOSTNAME=db + - DB_PORT=5432 + - DB_PASSWORD=postgres + links: + - db + - redis + + worker_beat: + build: . + command: ./run_celery_beat.sh + environment: + - SETTINGS_CONFIGURATION=local + - DB_NAME=postgres + - DB_USERNAME=postgres + - DB_HOSTNAME=db + - DB_PORT=5432 + - DB_PASSWORD=postgres + links: + - db + - redis + # App: G&M gm_app: build: . From 18062e60a560c56976ab5cf16315b499b5979c83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Wed, 23 Oct 2019 17:36:13 +0300 Subject: [PATCH 186/223] elastic --- compose-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compose-ci.yml b/compose-ci.yml index 66c767c4..94079822 100644 --- a/compose-ci.yml +++ b/compose-ci.yml @@ -71,6 +71,9 @@ services: - DB_PASSWORD=postgres depends_on: - db + - redis + - worker + - worker_beat - elasticsearch ports: - "8000:8000" From 1e3c80ad0cda529a5f5c0575224308f03833c38e Mon Sep 17 00:00:00 2001 From: Anatoly Date: Wed, 23 Oct 2019 17:40:42 +0300 Subject: [PATCH 187/223] 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 188/223] 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 189/223] 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 f45f69e0e561828c4af907cc72e524356b9defbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Thu, 24 Oct 2019 09:47:29 +0300 Subject: [PATCH 190/223] add deploy --- .gitlab-ci.yml | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fe878f7f..94dda56d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,12 +3,9 @@ image: docker:latest stages: - build - test -# - deploy + - deploy - clean -#before_script: -## - apk add --update python python-dev py-pip gcc libc-dev libffi-dev openssl-dev make -# - pip install docker-compose clean: stage: clean @@ -28,17 +25,16 @@ buid: test: stage: test script: -# - docker-compose -f compose-ci.yml build - docker-compose -f compose-ci.yml run gm_app python manage.py test -v 3 --noinput when: always -#deploy-develop: -# stage: deploy -# only: -# - feature/develop_ci -# script: -# - fab --roles=develop deploy -# environment: -# name: Develop +deploy-develop: + stage: deploy + only: + - develop + script: + - fab --roles=develop deploy + environment: + name: Develop \ No newline at end of file From 890d1004e14231914522b8d3c917d98e155b077c Mon Sep 17 00:00:00 2001 From: Semyon Yekhmenin Date: Thu, 24 Oct 2019 07:44:47 +0000 Subject: [PATCH 191/223] 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 192/223] 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'), + ), + ] From 0edc2fae38aa3060c38c03ec55454f23d924296a Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Thu, 24 Oct 2019 12:03:41 +0300 Subject: [PATCH 193/223] Clean news serializer --- apps/news/serializers.py | 24 ++---------------------- apps/news/views.py | 4 +--- 2 files changed, 3 insertions(+), 25 deletions(-) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 6814965a..c23ad2c3 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -129,11 +129,6 @@ class NewsTypeSerializer(serializers.ModelSerializer): class NewsBaseSerializer(ProjectModelSerializer): """Base 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) @@ -155,27 +150,12 @@ class NewsBaseSerializer(ProjectModelSerializer): 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: + class Meta(NewsBaseSerializer.Meta): """Meta class.""" - model = models.News - fields = ( - 'id', - 'title_translated', - 'subtitle_translated', - 'is_highlighted', - 'news_type', - 'tags', - 'slug', + fields = NewsBaseSerializer.Meta.fields + ( 'image', ) diff --git a/apps/news/views.py b/apps/news/views.py index c2a71eca..e0034afa 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 @@ -17,7 +15,6 @@ class NewsMixinView: """News mixin.""" permission_classes = (permissions.AllowAny, ) - serializer_class = serializers.NewsBaseSerializer def get_queryset(self, *args, **kwargs): """Override get_queryset method.""" @@ -30,6 +27,7 @@ class NewsMixinView: class NewsListView(NewsMixinView, generics.ListAPIView): """News list view.""" + serializer_class = serializers.NewsListSerializer filter_class = filters.NewsListFilterSet From 6c4f1a2e40d37613583369e21f4817a9b73baa0d Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Thu, 24 Oct 2019 12:08:44 +0300 Subject: [PATCH 194/223] Fix news serializer --- apps/news/serializers.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index c23ad2c3..cd7afff4 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -129,6 +129,8 @@ class NewsTypeSerializer(serializers.ModelSerializer): class NewsBaseSerializer(ProjectModelSerializer): """Base serializer for News model.""" + title_translated = TranslatedField() + subtitle_translated = TranslatedField() news_type = NewsTypeSerializer(read_only=True) tags = TagBaseSerializer(read_only=True, many=True) @@ -246,8 +248,10 @@ class NewsBackOfficeDetailSerializer(NewsBackOfficeBaseSerializer, class NewsBackOfficeGallerySerializer(serializers.ModelSerializer): """Serializer class for model NewsGallery.""" + class Meta: """Meta class""" + model = models.NewsGallery fields = [ 'id', From 7fa52d770e43f91dd74a09e674832a2c87007235 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Thu, 24 Oct 2019 12:17:58 +0300 Subject: [PATCH 195/223] added missing migration for app news --- .../migrations/0026_auto_20191024_0913.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 apps/news/migrations/0026_auto_20191024_0913.py diff --git a/apps/news/migrations/0026_auto_20191024_0913.py b/apps/news/migrations/0026_auto_20191024_0913.py new file mode 100644 index 00000000..bcad2ce2 --- /dev/null +++ b/apps/news/migrations/0026_auto_20191024_0913.py @@ -0,0 +1,21 @@ +# Generated by Django 2.2.4 on 2019-10-24 09:13 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0025_merge_20191023_1317'), + ] + + operations = [ + migrations.RemoveField( + model_name='news', + name='image_url', + ), + migrations.RemoveField( + model_name='news', + name='preview_image_url', + ), + ] From 636bc43a35ff59d38c098eee80e0a3ac3c3b8840 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Thu, 24 Oct 2019 12:38:44 +0300 Subject: [PATCH 196/223] remove playlist from news model --- apps/favorites/tests.py | 2 +- .../migrations/0027_remove_news_playlist.py | 17 +++++++++++++++++ apps/news/models.py | 1 - apps/news/serializers.py | 1 - apps/news/tests.py | 3 +-- apps/search_indexes/documents/news.py | 1 - apps/utils/tests/tests_translated.py | 1 - 7 files changed, 19 insertions(+), 7 deletions(-) create mode 100644 apps/news/migrations/0027_remove_news_playlist.py diff --git a/apps/favorites/tests.py b/apps/favorites/tests.py index 99b01444..dd5c6c4f 100644 --- a/apps/favorites/tests.py +++ b/apps/favorites/tests.py @@ -26,7 +26,7 @@ class BaseTestCase(APITestCase): self.test_news = News.objects.create(created_by=self.user, modified_by=self.user, title={"en-GB": "Test news"}, news_type=self.test_news_type, description={"en-GB": "Description test news"}, - playlist=1, start="2020-12-03 12:00:00", end="2020-12-13 12:00:00", + start="2020-12-03 12:00:00", end="2020-12-13 12:00:00", state=News.PUBLISHED, slug='test-news') self.test_content_type = ContentType.objects.get(app_label="news", model="news") diff --git a/apps/news/migrations/0027_remove_news_playlist.py b/apps/news/migrations/0027_remove_news_playlist.py new file mode 100644 index 00000000..3741fca1 --- /dev/null +++ b/apps/news/migrations/0027_remove_news_playlist.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.4 on 2019-10-24 09:37 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0026_auto_20191024_0913'), + ] + + operations = [ + migrations.RemoveField( + model_name='news', + name='playlist', + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index 1abd10e1..a2130fac 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -141,7 +141,6 @@ class News(BaseAttributes, TranslatedFieldsMixin): verbose_name=_('End')) slug = models.SlugField(unique=True, max_length=255, verbose_name=_('News slug')) - playlist = models.IntegerField(_('playlist')) state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES, verbose_name=_('State')) is_highlighted = models.BooleanField(default=False, diff --git a/apps/news/serializers.py b/apps/news/serializers.py index cd7afff4..31c8fc91 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -179,7 +179,6 @@ class NewsDetailSerializer(NewsBaseSerializer): 'description_translated', 'start', 'end', - 'playlist', 'is_publish', 'state', 'state_display', diff --git a/apps/news/tests.py b/apps/news/tests.py index b4e2b296..f5340d15 100644 --- a/apps/news/tests.py +++ b/apps/news/tests.py @@ -50,7 +50,7 @@ class BaseTestCase(APITestCase): title={"en-GB": "Test news"}, news_type=self.test_news_type, description={"en-GB": "Description test news"}, - playlist=1, start=datetime.now() + timedelta(hours=-2), + start=datetime.now() + timedelta(hours=-2), end=datetime.now() + timedelta(hours=2), state=News.PUBLISHED, slug='test-news-slug', country=self.country_ru) @@ -85,7 +85,6 @@ class NewsTestCase(BaseTestCase): 'description': {"en-GB": "Description test news!"}, 'slug': self.test_news.slug, 'start': self.test_news.start, - 'playlist': self.test_news.playlist, 'news_type_id':self.test_news.news_type_id, 'country_id': self.country_ru.id } diff --git a/apps/search_indexes/documents/news.py b/apps/search_indexes/documents/news.py index cd6fc089..699a1cca 100644 --- a/apps/search_indexes/documents/news.py +++ b/apps/search_indexes/documents/news.py @@ -37,7 +37,6 @@ class NewsDocument(Document): model = models.News fields = ( 'id', - 'playlist', 'start', 'end', 'slug', diff --git a/apps/utils/tests/tests_translated.py b/apps/utils/tests/tests_translated.py index c6a990c0..7a304095 100644 --- a/apps/utils/tests/tests_translated.py +++ b/apps/utils/tests/tests_translated.py @@ -47,7 +47,6 @@ class TranslateFieldTests(BaseTestCase): "ru-RU": "Тестовая новость" }, description={"en-GB": "Test description"}, - playlist=1, start=datetime.now(pytz.utc) + timedelta(hours=-13), end=datetime.now(pytz.utc) + timedelta(hours=13), news_type=self.news_type, From 74b2b52ee1b59a7bd31f0a53e19a3b6e29c9f147 Mon Sep 17 00:00:00 2001 From: Semyon Yekhmenin Date: Thu, 24 Oct 2019 11:06:58 +0000 Subject: [PATCH 197/223] Added currency to establishment model --- .../migrations/0016_auto_20191024_0833.py | 24 +++++++++++++++++++ apps/establishment/models.py | 5 +++- apps/establishment/serializers/common.py | 2 ++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 apps/account/migrations/0016_auto_20191024_0833.py diff --git a/apps/account/migrations/0016_auto_20191024_0833.py b/apps/account/migrations/0016_auto_20191024_0833.py new file mode 100644 index 00000000..6c99d567 --- /dev/null +++ b/apps/account/migrations/0016_auto_20191024_0833.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2.4 on 2019-10-24 08:33 + +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'), + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 7de88d91..304ea2a6 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -17,7 +17,7 @@ from phonenumber_field.modelfields import PhoneNumberField from collection.models import Collection from location.models import Address -from main.models import Award +from main.models import Award, Currency from review.models import Review from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin, TranslatedFieldsMixin, BaseAttributes) @@ -362,6 +362,9 @@ class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin): reviews = generic.GenericRelation(to='review.Review') comments = generic.GenericRelation(to='comment.Comment') favorites = generic.GenericRelation(to='favorites.Favorites') + currency = models.ForeignKey(Currency, blank=True, null=True, default=None, + on_delete=models.PROTECT, + verbose_name=_('currency')) objects = EstablishmentQuerySet.as_manager() diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 389d0d1c..266ed0df 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -163,6 +163,7 @@ class EstablishmentBaseSerializer(ProjectModelSerializer): address = AddressBaseSerializer() in_favorites = serializers.BooleanField(allow_null=True) tags = TagBaseSerializer(read_only=True, many=True) + currency = CurrencySerializer() class Meta: """Meta class.""" @@ -180,6 +181,7 @@ class EstablishmentBaseSerializer(ProjectModelSerializer): 'in_favorites', 'address', 'tags', + 'currency' ] From 55ea169dce4de8df9129d9f17261f166beae0766 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 14:11:07 +0300 Subject: [PATCH 198/223] return fields --- apps/search_indexes/documents/news.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/search_indexes/documents/news.py b/apps/search_indexes/documents/news.py index 699a1cca..7cd69042 100644 --- a/apps/search_indexes/documents/news.py +++ b/apps/search_indexes/documents/news.py @@ -42,6 +42,8 @@ class NewsDocument(Document): 'slug', 'state', 'is_highlighted', + 'image_url', + 'preview_image_url', 'template', ) related_models = [models.NewsType] From 0dde77a64b908f1761bf17da753509d80c5d5023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Thu, 24 Oct 2019 15:12:15 +0300 Subject: [PATCH 199/223] Fix search_index news --- apps/search_indexes/documents/news.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/search_indexes/documents/news.py b/apps/search_indexes/documents/news.py index 7cd69042..0d1cc765 100644 --- a/apps/search_indexes/documents/news.py +++ b/apps/search_indexes/documents/news.py @@ -42,8 +42,8 @@ class NewsDocument(Document): 'slug', 'state', 'is_highlighted', - 'image_url', - 'preview_image_url', + # 'image_url', + # 'preview_image_url', 'template', ) related_models = [models.NewsType] From 8c5d59e6bf0d27a16bfb61c05296e62fbab245fa Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Thu, 24 Oct 2019 15:16:51 +0300 Subject: [PATCH 200/223] Revert "return fields" This reverts commit 55ea169 --- apps/search_indexes/documents/news.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/search_indexes/documents/news.py b/apps/search_indexes/documents/news.py index 7cd69042..699a1cca 100644 --- a/apps/search_indexes/documents/news.py +++ b/apps/search_indexes/documents/news.py @@ -42,8 +42,6 @@ class NewsDocument(Document): 'slug', 'state', 'is_highlighted', - 'image_url', - 'preview_image_url', 'template', ) related_models = [models.NewsType] From 2b9c9d14ef144137188b16254f7e855b0b49cf31 Mon Sep 17 00:00:00 2001 From: Semyon Yekhmenin Date: Thu, 24 Oct 2019 12:21:59 +0000 Subject: [PATCH 201/223] Added temporary stub for international news logic (hardcoded news id's) --- apps/news/views.py | 20 +++++++++++++++----- project/settings/base.py | 2 +- project/settings/development.py | 4 ++++ project/settings/local.py | 5 +++++ project/settings/stage.py | 5 +++++ 5 files changed, 30 insertions(+), 6 deletions(-) diff --git a/apps/news/views.py b/apps/news/views.py index e0034afa..51d49be3 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -14,15 +14,25 @@ from utils.permissions import IsCountryAdmin, IsContentPageManager class NewsMixinView: """News mixin.""" - permission_classes = (permissions.AllowAny, ) + permission_classes = (permissions.AllowAny,) + serializer_class = serializers.NewsBaseSerializer def get_queryset(self, *args, **kwargs): + from django.conf import settings """Override get_queryset method.""" - qs = models.News.objects.published().with_base_related()\ + qs = models.News.objects.published().with_base_related() \ .order_by('-is_highlighted', '-created') - if self.request.country_code: - qs = qs.by_country_code(self.request.country_code) - return qs + country_code = self.request.country_code + if country_code and country_code != 'www' and country_code != 'main': + return qs.by_country_code(self.request.country_code) + else: + if settings.HARDCODED_INTERNATIONAL_NEWS_IDS: + # Temporary stub for international news logic + # (по договорённости с заказчиком на демонстрации 4 ноября здесь будет 6 фиксированных новостей) + # TODO replace this stub with actual logic + return models.News.objects.filter(id__in=settings.HARDCODED_INTERNATIONAL_NEWS_IDS) + else: + return qs class NewsListView(NewsMixinView, generics.ListAPIView): diff --git a/project/settings/base.py b/project/settings/base.py index 02df804e..f9524f94 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -471,4 +471,4 @@ STATICFILES_DIRS = ( # MEDIA -MEDIA_LOCATION = 'media' \ No newline at end of file +MEDIA_LOCATION = 'media' diff --git a/project/settings/development.py b/project/settings/development.py index 59691818..9f950abf 100644 --- a/project/settings/development.py +++ b/project/settings/development.py @@ -34,3 +34,7 @@ sentry_sdk.init( dsn="https://35d9bb789677410ab84a822831c6314f@sentry.io/1729093", integrations=[DjangoIntegration()] ) + +# TMP ( TODO remove it later) +# Временный хардкод для демонстрации 4 ноября, потом удалить! +HARDCODED_INTERNATIONAL_NEWS_IDS = [8, 9, 10, 11, 15, 17] diff --git a/project/settings/local.py b/project/settings/local.py index e87b99f6..1b511692 100644 --- a/project/settings/local.py +++ b/project/settings/local.py @@ -88,3 +88,8 @@ ELASTICSEARCH_INDEX_NAMES = { TESTING = sys.argv[1:2] == ['test'] if TESTING: ELASTICSEARCH_INDEX_NAMES = {} + + +# TMP ( TODO remove it later) +# Временный хардкод для демонстрации 4 ноября, потом удалить! +HARDCODED_INTERNATIONAL_NEWS_IDS = [8, 9, 10, 11, 15, 17] \ No newline at end of file diff --git a/project/settings/stage.py b/project/settings/stage.py index 49a7ae0f..0afe7e4e 100644 --- a/project/settings/stage.py +++ b/project/settings/stage.py @@ -26,3 +26,8 @@ ELASTICSEARCH_INDEX_NAMES = { # 'search_indexes.documents.news': 'stage_news', #temporarily disabled 'search_indexes.documents.establishment': 'stage_establishment', } + + +# TMP ( TODO remove it later) +# Временный хардкод для демонстрации 4 ноября, потом удалить! +HARDCODED_INTERNATIONAL_NEWS_IDS = [8, 9, 10, 11, 15, 17] \ No newline at end of file From df52c8a582d46ec0fd58bacf6ce19f2d17f1b8d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Thu, 24 Oct 2019 15:34:35 +0300 Subject: [PATCH 202/223] Add merged migrations --- .../account/migrations/0017_merge_20191024_1233.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 apps/account/migrations/0017_merge_20191024_1233.py diff --git a/apps/account/migrations/0017_merge_20191024_1233.py b/apps/account/migrations/0017_merge_20191024_1233.py new file mode 100644 index 00000000..580f6b2f --- /dev/null +++ b/apps/account/migrations/0017_merge_20191024_1233.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.4 on 2019-10-24 12:33 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0016_auto_20191024_0830'), + ('account', '0016_auto_20191024_0833'), + ] + + operations = [ + ] From 2cf0a94ccb36d251c67224fc141284a16a7e7f13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Thu, 24 Oct 2019 16:14:18 +0300 Subject: [PATCH 203/223] Fix migr --- .../migrations/0043_establishment_currency.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 apps/establishment/migrations/0043_establishment_currency.py diff --git a/apps/establishment/migrations/0043_establishment_currency.py b/apps/establishment/migrations/0043_establishment_currency.py new file mode 100644 index 00000000..7c324dc4 --- /dev/null +++ b/apps/establishment/migrations/0043_establishment_currency.py @@ -0,0 +1,20 @@ +# Generated by Django 2.2.4 on 2019-10-24 13:13 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0022_auto_20191023_1113'), + ('establishment', '0042_establishment_tz'), + ] + + operations = [ + migrations.AddField( + model_name='establishment', + name='currency', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='main.Currency', verbose_name='currency'), + ), + ] From 128488b5ee364f98dbee7cd14070dff1067ac98a Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Thu, 24 Oct 2019 16:17:37 +0300 Subject: [PATCH 204/223] some bugfixes --- apps/establishment/serializers/back.py | 3 ++- apps/establishment/serializers/common.py | 1 - apps/main/serializers.py | 6 ++++-- apps/search_indexes/serializers.py | 25 ------------------------ apps/utils/serializers.py | 16 +++++++++++++++ 5 files changed, 22 insertions(+), 29 deletions(-) diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index 714dd62d..f1fb40a5 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -7,6 +7,7 @@ from establishment.serializers import ( EstablishmentTypeBaseSerializer) from main.models import Currency from utils.decorators import with_base_attributes +from utils.serializers import TimeZoneChoiceField class EstablishmentListCreateSerializer(EstablishmentBaseSerializer): @@ -19,8 +20,8 @@ class EstablishmentListCreateSerializer(EstablishmentBaseSerializer): phones = ContactPhonesSerializer(read_only=True, many=True, ) emails = ContactEmailsSerializer(read_only=True, many=True, ) socials = SocialNetworkRelatedSerializers(read_only=True, many=True, ) - slug = serializers.SlugField(required=True, allow_blank=False, max_length=50) type = EstablishmentTypeBaseSerializer(source='establishment_type', read_only=True) + tz = TimeZoneChoiceField() class Meta: model = models.Establishment diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 389d0d1c..8a217106 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -159,7 +159,6 @@ class EstablishmentBaseSerializer(ProjectModelSerializer): """Base serializer for Establishment model.""" preview_image = serializers.URLField(source='preview_image_url') - slug = serializers.SlugField(allow_blank=False, required=True, max_length=50) address = AddressBaseSerializer() in_favorites = serializers.BooleanField(allow_null=True) tags = TagBaseSerializer(read_only=True, many=True) diff --git a/apps/main/serializers.py b/apps/main/serializers.py index 8975af14..a0af662b 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 ProjectModelSerializer +from utils.serializers import ProjectModelSerializer, TranslatedField class FeatureSerializer(serializers.ModelSerializer): @@ -41,7 +41,9 @@ class SiteFeatureSerializer(serializers.ModelSerializer): class CurrencySerializer(ProjectModelSerializer): - """Currency serializer""" + """Currency serializer.""" + + name_translated = TranslatedField() class Meta: model = models.Currency diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 535974a9..d4ab2dbf 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -102,28 +102,3 @@ class EstablishmentDocumentSerializer(DocumentSerializer): # 'establishment_type', # 'establishment_subtypes', ) - - - # def to_representation(self, instance): - # ret = super().to_representation(instance) - # dict_merge = lambda a, b: a.update(b) or a - # - # ret['tags'] = map(lambda tag: dict_merge(tag, {'label_translated': get_translated_value(tag.pop('label'))}), - # ret['tags']) - # ret['establishment_subtypes'] = map( - # lambda subtype: dict_merge(subtype, {'name_translated': get_translated_value(subtype.pop('name'))}), - # ret['establishment_subtypes']) - # if ret.get('establishment_type'): - # ret['establishment_type']['name_translated'] = get_translated_value(ret['establishment_type'].pop('name')) - # if ret.get('address'): - # ret['address']['city']['country']['name_translated'] = get_translated_value( - # ret['address']['city']['country'].pop('name')) - # location = ret['address'].pop('location') - # if location: - # ret['address']['geo_lon'] = location['lon'] - # ret['address']['geo_lat'] = location['lat'] - # - # ret['type'] = ret.pop('establishment_type') - # ret['subtypes'] = ret.pop('establishment_subtypes') - # - # return ret \ No newline at end of file diff --git a/apps/utils/serializers.py b/apps/utils/serializers.py index 90efea00..a2a33ee2 100644 --- a/apps/utils/serializers.py +++ b/apps/utils/serializers.py @@ -1,4 +1,5 @@ """Utils app serializer.""" +import pytz from django.core import exceptions from rest_framework import serializers from utils import models @@ -48,6 +49,21 @@ class TJSONField(serializers.JSONField): validators = [validate_tjson] +class TimeZoneChoiceField(serializers.ChoiceField): + """Take the timezone object and make it JSON serializable.""" + + def __init__(self, choices=None, **kwargs): + if choices is None: + choices = pytz.all_timezones + super().__init__(choices=choices, **kwargs) + + def to_representation(self, value): + return value.zone + + def to_internal_value(self, data): + return pytz.timezone(data) + + class ProjectModelSerializer(serializers.ModelSerializer): """Overrided ModelSerializer.""" From 84fb0aec705882245f65ccbeff84324cdb4f92be Mon Sep 17 00:00:00 2001 From: Anatoly Date: Thu, 24 Oct 2019 16:41:41 +0300 Subject: [PATCH 205/223] change field parameter in model Guide (gm-241) --- .../migrations/0016_auto_20191024_1334.py | 19 +++++++++++++++++++ apps/collection/models.py | 5 +++-- apps/news/models.py | 6 ++++-- apps/news/serializers.py | 2 +- 4 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 apps/collection/migrations/0016_auto_20191024_1334.py diff --git a/apps/collection/migrations/0016_auto_20191024_1334.py b/apps/collection/migrations/0016_auto_20191024_1334.py new file mode 100644 index 00000000..a362777f --- /dev/null +++ b/apps/collection/migrations/0016_auto_20191024_1334.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.4 on 2019-10-24 13:34 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('collection', '0015_auto_20191023_0715'), + ] + + operations = [ + migrations.AlterField( + model_name='guide', + name='collection', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='collection.Collection', verbose_name='collection'), + ), + ] diff --git a/apps/collection/models.py b/apps/collection/models.py index 0a2700bd..cbcc3842 100644 --- a/apps/collection/models.py +++ b/apps/collection/models.py @@ -101,8 +101,9 @@ class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin): advertorials = JSONField( _('advertorials'), null=True, blank=True, default=None, help_text='{"key":"value"}') - collection = models.ForeignKey( - Collection, verbose_name=_('collection'), on_delete=models.CASCADE) + collection = models.ForeignKey(Collection, on_delete=models.CASCADE, + null=True, blank=True, default=None, + verbose_name=_('collection')) objects = GuideQuerySet.as_manager() diff --git a/apps/news/models.py b/apps/news/models.py index a2130fac..d7bad867 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -194,13 +194,15 @@ class News(BaseAttributes, TranslatedFieldsMixin): @property def main_image(self): - return self.news_gallery.main_images().first().image + qs = self.news_gallery.main_image() + if qs.exists(): + return qs.first().image class NewsGalleryQuerySet(models.QuerySet): """QuerySet for model News""" - def main_images(self): + def main_image(self): """Return objects with flag is_main is True""" return self.filter(is_main=True) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 31c8fc91..2b4e98b6 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -281,7 +281,7 @@ 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(): + if is_main and news.news_gallery.main_image().exists(): raise serializers.ValidationError({'detail': _('Main image is already added')}) attrs['news'] = news From 7ac7df9ea36f6635e9c70970088c03e5fc3cb47b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Thu, 24 Oct 2019 16:48:29 +0300 Subject: [PATCH 206/223] Fix test news --- apps/news/urls/back.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/news/urls/back.py b/apps/news/urls/back.py index 4ab11727..9cc3d94a 100644 --- a/apps/news/urls/back.py +++ b/apps/news/urls/back.py @@ -8,7 +8,7 @@ app_name = 'news' urlpatterns = [ path('', views.NewsBackOfficeLCView.as_view(), name='list-create'), path('/', views.NewsBackOfficeRUDView.as_view(), - name='gallery-retrieve-update-destroy'), + name='retrieve-update-destroy'), path('/gallery/', views.NewsBackOfficeGalleryListView.as_view(), name='gallery-list'), path('/gallery//', views.NewsBackOfficeGalleryCreateDestroyView.as_view(), From 2864576c0c8b988c9356083fb0eb56711a126c35 Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Thu, 24 Oct 2019 17:09:08 +0300 Subject: [PATCH 207/223] fix TimeZoneChoiceField --- apps/utils/serializers.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/utils/serializers.py b/apps/utils/serializers.py index a2a33ee2..eeff1043 100644 --- a/apps/utils/serializers.py +++ b/apps/utils/serializers.py @@ -58,7 +58,11 @@ class TimeZoneChoiceField(serializers.ChoiceField): super().__init__(choices=choices, **kwargs) def to_representation(self, value): - return value.zone + if isinstance(value, str): + return value + elif isinstance(value, pytz.tzinfo.BaseTzInfo): + return value.zone + return None def to_internal_value(self, data): return pytz.timezone(data) From 81cdaa5c89d221428f842f0c70d4173e2ea8ea88 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Thu, 24 Oct 2019 19:53:09 +0300 Subject: [PATCH 208/223] added image_url & preview_image_url to NewsDocument --- .../migrations/0028_auto_20191024_1649.py | 28 +++++++++++++++++++ apps/news/models.py | 14 ++++++++-- apps/search_indexes/documents/news.py | 2 ++ 3 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 apps/news/migrations/0028_auto_20191024_1649.py diff --git a/apps/news/migrations/0028_auto_20191024_1649.py b/apps/news/migrations/0028_auto_20191024_1649.py new file mode 100644 index 00000000..c9eaacb1 --- /dev/null +++ b/apps/news/migrations/0028_auto_20191024_1649.py @@ -0,0 +1,28 @@ +# Generated by Django 2.2.4 on 2019-10-24 16:49 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('news', '0027_remove_news_playlist'), + ] + + operations = [ + migrations.AlterField( + model_name='newsgallery', + name='image', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='news_gallery', to='gallery.Image', verbose_name='gallery'), + ), + migrations.AlterField( + model_name='newsgallery', + name='news', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='news_gallery', to='news.News', verbose_name='news'), + ), + migrations.AlterUniqueTogether( + name='newsgallery', + unique_together={('news', 'is_main')}, + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index d7bad867..dbb2f5bf 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -198,6 +198,15 @@ class News(BaseAttributes, TranslatedFieldsMixin): if qs.exists(): return qs.first().image + @property + def image_url(self): + return self.main_image.image.url if self.main_image else None + + @property + def preview_image_url(self): + if self.main_image: + return self.main_image.get_image_url(thumbnail_key='news_preview') + class NewsGalleryQuerySet(models.QuerySet): """QuerySet for model News""" @@ -211,11 +220,11 @@ class NewsGallery(models.Model): news = models.ForeignKey(News, null=True, related_name='news_gallery', - on_delete=models.SET_NULL, + on_delete=models.CASCADE, verbose_name=_('news')) image = models.ForeignKey('gallery.Image', null=True, related_name='news_gallery', - on_delete=models.SET_NULL, + on_delete=models.CASCADE, verbose_name=_('gallery')) is_main = models.BooleanField(default=False, verbose_name=_('Is the main image')) @@ -226,3 +235,4 @@ class NewsGallery(models.Model): """NewsGallery meta class.""" verbose_name = _('news gallery') verbose_name_plural = _('news galleries') + unique_together = ('news', 'is_main') diff --git a/apps/search_indexes/documents/news.py b/apps/search_indexes/documents/news.py index 699a1cca..86b117e5 100644 --- a/apps/search_indexes/documents/news.py +++ b/apps/search_indexes/documents/news.py @@ -24,6 +24,8 @@ class NewsDocument(Document): country = fields.ObjectField(properties={'id': fields.IntegerField(), 'code': fields.KeywordField()}) web_url = fields.KeywordField(attr='web_url') + image_url = fields.KeywordField(attr='image_url') + preview_image_url = fields.KeywordField(attr='preview_image_url') tags = fields.ObjectField( properties={ 'id': fields.IntegerField(attr='id'), From 66ddfb455b032f5da8433a02e4ffa51b6134c9c0 Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Thu, 24 Oct 2019 22:19:59 +0300 Subject: [PATCH 209/223] fix tests --- apps/establishment/tests.py | 4 +- apps/favorites/tests.py | 71 ++++++++++++++++++---------- apps/news/tests.py | 35 ++++++++------ apps/news/urls/back.py | 2 +- apps/news/views.py | 27 +++++++---- apps/utils/tests/tests_translated.py | 5 +- docker-compose.yml | 8 ---- project/settings/local.py | 5 -- 8 files changed, 91 insertions(+), 66 deletions(-) diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index a1d8fcb5..43aa62c6 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -9,6 +9,7 @@ from establishment.models import Establishment, EstablishmentType, Menu from translation.models import Language from account.models import Role, UserRole from location.models import Country, Address, City, Region +from pytz import timezone as py_tz class BaseTestCase(APITestCase): @@ -77,7 +78,8 @@ class EstablishmentBTests(BaseTestCase): 'name': 'Test establishment', 'type_id': self.establishment_type.id, 'is_publish': True, - 'slug': 'test-establishment-slug' + 'slug': 'test-establishment-slug', + 'tz': py_tz('Europe/Moscow').zone } response = self.client.post('/api/back/establishments/', data=data, format='json') diff --git a/apps/favorites/tests.py b/apps/favorites/tests.py index dd5c6c4f..cebd43c2 100644 --- a/apps/favorites/tests.py +++ b/apps/favorites/tests.py @@ -1,14 +1,16 @@ # Create your tests here. from http.cookies import SimpleCookie -from django.contrib.contenttypes.models import ContentType -from rest_framework.test import APITestCase +from django.contrib.contenttypes.models import ContentType +from django.urls import reverse from rest_framework import status +from rest_framework.test import APITestCase from account.models import User +from establishment.models import Establishment, EstablishmentType from favorites.models import Favorites -from establishment.models import Establishment, EstablishmentType, EstablishmentSubType from news.models import NewsType, News +from datetime import datetime class BaseTestCase(APITestCase): @@ -17,38 +19,57 @@ class BaseTestCase(APITestCase): self.username = 'sedragurda' self.password = 'sedragurdaredips19' self.email = 'sedragurda@desoz.com' - self.user = User.objects.create_user(username=self.username, email=self.email, password=self.password) - tokkens = User.create_jwt_tokens(self.user) - self.client.cookies = SimpleCookie({'access_token': tokkens.get('access_token'), - 'refresh_token': tokkens.get('refresh_token')}) + self.user = User.objects.create_user( + username=self.username, + email=self.email, + password=self.password + ) - self.test_news_type = NewsType.objects.create(name="Test news type") - self.test_news = News.objects.create(created_by=self.user, modified_by=self.user, title={"en-GB": "Test news"}, - news_type=self.test_news_type, - description={"en-GB": "Description test news"}, - start="2020-12-03 12:00:00", end="2020-12-13 12:00:00", - state=News.PUBLISHED, slug='test-news') + tokens = User.create_jwt_tokens(self.user) + self.client.cookies = SimpleCookie( + {'access_token': tokens.get('access_token'), + 'refresh_token': tokens.get('refresh_token')}) - self.test_content_type = ContentType.objects.get(app_label="news", model="news") + self.test_news_type = NewsType.objects.create( + name="Test news type", + ) + self.test_news = News.objects.create( + created_by=self.user, + modified_by=self.user, + title={"en-GB": "Test news"}, + news_type=self.test_news_type, + description={"en-GB": "Description test news"}, + start=datetime.fromisoformat("2020-12-03 12:00:00"), + end=datetime.fromisoformat("2020-12-03 12:00:00"), + state=News.PUBLISHED, + slug='test-news' + ) - self.test_favorites = Favorites.objects.create(user=self.user, content_type=self.test_content_type, - object_id=self.test_news.id) + self.test_content_type = ContentType.objects.get( + app_label="news", model="news") - self.test_establishment_type = EstablishmentType.objects.create(name={"en-GB": "test establishment type"}, - use_subtypes=False) + self.test_favorites = Favorites.objects.create( + user=self.user, content_type=self.test_content_type, + object_id=self.test_news.id) - self.test_establishment = Establishment.objects.create(name="test establishment", - description={"en-GB": "description of test establishment"}, - establishment_type=self.test_establishment_type, - is_publish=True) + self.test_establishment_type = EstablishmentType.objects.create( + name={"en-GB": "test establishment type"}, + use_subtypes=False) + + self.test_establishment = Establishment.objects.create( + name="test establishment", + description={"en-GB": "description of test establishment"}, + establishment_type=self.test_establishment_type, + is_publish=True) # value for GenericRelation(reverse side) field must be iterable - # value for GenericRelation(reverse side) field must be assigned through "set" method of field + # value for GenericRelation(reverse side) field must be assigned through + # "set" method of field self.test_establishment.favorites.set([self.test_favorites]) class FavoritesTestCase(BaseTestCase): def test_func(self): - response = self.client.get("/api/web/favorites/establishments/") - print(response.json()) + url = reverse('web:favorites:establishment-list') + response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) \ No newline at end of file diff --git a/apps/news/tests.py b/apps/news/tests.py index f5340d15..115763e5 100644 --- a/apps/news/tests.py +++ b/apps/news/tests.py @@ -18,11 +18,14 @@ class BaseTestCase(APITestCase): self.username = 'sedragurda' self.password = 'sedragurdaredips19' self.email = 'sedragurda@desoz.com' - self.user = User.objects.create_user(username=self.username, email=self.email, password=self.password) - #get tokkens - tokkens = User.create_jwt_tokens(self.user) - self.client.cookies = SimpleCookie({'access_token': tokkens.get('access_token'), - 'refresh_token': tokkens.get('refresh_token')}) + self.user = User.objects.create_user( + username=self.username, email=self.email, password=self.password) + + # get tokens + tokens = User.create_jwt_tokens(self.user) + self.client.cookies = SimpleCookie( + {'access_token': tokens.get('access_token'), + 'refresh_token': tokens.get('refresh_token')}) self.test_news_type = NewsType.objects.create(name="Test news type") self.lang = Language.objects.get( @@ -46,21 +49,25 @@ class BaseTestCase(APITestCase): ) user_role.save() - self.test_news = News.objects.create(created_by=self.user, modified_by=self.user, - title={"en-GB": "Test news"}, - news_type=self.test_news_type, - description={"en-GB": "Description test news"}, - start=datetime.now() + timedelta(hours=-2), - end=datetime.now() + timedelta(hours=2), - state=News.PUBLISHED, slug='test-news-slug', - country=self.country_ru) + self.test_news = News.objects.create( + created_by=self.user, modified_by=self.user, + title={"en-GB": "Test news"}, + news_type=self.test_news_type, + description={"en-GB": "Description test news"}, + start=datetime.now() + timedelta(hours=-2), + end=datetime.now() + timedelta(hours=2), + state=News.PUBLISHED, + slug='test-news-slug', + country=self.country_ru, + ) + class NewsTestCase(BaseTestCase): def setUp(self): super().setUp() def test_web_news(self): - response = self.client.get("/api/web/news/") + response = self.client.get(reverse('web:news:list')) self.assertEqual(response.status_code, status.HTTP_200_OK) response = self.client.get(f"/api/web/news/slug/{self.test_news.slug}/") diff --git a/apps/news/urls/back.py b/apps/news/urls/back.py index 4ab11727..9cc3d94a 100644 --- a/apps/news/urls/back.py +++ b/apps/news/urls/back.py @@ -8,7 +8,7 @@ app_name = 'news' urlpatterns = [ path('', views.NewsBackOfficeLCView.as_view(), name='list-create'), path('/', views.NewsBackOfficeRUDView.as_view(), - name='gallery-retrieve-update-destroy'), + name='retrieve-update-destroy'), path('/gallery/', views.NewsBackOfficeGalleryListView.as_view(), name='gallery-list'), path('/gallery//', views.NewsBackOfficeGalleryCreateDestroyView.as_view(), diff --git a/apps/news/views.py b/apps/news/views.py index 51d49be3..9cd5d969 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -20,19 +20,28 @@ class NewsMixinView: def get_queryset(self, *args, **kwargs): from django.conf import settings """Override get_queryset method.""" + qs = models.News.objects.published().with_base_related() \ .order_by('-is_highlighted', '-created') country_code = self.request.country_code - if country_code and country_code != 'www' and country_code != 'main': - return qs.by_country_code(self.request.country_code) - else: - if settings.HARDCODED_INTERNATIONAL_NEWS_IDS: - # Temporary stub for international news logic - # (по договорённости с заказчиком на демонстрации 4 ноября здесь будет 6 фиксированных новостей) - # TODO replace this stub with actual logic - return models.News.objects.filter(id__in=settings.HARDCODED_INTERNATIONAL_NEWS_IDS) - else: + if country_code: + + # temp code + # Temporary stub for international news logic + # (по договорённости с заказчиком на демонстрации 4 ноября + # здесь будет 6 фиксированных новостей) + # TODO replace this stub with actual logic + if hasattr(settings, 'HARDCODED_INTERNATIONAL_NEWS_IDS'): + if country_code and country_code != 'www' and country_code != 'main': + qs = qs.by_country_code(country_code) + else: + qs = models.News.objects.filter( + id__in=settings.HARDCODED_INTERNATIONAL_NEWS_IDS) return qs + # temp code + + qs = qs.by_country_code(country_code) + return qs class NewsListView(NewsMixinView, generics.ListAPIView): diff --git a/apps/utils/tests/tests_translated.py b/apps/utils/tests/tests_translated.py index 7a304095..77b67d8a 100644 --- a/apps/utils/tests/tests_translated.py +++ b/apps/utils/tests/tests_translated.py @@ -27,7 +27,7 @@ class BaseTestCase(APITestCase): self.client.cookies = SimpleCookie( {'access_token': tokkens.get('access_token'), 'refresh_token': tokkens.get('refresh_token'), - 'locale': "en" + 'locale': "en-GB" }) @@ -58,12 +58,11 @@ class TranslateFieldTests(BaseTestCase): def test_model_field(self): self.assertTrue(hasattr(self.news_item, "title_translated")) - def test_read_locale(self): response = self.client.get(f"/api/web/news/slug/{self.news_item.slug}/", format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) news_data = response.json() - + print(news_data) self.assertIn("title_translated", news_data) self.assertIn("Test news item", news_data['title_translated']) diff --git a/docker-compose.yml b/docker-compose.yml index 3b446101..c518a3ad 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,14 +31,6 @@ services: # Redis redis: image: redis:2.8.23 - ports: - - "6379:6379" - - # RabbitMQ - #rabbitmq: - # image: rabbitmq:latest - # ports: - # - "5672:5672" # Celery worker: diff --git a/project/settings/local.py b/project/settings/local.py index 1b511692..e87b99f6 100644 --- a/project/settings/local.py +++ b/project/settings/local.py @@ -88,8 +88,3 @@ ELASTICSEARCH_INDEX_NAMES = { TESTING = sys.argv[1:2] == ['test'] if TESTING: ELASTICSEARCH_INDEX_NAMES = {} - - -# TMP ( TODO remove it later) -# Временный хардкод для демонстрации 4 ноября, потом удалить! -HARDCODED_INTERNATIONAL_NEWS_IDS = [8, 9, 10, 11, 15, 17] \ No newline at end of file From 851ba7f9ddf43b7a26e15553480da25a09d2930b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Fri, 25 Oct 2019 10:14:50 +0300 Subject: [PATCH 210/223] Test edit --- apps/comment/tests.py | 44 ++++++++++++++++++++++++++++++++++---- apps/comment/views/back.py | 2 +- apps/utils/permissions.py | 35 ++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 5 deletions(-) diff --git a/apps/comment/tests.py b/apps/comment/tests.py index 9b060f4e..87b7d32f 100644 --- a/apps/comment/tests.py +++ b/apps/comment/tests.py @@ -5,8 +5,9 @@ from django.urls import reverse from django.contrib.contenttypes.models import ContentType from http.cookies import SimpleCookie from account.models import Role, User, UserRole +from account.serializers.common import UserSerializer from comment.models import Comment - +import json class CommentModeratorPermissionTests(BasePermissionTests): def setUp(self): @@ -28,18 +29,53 @@ class CommentModeratorPermissionTests(BasePermissionTests): ) self.userRole.save() - content_type = ContentType.objects.get(app_label='location', model='country') + self.content_type = ContentType.objects.get(app_label='location', model='country') self.user_test = get_tokens_for_user() self.comment = Comment.objects.create(text='Test comment', mark=1, user=self.user_test["user"], - object_id= self.country_ru.pk, - content_type_id=content_type.id, + object_id=self.country_ru.pk, + content_type_id=self.content_type.id, country=self.country_ru ) self.comment.save() self.url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id}) + def test_post(self): + self.url = reverse('back:comment:comment-list-create') + + comment = { + "text": "Test comment POST", + "user_id": self.user_test["user"].id, + "object_id": self.country_ru.pk, + "content_type_id": self.content_type.id, + "country_id": self.country_ru.id + } + # + # response = self.client.post(self.url, format='json', data=comment) + # self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + json_user = json.dumps(self.moderator) + user = UserSerializer(data=self.moderator) + user.is_valid() + u_data = user.data + self.assertFalse(user.is_valid()) + # comment = { + # "text": "Test comment POST moder", + # "user": user, + # "object_id": self.country_ru.pk, + # "content_type_id": self.content_type.id, + # "country_id": self.country_ru.id + # } + # # + # tokens = User.create_jwt_tokens(self.moderator) + # self.client.cookies = SimpleCookie( + # {'access_token': tokens.get('access_token'), + # 'refresh_token': tokens.get('access_token')}) + # + # response = self.client.post(self.url, format='json', data=comment) + # self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + # self.assertTrue(True) def test_put_moderator(self): tokens = User.create_jwt_tokens(self.moderator) diff --git a/apps/comment/views/back.py b/apps/comment/views/back.py index 2895fdbe..25c10a62 100644 --- a/apps/comment/views/back.py +++ b/apps/comment/views/back.py @@ -8,7 +8,7 @@ class CommentLstView(generics.ListCreateAPIView): """Comment list create view.""" serializer_class = serializers.CommentBaseSerializer queryset = models.Comment.objects.all() - permission_classes = [permissions.IsAuthenticatedOrReadOnly,] + permission_classes = [permissions.IsAuthenticatedOrReadOnly|IsCommentModerator] class CommentRUDView(generics.RetrieveUpdateDestroyAPIView): diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 45d978a0..aee2ab57 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -72,6 +72,20 @@ class IsStandardUser(IsGuest): Object-level permission to only allow owners of an object to edit it. Assumes the model instance has an `owner` attribute. """ + def has_permission(self, request, view): + rules = [ + super().has_permission(request, view) + ] + + # and request.user.email_confirmed, + if hasattr(request, 'user'): + rules = [ + request.user.is_authenticated, + super().has_permission(request, view) + ] + + return any(rules) + def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request rules = [ @@ -131,6 +145,27 @@ class IsCommentModerator(IsStandardUser): Object-level permission to only allow owners of an object to edit it. Assumes the model instance has an `owner` attribute. """ + + def has_permission(self, request, view): + rules = [ + super().has_permission(request, view) + ] + + # and request.user.email_confirmed, + if hasattr(request.data, 'user') and hasattr(request.data, 'country_id'): + # Read permissions are allowed to any request. + + role = Role.objects.filter(role=Role.COMMENTS_MODERATOR, + country_id=request.data.country_id) \ + .first() # 'Comments moderator' + + rules = [ + UserRole.objects.filter(user=request.user, role=role).exists(), + super().has_permission(request, view) + ] + + return any(rules) + def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request. role = Role.objects.filter(role=Role.COMMENTS_MODERATOR, From 046d0c5fe677ece42bce72efa32804e9f4c2287b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Fri, 25 Oct 2019 10:59:31 +0300 Subject: [PATCH 211/223] Fix country and comment role --- apps/comment/serializers/back.py | 2 +- apps/comment/tests.py | 58 +++++++++++++-------------- apps/comment/views/back.py | 2 +- apps/utils/permissions.py | 20 +++++++++ apps/utils/tests/tests_permissions.py | 3 +- 5 files changed, 51 insertions(+), 34 deletions(-) diff --git a/apps/comment/serializers/back.py b/apps/comment/serializers/back.py index d0cd47c8..325086c0 100644 --- a/apps/comment/serializers/back.py +++ b/apps/comment/serializers/back.py @@ -6,4 +6,4 @@ from rest_framework import serializers class CommentBaseSerializer(serializers.ModelSerializer): class Meta: model = models.Comment - fields = ('id', 'text', 'mark', 'user') \ No newline at end of file + fields = ('id', 'text', 'mark', 'user', 'object_id', 'content_type') \ No newline at end of file diff --git a/apps/comment/tests.py b/apps/comment/tests.py index 87b7d32f..e91ee2f4 100644 --- a/apps/comment/tests.py +++ b/apps/comment/tests.py @@ -5,9 +5,8 @@ from django.urls import reverse from django.contrib.contenttypes.models import ContentType from http.cookies import SimpleCookie from account.models import Role, User, UserRole -from account.serializers.common import UserSerializer from comment.models import Comment -import json + class CommentModeratorPermissionTests(BasePermissionTests): def setUp(self): @@ -46,36 +45,30 @@ class CommentModeratorPermissionTests(BasePermissionTests): comment = { "text": "Test comment POST", - "user_id": self.user_test["user"].id, + "user": self.user_test["user"].id, "object_id": self.country_ru.pk, - "content_type_id": self.content_type.id, + "content_type": self.content_type.id, "country_id": self.country_ru.id } - # - # response = self.client.post(self.url, format='json', data=comment) - # self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) - json_user = json.dumps(self.moderator) - user = UserSerializer(data=self.moderator) - user.is_valid() - u_data = user.data - self.assertFalse(user.is_valid()) - # comment = { - # "text": "Test comment POST moder", - # "user": user, - # "object_id": self.country_ru.pk, - # "content_type_id": self.content_type.id, - # "country_id": self.country_ru.id - # } - # # - # tokens = User.create_jwt_tokens(self.moderator) - # self.client.cookies = SimpleCookie( - # {'access_token': tokens.get('access_token'), - # 'refresh_token': tokens.get('access_token')}) - # - # response = self.client.post(self.url, format='json', data=comment) - # self.assertEqual(response.status_code, status.HTTP_201_CREATED) - # self.assertTrue(True) + response = self.client.post(self.url, format='json', data=comment) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + comment = { + "text": "Test comment POST moder", + "user": self.moderator.id, + "object_id": self.country_ru.id, + "content_type": self.content_type.id, + "country_id": self.country_ru.id + } + + tokens = User.create_jwt_tokens(self.moderator) + self.client.cookies = SimpleCookie( + {'access_token': tokens.get('access_token'), + 'refresh_token': tokens.get('access_token')}) + + response = self.client.post(self.url, format='json', data=comment) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) def test_put_moderator(self): tokens = User.create_jwt_tokens(self.moderator) @@ -87,7 +80,9 @@ class CommentModeratorPermissionTests(BasePermissionTests): "id": self.comment.id, "text": "test text moderator", "mark": 1, - "user": self.moderator.id + "user": self.moderator.id, + "object_id": self.comment.country_id, + "content_type": self.content_type.id } response = self.client.put(self.url, data=data, format='json') @@ -134,9 +129,10 @@ class CommentModeratorPermissionTests(BasePermissionTests): "id": self.comment.id, "text": "test text moderator", "mark": 1, - "user": super_user.id + "user": super_user.id, + "object_id": self.country_ru.id, + "content_type": self.content_type.id, } - response = self.client.put(self.url, data=data, format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/apps/comment/views/back.py b/apps/comment/views/back.py index 25c10a62..8d836177 100644 --- a/apps/comment/views/back.py +++ b/apps/comment/views/back.py @@ -8,7 +8,7 @@ class CommentLstView(generics.ListCreateAPIView): """Comment list create view.""" serializer_class = serializers.CommentBaseSerializer queryset = models.Comment.objects.all() - permission_classes = [permissions.IsAuthenticatedOrReadOnly|IsCommentModerator] + permission_classes = [permissions.IsAuthenticatedOrReadOnly|IsCountryAdmin|IsCommentModerator] class CommentRUDView(generics.RetrieveUpdateDestroyAPIView): diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index aee2ab57..8ad1ae32 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -126,6 +126,26 @@ class IsCountryAdmin(IsStandardUser): Object-level permission to only allow owners of an object to edit it. Assumes the model instance has an `owner` attribute. """ + + def has_permission(self, request, view): + rules = [ + super().has_permission(request, view) + ] + + # and request.user.email_confirmed, + if hasattr(request.data, 'user') and hasattr(request.data, 'country_id'): + # Read permissions are allowed to any request. + + role = Role.objects.filter(role=Role.COUNTRY_ADMIN, + country_id=request.data.country_id) \ + .first() # 'Comments moderator' + + rules = [ + UserRole.objects.filter(user=request.user, role=role).exists(), + super().has_permission(request, view) + ] + return any(rules) + def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request. role = Role.objects.filter(role=Role.COUNTRY_ADMIN, diff --git a/apps/utils/tests/tests_permissions.py b/apps/utils/tests/tests_permissions.py index edc1a5d7..3bba7b7d 100644 --- a/apps/utils/tests/tests_permissions.py +++ b/apps/utils/tests/tests_permissions.py @@ -9,10 +9,11 @@ class BasePermissionTests(APITestCase): title='Russia', locale='ru-RU' ) + self.lang.save() self.country_ru = Country.objects.get( name={"en-GB": "Russian"} ) - + self.country_ru.save() From 72713badff2a24556e9cb2089fca4ba4b3a07fc5 Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Fri, 25 Oct 2019 11:04:40 +0300 Subject: [PATCH 212/223] remove print from tests --- apps/account/tests/tests_back.py | 21 ++++++++----- apps/account/tests/tests_common.py | 12 ++++---- .../tests/tests_authorization.py | 8 ++--- apps/comment/tests.py | 13 ++++---- apps/establishment/tests.py | 30 +++++++++++-------- apps/favorites/tests.py | 3 -- apps/location/tests.py | 12 ++------ apps/notification/tests.py | 8 ++--- apps/recipe/tests.py | 2 -- apps/utils/tests/tests_translated.py | 1 - 10 files changed, 55 insertions(+), 55 deletions(-) diff --git a/apps/account/tests/tests_back.py b/apps/account/tests/tests_back.py index 8adc6b35..d0178159 100644 --- a/apps/account/tests/tests_back.py +++ b/apps/account/tests/tests_back.py @@ -1,10 +1,13 @@ -from rest_framework.test import APITestCase -from rest_framework import status -from authorization.tests.tests_authorization import get_tokens_for_user -from django.urls import reverse from http.cookies import SimpleCookie + +from django.urls import reverse +from rest_framework import status +from rest_framework.test import APITestCase + +from account.models import Role, User +from authorization.tests.tests_authorization import get_tokens_for_user from location.models import Country -from account.models import Role, User, UserRole + class RoleTests(APITestCase): def setUp(self): @@ -65,9 +68,11 @@ class UserRoleTests(APITestCase): ) self.role.save() - self.user_test = User.objects.create_user(username='test', - email='testemail@mail.com', - password='passwordtest') + self.user_test = User.objects.create_user( + username='test', + email='testemail@mail.com', + password='passwordtest' + ) def test_user_role_post(self): url = reverse('back:account:user-role-list-create') diff --git a/apps/account/tests/tests_common.py b/apps/account/tests/tests_common.py index dea807c4..67dfacda 100644 --- a/apps/account/tests/tests_common.py +++ b/apps/account/tests/tests_common.py @@ -1,9 +1,11 @@ -from rest_framework.test import APITestCase -from rest_framework import status -from authorization.tests.tests_authorization import get_tokens_for_user from http.cookies import SimpleCookie -from account.models import User + from django.urls import reverse +from rest_framework import status +from rest_framework.test import APITestCase + +from account.models import User +from authorization.tests.tests_authorization import get_tokens_for_user class AccountUserTests(APITestCase): @@ -62,7 +64,7 @@ class AccountChangePasswordTests(APITestCase): self.assertEqual(response.status_code, status.HTTP_200_OK) -class AccountChangePasswordTests(APITestCase): +class AccountConfirmEmail(APITestCase): def setUp(self): self.data = get_tokens_for_user() diff --git a/apps/authorization/tests/tests_authorization.py b/apps/authorization/tests/tests_authorization.py index a6a49ea5..cc0881f5 100644 --- a/apps/authorization/tests/tests_authorization.py +++ b/apps/authorization/tests/tests_authorization.py @@ -1,7 +1,7 @@ -from rest_framework.test import APITestCase -from account.models import User from django.urls import reverse -# Create your tests here. +from rest_framework.test import APITestCase + +from account.models import User def get_tokens_for_user( @@ -28,7 +28,7 @@ class AuthorizationTests(APITestCase): self.password = data["password"] def LoginTests(self): - data ={ + data = { 'username_or_email': self.username, 'password': self.password, 'remember': True diff --git a/apps/comment/tests.py b/apps/comment/tests.py index 9b060f4e..bd22214a 100644 --- a/apps/comment/tests.py +++ b/apps/comment/tests.py @@ -1,11 +1,13 @@ -from utils.tests.tests_permissions import BasePermissionTests -from rest_framework import status -from authorization.tests.tests_authorization import get_tokens_for_user -from django.urls import reverse -from django.contrib.contenttypes.models import ContentType from http.cookies import SimpleCookie + +from django.contrib.contenttypes.models import ContentType +from django.urls import reverse +from rest_framework import status + from account.models import Role, User, UserRole +from authorization.tests.tests_authorization import get_tokens_for_user from comment.models import Comment +from utils.tests.tests_permissions import BasePermissionTests class CommentModeratorPermissionTests(BasePermissionTests): @@ -40,7 +42,6 @@ class CommentModeratorPermissionTests(BasePermissionTests): self.comment.save() self.url = reverse('back:comment:comment-crud', kwargs={"id": self.comment.id}) - def test_put_moderator(self): tokens = User.create_jwt_tokens(self.moderator) self.client.cookies = SimpleCookie( diff --git a/apps/establishment/tests.py b/apps/establishment/tests.py index 43aa62c6..cc9ed152 100644 --- a/apps/establishment/tests.py +++ b/apps/establishment/tests.py @@ -20,13 +20,14 @@ class BaseTestCase(APITestCase): self.newsletter = True self.user = User.objects.create_user( username=self.username, email=self.email, password=self.password) - #get tokkens - tokkens = User.create_jwt_tokens(self.user) + # get tokens + tokens = User.create_jwt_tokens(self.user) self.client.cookies = SimpleCookie( - {'access_token': tokkens.get('access_token'), - 'refresh_token': tokkens.get('refresh_token')}) + {'access_token': tokens.get('access_token'), + 'refresh_token': tokens.get('refresh_token')}) - self.establishment_type = EstablishmentType.objects.create(name="Test establishment type") + self.establishment_type = EstablishmentType.objects.create( + name="Test establishment type") # Create lang object self.lang = Language.objects.get( @@ -42,12 +43,15 @@ class BaseTestCase(APITestCase): country=self.country_ru) self.region.save() - self.city = City.objects.create(name='Mosocow', code='01', - region=self.region, country=self.country_ru) + self.city = City.objects.create( + name='Mosocow', code='01', + region=self.region, + country=self.country_ru) self.city.save() - self.address = Address.objects.create(city=self.city, street_name_1='Krasnaya', - number=2, postal_code='010100') + self.address = Address.objects.create( + city=self.city, street_name_1='Krasnaya', + number=2, postal_code='010100') self.address.save() self.role = Role.objects.create(role=Role.ESTABLISHMENT_MANAGER) @@ -63,8 +67,9 @@ class BaseTestCase(APITestCase): self.establishment.save() - self.user_role = UserRole.objects.create(user=self.user, role=self.role, - establishment=self.establishment) + self.user_role = UserRole.objects.create( + user=self.user, role=self.role, + establishment=self.establishment) self.user_role.save() @@ -128,12 +133,11 @@ class EmployeeTests(BaseTestCase): self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) -# Class to test childs class ChildTestCase(BaseTestCase): def setUp(self): super().setUp() -# Test childs + class EmailTests(ChildTestCase): def setUp(self): super().setUp() diff --git a/apps/favorites/tests.py b/apps/favorites/tests.py index cebd43c2..5f0a2fcd 100644 --- a/apps/favorites/tests.py +++ b/apps/favorites/tests.py @@ -61,9 +61,6 @@ class BaseTestCase(APITestCase): description={"en-GB": "description of test establishment"}, establishment_type=self.test_establishment_type, is_publish=True) - # value for GenericRelation(reverse side) field must be iterable - # value for GenericRelation(reverse side) field must be assigned through - # "set" method of field self.test_establishment.favorites.set([self.test_favorites]) diff --git a/apps/location/tests.py b/apps/location/tests.py index cb574036..4e192831 100644 --- a/apps/location/tests.py +++ b/apps/location/tests.py @@ -19,15 +19,10 @@ class BaseTestCase(APITestCase): self.user = User.objects.create_user( username=self.username, email=self.email, password=self.password) - # get tokens - - # self.user.is_superuser = True - # self.user.save() - - tokkens = User.create_jwt_tokens(self.user) + tokens = User.create_jwt_tokens(self.user) self.client.cookies = SimpleCookie( - {'access_token': tokkens.get('access_token'), - 'refresh_token': tokkens.get('refresh_token')}) + {'access_token': tokens.get('access_token'), + 'refresh_token': tokens.get('refresh_token')}) self.lang = Language.objects.get( title='Russia', @@ -102,7 +97,6 @@ class RegionTests(BaseTestCase): user_role.save() - def test_region_CRUD(self): response = self.client.get('/api/back/location/regions/', format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/apps/notification/tests.py b/apps/notification/tests.py index d78c7fca..87264435 100644 --- a/apps/notification/tests.py +++ b/apps/notification/tests.py @@ -15,10 +15,10 @@ class BaseTestCase(APITestCase): self.password = 'sedragurdaredips19' self.email = 'sedragurda@desoz.com' self.user = User.objects.create_user(username=self.username, email=self.email, password=self.password) - # get tokkens - tokkens = User.create_jwt_tokens(self.user) - self.client.cookies = SimpleCookie({'access_token': tokkens.get('access_token'), - 'refresh_token': tokkens.get('refresh_token')}) + # get tokens + tokens = User.create_jwt_tokens(self.user) + self.client.cookies = SimpleCookie({'access_token': tokens.get('access_token'), + 'refresh_token': tokens.get('refresh_token')}) class NotificationAnonSubscribeTestCase(APITestCase): diff --git a/apps/recipe/tests.py b/apps/recipe/tests.py index d0a17a36..68359be0 100644 --- a/apps/recipe/tests.py +++ b/apps/recipe/tests.py @@ -27,7 +27,5 @@ class BaseTestCase(APITestCase): self.assertEqual(response.status_code, status.HTTP_200_OK) def test_recipe_detail(self): - print(self.test_recipe.id) response = self.client.get(f"/api/web/recipes/{self.test_recipe.id}/") - print(response.json()) self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/apps/utils/tests/tests_translated.py b/apps/utils/tests/tests_translated.py index 77b67d8a..4569ecf2 100644 --- a/apps/utils/tests/tests_translated.py +++ b/apps/utils/tests/tests_translated.py @@ -62,7 +62,6 @@ class TranslateFieldTests(BaseTestCase): response = self.client.get(f"/api/web/news/slug/{self.news_item.slug}/", format='json') self.assertEqual(response.status_code, status.HTTP_200_OK) news_data = response.json() - print(news_data) self.assertIn("title_translated", news_data) self.assertIn("Test news item", news_data['title_translated']) From 7f4b46dbf83e989bad971831090d086f0896f3c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Fri, 25 Oct 2019 12:42:01 +0300 Subject: [PATCH 213/223] Fix country admin --- apps/comment/tests.py | 2 +- apps/comment/views/back.py | 5 +++-- apps/location/serializers/back.py | 1 + apps/location/tests.py | 5 ----- apps/location/views/back.py | 4 ++-- apps/utils/permissions.py | 28 +++++++++++++++++++++++----- 6 files changed, 30 insertions(+), 15 deletions(-) diff --git a/apps/comment/tests.py b/apps/comment/tests.py index e91ee2f4..786f68d3 100644 --- a/apps/comment/tests.py +++ b/apps/comment/tests.py @@ -90,7 +90,7 @@ class CommentModeratorPermissionTests(BasePermissionTests): def test_get(self): response = self.client.get(self.url, format='json') - self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + self.assertEqual(response.status_code, status.HTTP_200_OK) def test_put_other_user(self): other_user = User.objects.create_user(username='test', diff --git a/apps/comment/views/back.py b/apps/comment/views/back.py index 8d836177..3b96cbd2 100644 --- a/apps/comment/views/back.py +++ b/apps/comment/views/back.py @@ -8,12 +8,13 @@ class CommentLstView(generics.ListCreateAPIView): """Comment list create view.""" serializer_class = serializers.CommentBaseSerializer queryset = models.Comment.objects.all() - permission_classes = [permissions.IsAuthenticatedOrReadOnly|IsCountryAdmin|IsCommentModerator] + permission_classes = [permissions.IsAuthenticatedOrReadOnly| IsCommentModerator|IsCountryAdmin] class CommentRUDView(generics.RetrieveUpdateDestroyAPIView): """Comment RUD view.""" serializer_class = serializers.CommentBaseSerializer queryset = models.Comment.objects.all() - permission_classes = [IsCountryAdmin|IsCommentModerator] + + permission_classes = [IsCountryAdmin | IsCommentModerator] lookup_field = 'id' diff --git a/apps/location/serializers/back.py b/apps/location/serializers/back.py index f25aacf6..c178f7fd 100644 --- a/apps/location/serializers/back.py +++ b/apps/location/serializers/back.py @@ -16,4 +16,5 @@ class CountryBackSerializer(common.CountrySerializer): 'code', 'svg_image', 'name', + 'country_id' ] diff --git a/apps/location/tests.py b/apps/location/tests.py index cb574036..eed68071 100644 --- a/apps/location/tests.py +++ b/apps/location/tests.py @@ -19,11 +19,6 @@ class BaseTestCase(APITestCase): self.user = User.objects.create_user( username=self.username, email=self.email, password=self.password) - # get tokens - - # self.user.is_superuser = True - # self.user.save() - tokkens = User.create_jwt_tokens(self.user) self.client.cookies = SimpleCookie( {'access_token': tokkens.get('access_token'), diff --git a/apps/location/views/back.py b/apps/location/views/back.py index cb8246a4..1cdd91da 100644 --- a/apps/location/views/back.py +++ b/apps/location/views/back.py @@ -4,7 +4,7 @@ from rest_framework import generics from location import models, serializers from location.views import common from utils.permissions import IsCountryAdmin - +from rest_framework.permissions import IsAuthenticatedOrReadOnly # Address class AddressListCreateView(common.AddressViewMixin, generics.ListCreateAPIView): """Create view for model Address.""" @@ -50,7 +50,7 @@ class CountryListCreateView(generics.ListCreateAPIView): queryset = models.Country.objects.all() serializer_class = serializers.CountryBackSerializer pagination_class = None - permission_classes = [IsCountryAdmin] + permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin] class CountryRUDView(generics.RetrieveUpdateDestroyAPIView): """RUD view for model Country.""" diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 8ad1ae32..2a10200c 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -56,7 +56,15 @@ class IsGuest(permissions.IsAuthenticatedOrReadOnly): Object-level permission to only allow owners of an object to edit it. """ def has_permission(self, request, view): - return request.user.is_authenticated + rules = [ + request.method in permissions.SAFE_METHODS + ] + # if hasattr(request, 'user.is_superuser'): + # rules = [ + # request.user.is_superuser, + # request.method in permissions.SAFE_METHODS + # ] + return any(rules) def has_object_permission(self, request, view, obj): @@ -131,7 +139,6 @@ class IsCountryAdmin(IsStandardUser): rules = [ super().has_permission(request, view) ] - # and request.user.email_confirmed, if hasattr(request.data, 'user') and hasattr(request.data, 'country_id'): # Read permissions are allowed to any request. @@ -153,9 +160,20 @@ class IsCountryAdmin(IsStandardUser): .first() # 'Comments moderator' rules = [ - UserRole.objects.filter(user=request.user, role=role).exists(), - super().has_object_permission(request, view, obj), - ] + super().has_object_permission(request, view, obj) + ] + # and request.user.email_confirmed, + if hasattr(request, 'user') and request.user.is_authenticated: + rules = [ + UserRole.objects.filter(user=request.user, role=role).exists(), + super().has_object_permission(request, view, obj), + ] + + if hasattr(request.data, 'user'): + rules = [ + UserRole.objects.filter(user=request.data.user, role=role).exists(), + super().has_object_permission(request, view, obj), + ] return any(rules) From 91f6503a26fe1b22d7b9c60bd5d68822f2c03995 Mon Sep 17 00:00:00 2001 From: Semyon Yekhmenin Date: Fri, 25 Oct 2019 10:54:01 +0000 Subject: [PATCH 214/223] Added country name to site settings --- apps/main/serializers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/main/serializers.py b/apps/main/serializers.py index a0af662b..f939c448 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -63,6 +63,8 @@ class SiteSettingsSerializer(serializers.ModelSerializer): # todo: remove this country_code = serializers.CharField(source='subdomain', read_only=True) + country_name = serializers.CharField(source='country.name_translated', read_only=True) + class Meta: """Meta class.""" @@ -78,7 +80,8 @@ class SiteSettingsSerializer(serializers.ModelSerializer): 'config', 'ad_config', 'published_features', - 'currency' + 'currency', + 'country_name' ) From 7f23f0e891455324d1832a44e0053f3ffdadb489 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 14:11:07 +0300 Subject: [PATCH 215/223] Establishment each hour reindexing --- apps/establishment/models.py | 5 +++++ apps/establishment/tasks.py | 12 ++++++++++-- apps/search_indexes/documents/establishment.py | 2 +- project/settings/base.py | 10 +++++++++- requirements/base.txt | 3 ++- 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 304ea2a6..4a6a95e4 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -120,6 +120,11 @@ class EstablishmentQuerySet(models.QuerySet): def with_type_related(self): return self.prefetch_related('establishment_subtypes') + def with_es_related(self): + """Return qs with related for ES indexing objects.""" + return self.select_related('address', 'establishment_type', 'address__city', 'address__city__country').\ + prefetch_related('tags', 'schedule') + def search(self, value, locale=None): """Search text in JSON fields.""" if locale is not None: diff --git a/apps/establishment/tasks.py b/apps/establishment/tasks.py index cf23a7e6..978f29ef 100644 --- a/apps/establishment/tasks.py +++ b/apps/establishment/tasks.py @@ -1,10 +1,13 @@ """Establishment app tasks.""" import logging + from celery import shared_task +from django.core import management +from django_elasticsearch_dsl.management.commands import search_index + from establishment import models from location.models import Country - logger = logging.getLogger(__name__) @@ -12,10 +15,15 @@ logger = logging.getLogger(__name__) def recalculate_price_levels_by_country(country_id): try: country = Country.objects.get(pk=country_id) - except Country.DoesNotExist as ex: + except Country.DoesNotExist as _: logger.error(f'ESTABLISHMENT. Country does not exist. ID {country_id}') else: qs = models.Establishment.objects.filter(address__city__country=country) for establishment in qs: establishment.recalculate_price_level(low_price=country.low_price, high_price=country.high_price) + +@shared_task +def rebuild_establishment_indices(): + management.call_command(search_index.Command(), action='rebuild', models=[models.Establishment.__name__], + force=True) diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index 5d858321..665fc85d 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -106,4 +106,4 @@ class EstablishmentDocument(Document): ) def get_queryset(self): - return super().get_queryset().published() + return super().get_queryset().published().with_es_related() diff --git a/project/settings/base.py b/project/settings/base.py index f9524f94..8a65c1bb 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -13,6 +13,7 @@ https://docs.djangoproject.com/en/2.2/ref/settings/ import os import sys from datetime import timedelta +from celery.schedules import crontab # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -98,7 +99,8 @@ EXTERNAL_APPS = [ 'timezone_field', 'storages', 'sorl.thumbnail', - 'timezonefinder' + 'timezonefinder', + 'django_celery_beat', ] @@ -324,6 +326,12 @@ CELERY_ACCEPT_CONTENT = ['application/json'] CELERY_TASK_SERIALIZER = 'json' CELERY_RESULT_SERIALIZER = 'json' CELERY_TIMEZONE = TIME_ZONE +CELERY_BEAT_SCHEDULE = { + 'send-summary-every-hour': { + 'task': 'establishment.tasks.rebuild_establishment_indices', + 'schedule': crontab(hour=1), + }, +} # Django FCM (Firebase push notifications) FCM_DJANGO_SETTINGS = { diff --git a/requirements/base.txt b/requirements/base.txt index c95055de..19c29b7d 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -44,4 +44,5 @@ sorl-thumbnail==12.5.0 # temp solution redis==3.2.0 amqp>=2.4.0 -celery==4.3.0rc2 \ No newline at end of file +celery==4.3.0rc2 +django-celery-beat==1.5.0 \ No newline at end of file From dd0b5951328fa09ea47ab1e221950b28b082110e Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 14:11:07 +0300 Subject: [PATCH 216/223] Review fixes --- apps/establishment/tasks.py | 4 +++- project/settings/base.py | 8 -------- requirements/base.txt | 3 +-- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/apps/establishment/tasks.py b/apps/establishment/tasks.py index 978f29ef..b7a39d2d 100644 --- a/apps/establishment/tasks.py +++ b/apps/establishment/tasks.py @@ -2,6 +2,8 @@ import logging from celery import shared_task +from celery.schedules import crontab +from celery.task import periodic_task from django.core import management from django_elasticsearch_dsl.management.commands import search_index @@ -23,7 +25,7 @@ def recalculate_price_levels_by_country(country_id): establishment.recalculate_price_level(low_price=country.low_price, high_price=country.high_price) -@shared_task +@periodic_task(run_every=crontab(minute=60)) def rebuild_establishment_indices(): management.call_command(search_index.Command(), action='rebuild', models=[models.Establishment.__name__], force=True) diff --git a/project/settings/base.py b/project/settings/base.py index 8a65c1bb..a8334839 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -13,7 +13,6 @@ https://docs.djangoproject.com/en/2.2/ref/settings/ import os import sys from datetime import timedelta -from celery.schedules import crontab # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -100,7 +99,6 @@ EXTERNAL_APPS = [ 'storages', 'sorl.thumbnail', 'timezonefinder', - 'django_celery_beat', ] @@ -326,12 +324,6 @@ CELERY_ACCEPT_CONTENT = ['application/json'] CELERY_TASK_SERIALIZER = 'json' CELERY_RESULT_SERIALIZER = 'json' CELERY_TIMEZONE = TIME_ZONE -CELERY_BEAT_SCHEDULE = { - 'send-summary-every-hour': { - 'task': 'establishment.tasks.rebuild_establishment_indices', - 'schedule': crontab(hour=1), - }, -} # Django FCM (Firebase push notifications) FCM_DJANGO_SETTINGS = { diff --git a/requirements/base.txt b/requirements/base.txt index 19c29b7d..c95055de 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -44,5 +44,4 @@ sorl-thumbnail==12.5.0 # temp solution redis==3.2.0 amqp>=2.4.0 -celery==4.3.0rc2 -django-celery-beat==1.5.0 \ No newline at end of file +celery==4.3.0rc2 \ No newline at end of file From 4eaa600fc1baee60176e9a37937670420d331e1a Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 16 Oct 2019 14:11:07 +0300 Subject: [PATCH 217/223] Fix all tests --- apps/establishment/tasks.py | 2 +- apps/utils/tests/tests_translated.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/establishment/tasks.py b/apps/establishment/tasks.py index b7a39d2d..fdc10933 100644 --- a/apps/establishment/tasks.py +++ b/apps/establishment/tasks.py @@ -25,7 +25,7 @@ def recalculate_price_levels_by_country(country_id): establishment.recalculate_price_level(low_price=country.low_price, high_price=country.high_price) -@periodic_task(run_every=crontab(minute=60)) +@periodic_task(run_every=crontab(minute=59)) def rebuild_establishment_indices(): management.call_command(search_index.Command(), action='rebuild', models=[models.Establishment.__name__], force=True) diff --git a/apps/utils/tests/tests_translated.py b/apps/utils/tests/tests_translated.py index 4569ecf2..557c8b5d 100644 --- a/apps/utils/tests/tests_translated.py +++ b/apps/utils/tests/tests_translated.py @@ -9,6 +9,7 @@ from account.models import User from news.models import News, NewsType from establishment.models import Establishment, EstablishmentType, Employee +from location.models import Country class BaseTestCase(APITestCase): @@ -39,7 +40,13 @@ class TranslateFieldTests(BaseTestCase): self.news_type = NewsType.objects.create(name="Test news type") self.news_type.save() + + self.country_ru = Country.objects.get( + name={"en-GB": "Russian"} + ) + self.news_item = News.objects.create( + id=8, created_by=self.user, modified_by=self.user, title={ @@ -52,6 +59,7 @@ class TranslateFieldTests(BaseTestCase): news_type=self.news_type, slug='test', state=News.PUBLISHED, + country=self.country_ru, ) self.news_item.save() From b7831b97393f3ace0a5b4375e593af11b3941a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Fri, 25 Oct 2019 15:25:35 +0300 Subject: [PATCH 218/223] Fix IsContentPageManager --- apps/location/views/back.py | 19 ++++++++++++------- apps/news/tests.py | 16 ++++++++++++++++ apps/utils/permissions.py | 26 ++++++++++++++++++++------ 3 files changed, 48 insertions(+), 13 deletions(-) diff --git a/apps/location/views/back.py b/apps/location/views/back.py index 1cdd91da..bb64ff72 100644 --- a/apps/location/views/back.py +++ b/apps/location/views/back.py @@ -6,42 +6,46 @@ from location.views import common from utils.permissions import IsCountryAdmin from rest_framework.permissions import IsAuthenticatedOrReadOnly # Address + + class AddressListCreateView(common.AddressViewMixin, generics.ListCreateAPIView): """Create view for model Address.""" serializer_class = serializers.AddressDetailSerializer queryset = models.Address.objects.all() - permission_classes = [IsCountryAdmin] + permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin] class AddressRUDView(common.AddressViewMixin, generics.RetrieveUpdateDestroyAPIView): """RUD view for model Address.""" serializer_class = serializers.AddressDetailSerializer queryset = models.Address.objects.all() - permission_classes = [IsCountryAdmin] + permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin] # City class CityListCreateView(common.CityViewMixin, generics.ListCreateAPIView): """Create view for model City.""" serializer_class = serializers.CitySerializer - permission_classes = [IsCountryAdmin] + permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin] + class CityRUDView(common.CityViewMixin, generics.RetrieveUpdateDestroyAPIView): """RUD view for model City.""" serializer_class = serializers.CitySerializer - permission_classes = [IsCountryAdmin] + permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin] # Region class RegionListCreateView(common.RegionViewMixin, generics.ListCreateAPIView): """Create view for model Region""" serializer_class = serializers.RegionSerializer - permission_classes = [IsCountryAdmin] + permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin] + class RegionRUDView(common.RegionViewMixin, generics.RetrieveUpdateDestroyAPIView): """Retrieve view for model Region""" serializer_class = serializers.RegionSerializer - permission_classes = [IsCountryAdmin] + permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin] # Country @@ -52,8 +56,9 @@ class CountryListCreateView(generics.ListCreateAPIView): pagination_class = None permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin] + class CountryRUDView(generics.RetrieveUpdateDestroyAPIView): """RUD view for model Country.""" serializer_class = serializers.CountryBackSerializer - permission_classes = [IsCountryAdmin] + permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin] queryset = models.Country.objects.all() \ No newline at end of file diff --git a/apps/news/tests.py b/apps/news/tests.py index 115763e5..77dbca8e 100644 --- a/apps/news/tests.py +++ b/apps/news/tests.py @@ -66,6 +66,22 @@ class NewsTestCase(BaseTestCase): def setUp(self): super().setUp() + def test_news_post(self): + test_news ={ + "title": {"en-GB": "Test news POST"}, + "news_type_id": self.test_news_type.id, + "description": {"en-GB": "Description test news"}, + "start": datetime.now() + timedelta(hours=-2), + "end": datetime.now() + timedelta(hours=2), + "state": News.PUBLISHED, + "slug": 'test-news-slug_post', + "country_id": self.country_ru.id, + } + + url = reverse("back:news:list-create") + response = self.client.post(url, data=test_news, format='json') + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + def test_web_news(self): response = self.client.get(reverse('web:news:list')) self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 2a10200c..7ee7811b 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -57,13 +57,9 @@ class IsGuest(permissions.IsAuthenticatedOrReadOnly): """ def has_permission(self, request, view): rules = [ + request.user.is_superuser, request.method in permissions.SAFE_METHODS ] - # if hasattr(request, 'user.is_superuser'): - # rules = [ - # request.user.is_superuser, - # request.method in permissions.SAFE_METHODS - # ] return any(rules) def has_object_permission(self, request, view, obj): @@ -114,6 +110,24 @@ class IsContentPageManager(IsStandardUser): Object-level permission to only allow owners of an object to edit it. Assumes the model instance has an `owner` attribute. """ + + def has_permission(self, request, view): + rules = [ + super().has_permission(request, view) + ] + # and request.user.email_confirmed, + if hasattr(request, 'user'): + role = Role.objects.filter(role=Role.CONTENT_PAGE_MANAGER, + country_id=request.country_id)\ + .first() # 'Comments moderator' + + rules = [ + UserRole.objects.filter(user=request.user, role=role).exists(), + # and obj.user != request.user, + super().has_permission(request, view) + ] + return any(rules) + def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request. @@ -134,8 +148,8 @@ class IsCountryAdmin(IsStandardUser): Object-level permission to only allow owners of an object to edit it. Assumes the model instance has an `owner` attribute. """ - def has_permission(self, request, view): + rules = [ super().has_permission(request, view) ] From a38fed847a0f7601e320046629e2184f97689a61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=20=D0=93=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BA=D0=B8=D1=85?= Date: Fri, 25 Oct 2019 15:51:07 +0300 Subject: [PATCH 219/223] Fix roles --- apps/utils/permissions.py | 87 ++++++++++++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 16 deletions(-) diff --git a/apps/utils/permissions.py b/apps/utils/permissions.py index 7ee7811b..86a4be6f 100644 --- a/apps/utils/permissions.py +++ b/apps/utils/permissions.py @@ -20,8 +20,8 @@ class IsAuthenticatedAndTokenIsValid(permissions.BasePermission): access_token = request.COOKIES.get('access_token') if user.is_authenticated and access_token: access_token = AccessToken(access_token) - valid_tokens = user.access_tokens.valid()\ - .by_jti(jti=access_token.payload.get('jti')) + valid_tokens = user.access_tokens.valid() \ + .by_jti(jti=access_token.payload.get('jti')) return valid_tokens.exists() else: return False @@ -31,13 +31,14 @@ class IsRefreshTokenValid(permissions.BasePermission): """ Check if user has a valid refresh token and authenticated """ + def has_permission(self, request, view): """Check permissions by refresh token and default REST permission IsAuthenticated""" refresh_token = request.COOKIES.get('refresh_token') if refresh_token: refresh_token = GMRefreshToken(refresh_token) - refresh_token_qs = JWTRefreshToken.objects.valid()\ - .by_jti(jti=refresh_token.payload.get('jti')) + refresh_token_qs = JWTRefreshToken.objects.valid() \ + .by_jti(jti=refresh_token.payload.get('jti')) return refresh_token_qs.exists() else: return False @@ -55,6 +56,7 @@ class IsGuest(permissions.IsAuthenticatedOrReadOnly): """ Object-level permission to only allow owners of an object to edit it. """ + def has_permission(self, request, view): rules = [ request.user.is_superuser, @@ -63,7 +65,6 @@ class IsGuest(permissions.IsAuthenticatedOrReadOnly): return any(rules) def has_object_permission(self, request, view, obj): - rules = [ request.user.is_superuser, request.method in permissions.SAFE_METHODS @@ -76,6 +77,7 @@ class IsStandardUser(IsGuest): Object-level permission to only allow owners of an object to edit it. Assumes the model instance has an `owner` attribute. """ + def has_permission(self, request, view): rules = [ super().has_permission(request, view) @@ -118,7 +120,7 @@ class IsContentPageManager(IsStandardUser): # and request.user.email_confirmed, if hasattr(request, 'user'): role = Role.objects.filter(role=Role.CONTENT_PAGE_MANAGER, - country_id=request.country_id)\ + country_id=request.country_id) \ .first() # 'Comments moderator' rules = [ @@ -132,7 +134,7 @@ class IsContentPageManager(IsStandardUser): # Read permissions are allowed to any request. role = Role.objects.filter(role=Role.CONTENT_PAGE_MANAGER, - country_id=obj.country_id)\ + country_id=obj.country_id) \ .first() # 'Comments moderator' rules = [ @@ -148,6 +150,7 @@ class IsCountryAdmin(IsStandardUser): Object-level permission to only allow owners of an object to edit it. Assumes the model instance has an `owner` attribute. """ + def has_permission(self, request, view): rules = [ @@ -174,8 +177,8 @@ class IsCountryAdmin(IsStandardUser): .first() # 'Comments moderator' rules = [ - super().has_object_permission(request, view, obj) - ] + super().has_object_permission(request, view, obj) + ] # and request.user.email_confirmed, if hasattr(request, 'user') and request.user.is_authenticated: rules = [ @@ -221,7 +224,7 @@ class IsCommentModerator(IsStandardUser): def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request. role = Role.objects.filter(role=Role.COMMENTS_MODERATOR, - country_id=obj.country_id)\ + country_id=obj.country_id) \ .first() # 'Comments moderator' rules = [ @@ -234,10 +237,28 @@ class IsCommentModerator(IsStandardUser): class IsEstablishmentManager(IsStandardUser): - def has_object_permission(self, request, view, obj): - role = Role.objects.filter(role=Role.ESTABLISHMENT_MANAGER)\ + def has_permission(self, request, view): + rules = [ + super().has_permission(request, view) + ] + + # and request.user.email_confirmed, + if hasattr(request.data, 'user') and hasattr(request.data, 'establishment_id'): + role = Role.objects.filter(role=Role.ESTABLISHMENT_MANAGER) \ .first() # 'Comments moderator' + rules = [ + UserRole.objects.filter(user=request.user, role=role, + establishment_id=request.data.establishment_id + ).exists(), + super().has_permission(request, view) + ] + return any(rules) + + def has_object_permission(self, request, view, obj): + role = Role.objects.filter(role=Role.ESTABLISHMENT_MANAGER) \ + .first() # 'Comments moderator' + rules = [ UserRole.objects.filter(user=request.user, role=role, establishment_id=obj.establishment_id @@ -250,11 +271,28 @@ class IsEstablishmentManager(IsStandardUser): class IsReviewerManager(IsStandardUser): - def has_object_permission(self, request, view, obj): + def has_permission(self, request, view): + rules = [ + super().has_permission(request, view) + ] + # and request.user.email_confirmed, + if hasattr(request.data, 'user') and hasattr(request.data, 'country_id'): + role = Role.objects.filter(role=Role.REVIEWER_MANGER) \ + .first() # 'Comments moderator' + + rules = [ + UserRole.objects.filter(user=request.user, role=role, + establishment_id=request.data.country_id + ).exists(), + super().has_permission(request, view) + ] + return any(rules) + + def has_object_permission(self, request, view, obj): role = Role.objects.filter(role=Role.REVIEWER_MANGER, - country_id=obj.country_id)\ - .first() + country_id=obj.country_id) \ + .first() rules = [ UserRole.objects.filter(user=request.user, role=role).exists(), @@ -266,8 +304,25 @@ class IsReviewerManager(IsStandardUser): class IsRestaurantReviewer(IsStandardUser): - def has_object_permission(self, request, view, obj): + def has_permission(self, request, view): + rules = [ + super().has_permission(request, view) + ] + # and request.user.email_confirmed, + if hasattr(request.data, 'user') and hasattr(request.data, 'object_id'): + role = Role.objects.filter(role=Role.RESTAURANT_REVIEWER) \ + .first() # 'Comments moderator' + + rules = [ + UserRole.objects.filter(user=request.user, role=role, + establishment_id=request.data.object_id + ).exists(), + super().has_permission(request, view) + ] + return any(rules) + + def has_object_permission(self, request, view, obj): content_type = ContentType.objects.get(app_lable='establishment', model='establishment') From d4fe5ebd2ddb4376f2998201e271b93ec2fe761f Mon Sep 17 00:00:00 2001 From: evgeniy-st Date: Fri, 25 Oct 2019 16:06:52 +0300 Subject: [PATCH 220/223] search by type & subtype --- apps/news/serializers.py | 1 + .../search_indexes/documents/establishment.py | 21 +++++++++++++------ apps/search_indexes/serializers.py | 2 +- apps/search_indexes/views.py | 9 ++++++++ 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 2b4e98b6..27d50977 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -97,6 +97,7 @@ class CropImageSerializer(serializers.Serializer): class NewsImageSerializer(serializers.ModelSerializer): """Serializer for returning crop images of news image.""" + orientation_display = serializers.CharField(source='get_orientation_display', read_only=True) original_url = serializers.URLField(source='image.url') diff --git a/apps/search_indexes/documents/establishment.py b/apps/search_indexes/documents/establishment.py index 5d858321..5d26ee59 100644 --- a/apps/search_indexes/documents/establishment.py +++ b/apps/search_indexes/documents/establishment.py @@ -10,6 +10,16 @@ EstablishmentIndex = Index(settings.ELASTICSEARCH_INDEX_NAMES.get(__name__, EstablishmentIndex.settings(number_of_shards=1, number_of_replicas=1) +# todo: check & refactor +class ObjectField(fields.ObjectField): + + def get_value_from_instance(self, *args, **kwargs): + value = super(ObjectField, self).get_value_from_instance(*args, **kwargs) + if value == {}: + return None + return value + + @EstablishmentIndex.doc_type class EstablishmentDocument(Document): """Establishment document.""" @@ -22,14 +32,13 @@ class EstablishmentDocument(Document): 'id': fields.IntegerField(), 'name': fields.ObjectField(attr='name_indexing', properties=OBJECT_FIELD_PROPERTIES), + 'index_name': fields.KeywordField(attr='index_name'), }) establishment_subtypes = fields.ObjectField( properties={ 'id': fields.IntegerField(), - 'name': fields.ObjectField(attr='name_indexing', - properties={ - 'id': fields.IntegerField(), - }), + 'name': fields.ObjectField(attr='name_indexing'), + 'index_name': fields.KeywordField(attr='index_name'), }, multi=True) works_evening = fields.ListField(fields.IntegerField( @@ -54,7 +63,7 @@ class EstablishmentDocument(Document): 'closed_at': fields.KeywordField(attr='closed_at_str'), } )) - address = fields.ObjectField( + address = ObjectField( properties={ 'id': fields.IntegerField(), 'street_name_1': fields.TextField( @@ -82,7 +91,7 @@ class EstablishmentDocument(Document): ), } ), - } + }, ) # todo: need to fix # collections = fields.ObjectField( diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index d4ab2dbf..cb1fe735 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -75,7 +75,7 @@ class NewsDocumentSerializer(DocumentSerializer): class EstablishmentDocumentSerializer(DocumentSerializer): """Establishment document serializer.""" - address = AddressDocumentSerializer() + address = AddressDocumentSerializer(allow_null=True) tags = TagsDocumentSerializer(many=True) schedule = ScheduleDocumentSerializer(many=True, allow_null=True) diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index 25205bac..fd1c5b7a 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -124,6 +124,15 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): 'establishment_subtypes': { 'field': 'establishment_subtypes.id' }, + 'type': { + 'field': 'establishment_type.index_name' + }, + 'subtype': { + 'field': 'establishment_subtypes.index_name', + 'lookups': [ + constants.LOOKUP_QUERY_IN, + ], + }, 'works_noon': { 'field': 'works_noon', 'lookups': [ From 7d0acba51b04b4156bd07c4e45a0a956509b6377 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 25 Oct 2019 16:18:16 +0300 Subject: [PATCH 221/223] added to same_theme and should_read preview_image_url, added UniqueConstraint to NewsGallery model --- .../migrations/0029_auto_20191025_1241.py | 18 +++++++++++++++ apps/news/models.py | 2 +- apps/news/serializers.py | 22 +++++++++++-------- apps/news/views.py | 2 +- 4 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 apps/news/migrations/0029_auto_20191025_1241.py diff --git a/apps/news/migrations/0029_auto_20191025_1241.py b/apps/news/migrations/0029_auto_20191025_1241.py new file mode 100644 index 00000000..8e5bcba4 --- /dev/null +++ b/apps/news/migrations/0029_auto_20191025_1241.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-10-25 12:41 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('gallery', '0003_auto_20191003_1228'), + ('news', '0028_auto_20191024_1649'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='newsgallery', + unique_together={('news', 'image'), ('news', 'is_main')}, + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index dbb2f5bf..9b7190e4 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -235,4 +235,4 @@ class NewsGallery(models.Model): """NewsGallery meta class.""" verbose_name = _('news gallery') verbose_name_plural = _('news galleries') - unique_together = ('news', 'is_main') + unique_together = (('news', 'is_main'), ('news', 'image')) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index 27d50977..1389d20e 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -150,6 +150,17 @@ class NewsBaseSerializer(ProjectModelSerializer): ) +class NewsSimilarListSerializer(NewsBaseSerializer): + """List serializer for News model.""" + preview_image_url = serializers.URLField() + + class Meta(NewsBaseSerializer.Meta): + """Meta class.""" + fields = NewsBaseSerializer.Meta.fields + ( + 'preview_image_url', + ) + + class NewsListSerializer(NewsBaseSerializer): """List serializer for News model.""" @@ -192,8 +203,8 @@ class NewsDetailSerializer(NewsBaseSerializer): class NewsDetailWebSerializer(NewsDetailSerializer): """News detail serializer for web users..""" - same_theme = NewsBaseSerializer(many=True, read_only=True) - should_read = NewsBaseSerializer(many=True, read_only=True) + same_theme = NewsSimilarListSerializer(many=True, read_only=True) + should_read = NewsSimilarListSerializer(many=True, read_only=True) agenda = AgendaSerializer() banner = NewsBannerSerializer() @@ -266,7 +277,6 @@ 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) @@ -279,12 +289,6 @@ class NewsBackOfficeGallerySerializer(serializers.ModelSerializer): news = news_qs.first() image = image_qs.first() - if news.news_gallery.filter(image=image).exists(): - raise serializers.ValidationError({'detail': _('Image is already added')}) - - if is_main and news.news_gallery.main_image().exists(): - raise serializers.ValidationError({'detail': _('Main image is already added')}) - attrs['news'] = news attrs['image'] = image diff --git a/apps/news/views.py b/apps/news/views.py index 9cd5d969..9cbe6b53 100644 --- a/apps/news/views.py +++ b/apps/news/views.py @@ -119,7 +119,7 @@ class NewsBackOfficeGalleryCreateDestroyView(NewsBackOfficeMixinView, return gallery def create(self, request, *args, **kwargs): - """Override create method""" + """Overridden create method""" super().create(request, *args, **kwargs) return Response(status=status.HTTP_201_CREATED) From e1e0f1e997028a838eb4a965a84455a2a9fbdca7 Mon Sep 17 00:00:00 2001 From: Semyon Yekhmenin Date: Fri, 25 Oct 2019 13:41:53 +0000 Subject: [PATCH 222/223] Added index_name in employee position --- .../migrations/0044_position_index_name.py | 18 ++++++++++++++++++ apps/establishment/models.py | 3 +++ apps/establishment/serializers/common.py | 3 ++- 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 apps/establishment/migrations/0044_position_index_name.py diff --git a/apps/establishment/migrations/0044_position_index_name.py b/apps/establishment/migrations/0044_position_index_name.py new file mode 100644 index 00000000..0bf423d1 --- /dev/null +++ b/apps/establishment/migrations/0044_position_index_name.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-10-24 14:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0043_establishment_currency'), + ] + + operations = [ + migrations.AddField( + model_name='position', + name='index_name', + field=models.CharField(db_index=True, max_length=255, null=True, unique=True, verbose_name='Index name'), + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 4a6a95e4..9648c6b0 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -497,6 +497,9 @@ class Position(BaseAttributes, TranslatedFieldsMixin): priority = models.IntegerField(unique=True, null=True, default=None) + index_name = models.CharField(max_length=255, db_index=True, unique=True, + null=True, verbose_name=_('Index name')) + class Meta: """Meta class.""" diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 2846c5c8..14be142a 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -147,12 +147,13 @@ class EstablishmentEmployeeSerializer(serializers.ModelSerializer): position_translated = serializers.CharField(source='position.name_translated') awards = AwardSerializer(source='employee.awards', many=True) priority = serializers.IntegerField(source='position.priority') + position_index_name = serializers.CharField(source='position.index_name') class Meta: """Meta class.""" model = models.Employee - fields = ('id', 'name', 'position_translated', 'awards', 'priority') + fields = ('id', 'name', 'position_translated', 'awards', 'priority', 'position_index_name') class EstablishmentBaseSerializer(ProjectModelSerializer): From e3616ee7f8c8ec4ce49e2dd115cdad819967284d Mon Sep 17 00:00:00 2001 From: Anatoly Date: Fri, 25 Oct 2019 17:31:59 +0300 Subject: [PATCH 223/223] change news_preview geometry string on 300x2260 --- 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 a8334839..fb542fce 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -360,7 +360,7 @@ THUMBNAIL_DEFAULT_OPTIONS = { THUMBNAIL_QUALITY = 85 THUMBNAIL_DEBUG = False SORL_THUMBNAIL_ALIASES = { - 'news_preview': {'geometry_string': '100x100', 'crop': 'center'}, + 'news_preview': {'geometry_string': '300x260', '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'},