From 96f317582747a5f79c9265adc6487062c0868afa Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 10 Dec 2019 09:45:35 +0300 Subject: [PATCH 01/23] fix get_name_translated --- apps/search_indexes/serializers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 3b5561fa..9c08b849 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -102,7 +102,7 @@ class ProductTypeDocumentSerializer(serializers.Serializer): @staticmethod def get_name_translated(obj): - return get_translated_value(obj.name) + return get_translated_value(obj.get['name']) class CityDocumentShortSerializer(serializers.Serializer): @@ -122,7 +122,7 @@ class CountryDocumentSerializer(serializers.Serializer): @staticmethod def get_name_translated(obj): - return get_translated_value(obj.name) + return get_translated_value(obj.get('name')) class AnotherCityDocumentShortSerializer(CityDocumentShortSerializer): From 32e6e20c6b40d536a2bd6cdfa81e57d6d43e303e Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 10 Dec 2019 10:15:04 +0300 Subject: [PATCH 02/23] fix get method --- apps/search_indexes/documents/product.py | 4 ++-- apps/search_indexes/serializers.py | 2 +- docker-compose.mysql.yml | 2 -- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/search_indexes/documents/product.py b/apps/search_indexes/documents/product.py index c9459dd8..6ce36bb7 100644 --- a/apps/search_indexes/documents/product.py +++ b/apps/search_indexes/documents/product.py @@ -5,7 +5,7 @@ from search_indexes.utils import OBJECT_FIELD_PROPERTIES from product import models ProductIndex = Index(settings.ELASTICSEARCH_INDEX_NAMES.get(__name__, 'product')) -ProductIndex.settings(number_of_shards=5, number_of_replicas=2, mapping={'total_fields':{'limit': 3000}}) +ProductIndex.settings(number_of_shards=5, number_of_replicas=2, mapping={'total_fields': {'limit': 3000}}) @ProductIndex.doc_type @@ -155,7 +155,7 @@ class ProductDocument(Document): name_ru = fields.TextField(attr='display_name', analyzer='russian') name_fr = fields.TextField(attr='display_name', analyzer='french') favorites_for_users = fields.ListField(field=fields.IntegerField()) - created = fields.DateField(attr='created') # publishing date (?) + created = fields.DateField(attr='created') # publishing date (?) class Django: model = models.Product diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 9c08b849..c9cacb0f 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -102,7 +102,7 @@ class ProductTypeDocumentSerializer(serializers.Serializer): @staticmethod def get_name_translated(obj): - return get_translated_value(obj.get['name']) + return get_translated_value(obj.get('name')) class CityDocumentShortSerializer(serializers.Serializer): diff --git a/docker-compose.mysql.yml b/docker-compose.mysql.yml index 3dc31d33..d62ff677 100644 --- a/docker-compose.mysql.yml +++ b/docker-compose.mysql.yml @@ -16,8 +16,6 @@ services: - .:/code - - # PostgreSQL database db: build: From 323c2628e02692033bbcc46aa81e54200d4ed601 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Tue, 10 Dec 2019 10:17:58 +0300 Subject: [PATCH 03/23] Add endpoint to get all cities with search string --- apps/location/urls/back.py | 1 + apps/location/views/back.py | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/apps/location/urls/back.py b/apps/location/urls/back.py index c5ef027b..2434dd26 100644 --- a/apps/location/urls/back.py +++ b/apps/location/urls/back.py @@ -10,6 +10,7 @@ urlpatterns = [ path('addresses//', views.AddressRUDView.as_view(), name='address-RUD'), path('cities/', views.CityListCreateView.as_view(), name='city-list-create'), + path('cities/all/', views.CityListSearchView.as_view(), name='city-list-create'), path('cities//', views.CityRUDView.as_view(), name='city-retrieve'), path('cities//gallery/', views.CityGalleryListView.as_view(), name='gallery-list'), diff --git a/apps/location/views/back.py b/apps/location/views/back.py index 8406dee3..fc4499ae 100644 --- a/apps/location/views/back.py +++ b/apps/location/views/back.py @@ -37,6 +37,15 @@ class CityListCreateView(common.CityViewMixin, generics.ListCreateAPIView): filter_class = filters.CityBackFilter +class CityListSearchView(common.CityViewMixin, generics.ListCreateAPIView): + """Create view for model City.""" + serializer_class = serializers.CitySerializer + permission_classes = [IsAuthenticatedOrReadOnly|IsCountryAdmin] + queryset = models.City.objects.all() + filter_class = filters.CityBackFilter + pagination_class = None + + class CityRUDView(common.CityViewMixin, generics.RetrieveUpdateDestroyAPIView): """RUD view for model City.""" serializer_class = serializers.CitySerializer From 0647dd45f8dc2a05423cb2236beb34ab42e8dace Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 10 Dec 2019 10:25:10 +0300 Subject: [PATCH 04/23] fix get_translated_value by type obj --- apps/search_indexes/serializers.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index c9cacb0f..6cb5692a 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -40,6 +40,8 @@ class ProductSubtypeDocumentSerializer(serializers.Serializer): name_translated = serializers.SerializerMethodField() def get_name_translated(self, obj): + if isinstance(obj, dict): + return get_translated_value(obj.get('name')) return get_translated_value(obj.name) @@ -102,7 +104,9 @@ class ProductTypeDocumentSerializer(serializers.Serializer): @staticmethod def get_name_translated(obj): - return get_translated_value(obj.get('name')) + if isinstance(obj, dict): + return get_translated_value(obj.get('name')) + return get_translated_value(obj.name) class CityDocumentShortSerializer(serializers.Serializer): @@ -114,7 +118,6 @@ class CityDocumentShortSerializer(serializers.Serializer): class CountryDocumentSerializer(serializers.Serializer): - id = serializers.IntegerField() code = serializers.CharField(allow_null=True) svg_image = serializers.CharField() @@ -122,11 +125,12 @@ class CountryDocumentSerializer(serializers.Serializer): @staticmethod def get_name_translated(obj): - return get_translated_value(obj.get('name')) + if isinstance(obj, dict): + return get_translated_value(obj.get('name')) + return get_translated_value(obj.name) class AnotherCityDocumentShortSerializer(CityDocumentShortSerializer): - country = CountryDocumentSerializer() def to_representation(self, instance): From 47cb7f8e63f235f352a8a737188d98f2988c9783 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Tue, 10 Dec 2019 17:09:43 +0300 Subject: [PATCH 05/23] Tmp commit --- apps/account/urls/back.py | 1 + apps/account/views/back.py | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/apps/account/urls/back.py b/apps/account/urls/back.py index 30f21573..cf1d114e 100644 --- a/apps/account/urls/back.py +++ b/apps/account/urls/back.py @@ -10,4 +10,5 @@ urlpatterns = [ path('user-role/', views.UserRoleLstView.as_view(), name='user-role-list-create'), path('user/', views.UserLstView.as_view(), name='user-create-list'), path('user//', views.UserRUDView.as_view(), name='user-rud'), + path('user//csv', views.get_user_csv, name='user-csv'), ] diff --git a/apps/account/views/back.py b/apps/account/views/back.py index fbbc986e..c1eb32b5 100644 --- a/apps/account/views/back.py +++ b/apps/account/views/back.py @@ -1,6 +1,8 @@ from django_filters.rest_framework import DjangoFilterBackend from rest_framework import generics, permissions from rest_framework.filters import OrderingFilter +import csv +from django.http import HttpResponse, HttpResponseNotFound from account import models from account.models import User @@ -46,3 +48,22 @@ class UserRUDView(generics.RetrieveUpdateDestroyAPIView): serializer_class = serializers.BackDetailUserSerializer permission_classes = (permissions.IsAdminUser,) lookup_field = 'id' + + +def get_user_csv(request, user_id): + try: + user = User.objects.get(id=user_id).values_list() + except User.DoesNotExist: + return HttpResponseNotFound("User not found") + + # + # + # response = HttpResponse(content_type='text/csv') + # response['Content-Disposition'] = f'attachment; filename="{user}.csv"' + # + # writer = csv.writer(response) + # writer.writerow(['First row', 'Foo', 'Bar', 'Baz']) + # writer.writerow(['Second row', 'A', 'B', 'C', '"Testing"', "Here's a quote"]) + # + # return response + return HttpResponseNotFound(user) \ No newline at end of file From 41659958f74c81bb4ce171e3fac8b2a96b41203f Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 11 Dec 2019 09:10:49 +0300 Subject: [PATCH 06/23] product establishment_detail address --- apps/product/models.py | 2 +- apps/search_indexes/documents/product.py | 28 ++++++------- apps/search_indexes/serializers.py | 50 +++++++++++++++--------- apps/search_indexes/signals.py | 12 +++--- 4 files changed, 52 insertions(+), 40 deletions(-) diff --git a/apps/product/models.py b/apps/product/models.py index b125c7eb..6923d6dd 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -171,7 +171,7 @@ class Product(GalleryModelMixin, TranslatedFieldsMixin, BaseAttributes, default=None, help_text='{"en-GB":"some text"}') available = models.BooleanField(_('available'), default=True) product_type = models.ForeignKey(ProductType, on_delete=models.PROTECT, - null=True, + null=True, blank=True, default=None, related_name='products', verbose_name=_('Type')) subtypes = models.ManyToManyField(ProductSubType, blank=True, related_name='products', diff --git a/apps/search_indexes/documents/product.py b/apps/search_indexes/documents/product.py index 6ce36bb7..2a88f4ad 100644 --- a/apps/search_indexes/documents/product.py +++ b/apps/search_indexes/documents/product.py @@ -14,12 +14,13 @@ class ProductDocument(Document): description = fields.ObjectField(attr='description_indexing', properties=OBJECT_FIELD_PROPERTIES) - product_type = fields.ObjectField(properties={ - 'id': fields.IntegerField(), - 'name': fields.ObjectField(attr='name_indexing', properties=OBJECT_FIELD_PROPERTIES), - 'index_name': fields.KeywordField(), - 'use_subtypes': fields.BooleanField(), - }) + product_type = fields.ObjectField( + properties={ + 'id': fields.IntegerField(), + 'name': fields.ObjectField(attr='name_indexing', properties=OBJECT_FIELD_PROPERTIES), + 'index_name': fields.KeywordField(), + } + ) subtypes = fields.ObjectField( properties={ 'id': fields.IntegerField(), @@ -54,15 +55,12 @@ class ProductDocument(Document): ), 'address': fields.ObjectField( properties={ - 'city': fields.ObjectField( - properties={ - 'country': fields.ObjectField( - properties={ - 'code': fields.KeywordField() - } - ) - } - ) + 'id': fields.IntegerField(), + 'street_name_1': fields.TextField( + fields={'raw': fields.KeywordField()} + ), + 'postal_code': fields.KeywordField(), + 'coordinates': fields.GeoPointField(attr='location_field_indexing'), } ) } diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 34574207..962c4032 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -95,7 +95,7 @@ class TagDocumentSerializer(serializers.Serializer): return get_translated_value(obj.label) -class ProductTypeDocumentSerializer(serializers.Serializer): +class ProductTypeSerializer(serializers.Serializer): """Product type ES document serializer.""" id = serializers.IntegerField() @@ -140,19 +140,6 @@ class AnotherCityDocumentShortSerializer(CityDocumentShortSerializer): return None -class ProductEstablishmentDocumentSerializer(serializers.Serializer): - """Related to Product Establishment ES document serializer.""" - - id = serializers.IntegerField() - name = serializers.CharField() - slug = serializers.CharField() - index_name = serializers.CharField() - city = AnotherCityDocumentShortSerializer() - - def get_attribute(self, instance): - return instance.establishment if instance and instance.establishment else None - - class AddressDocumentSerializer(serializers.Serializer): """Address serializer for ES Document.""" @@ -175,6 +162,34 @@ class AddressDocumentSerializer(serializers.Serializer): return None +class PSAddressDocumentSerializer(serializers.Serializer): + """Address serializer for ES Document.""" + + id = serializers.IntegerField() + street_name_1 = serializers.CharField() + postal_code = serializers.CharField() + + def to_representation(self, instance): + if instance != AttrDict(d={}) or \ + (isinstance(instance, dict) and len(instance) != 0): + return super().to_representation(instance) + return None + + +class ProductEstablishmentSerializer(serializers.Serializer): + """Related to Product Establishment ES document serializer.""" + + id = serializers.IntegerField() + name = serializers.CharField() + slug = serializers.CharField() + index_name = serializers.CharField() + city = AnotherCityDocumentShortSerializer() + address = PSAddressDocumentSerializer(allow_null=True) + + def get_attribute(self, instance): + return instance.establishment if instance and instance.establishment else None + + class ScheduleDocumentSerializer(serializers.Serializer): """Schedule serializer for ES Document""" @@ -289,15 +304,15 @@ class EstablishmentDocumentSerializer(InFavoritesMixin, DocumentSerializer): ) -class ProductDocumentSerializer(InFavoritesMixin, DocumentSerializer): +class ProductDocumentSerializer(InFavoritesMixin): """Product document serializer""" tags = TagsDocumentSerializer(many=True, source='related_tags') subtypes = ProductSubtypeDocumentSerializer(many=True, allow_null=True) wine_colors = TagDocumentSerializer(many=True) grape_variety = TagDocumentSerializer(many=True) - product_type = ProductTypeDocumentSerializer(allow_null=True) - establishment_detail = ProductEstablishmentDocumentSerializer(source='establishment', allow_null=True) + # product_type = ProductTypeSerializer(allow_null=True) + establishment_detail = ProductEstablishmentSerializer(source='establishment', allow_null=True) wine_origins = WineOriginSerializer(many=True) class Meta: @@ -321,7 +336,6 @@ class ProductDocumentSerializer(InFavoritesMixin, DocumentSerializer): 'subtypes', 'wine_colors', 'grape_variety', - 'establishment_detail', 'average_price', 'created', 'wine_origins', diff --git a/apps/search_indexes/signals.py b/apps/search_indexes/signals.py index 5bee4e17..ce7de7f7 100644 --- a/apps/search_indexes/signals.py +++ b/apps/search_indexes/signals.py @@ -12,8 +12,8 @@ def update_document(sender, **kwargs): instance = kwargs['instance'] app_label_model_name_to_filter = { - ('location','country'): 'address__city__country', - ('location','city'): 'address__city', + ('location', 'country'): 'address__city__country', + ('location', 'city'): 'address__city', ('location', 'address'): 'address', # todo: remove after migration ('establishment', 'establishmenttype'): 'establishment_type', @@ -34,8 +34,8 @@ def update_news(sender, **kwargs): model_name = sender._meta.model_name instance = kwargs['instance'] app_label_model_name_to_filter = { - ('location','country'): 'country', - ('news','newstype'): 'news_type', + ('location', 'country'): 'country', + ('news', 'newstype'): 'news_type', ('tag', 'tag'): 'tags', } filter_name = app_label_model_name_to_filter.get((app_label, model_name)) @@ -52,9 +52,9 @@ def update_product(sender, **kwargs): model_name = sender._meta.model_name instance = kwargs['instance'] app_label_model_name_to_filter = { - ('product','productstandard'): 'standards', + ('product', 'productstandard'): 'standards', ('product', 'producttype'): 'product_type', - ('tag','tag'): 'tags', + ('tag', 'tag'): 'tags', ('location', 'wineregion'): 'wine_region', ('location', 'winesubregion'): 'wine_sub_region', ('location', 'winevillage'): 'wine_village', From ca2ac15cdbca5399e96e42a49dfe9332c7e2d63a Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 11 Dec 2019 10:23:59 +0300 Subject: [PATCH 07/23] fix product type --- apps/search_indexes/documents/product.py | 2 +- apps/search_indexes/serializers.py | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/apps/search_indexes/documents/product.py b/apps/search_indexes/documents/product.py index 2a88f4ad..aa8fc999 100644 --- a/apps/search_indexes/documents/product.py +++ b/apps/search_indexes/documents/product.py @@ -19,7 +19,7 @@ class ProductDocument(Document): 'id': fields.IntegerField(), 'name': fields.ObjectField(attr='name_indexing', properties=OBJECT_FIELD_PROPERTIES), 'index_name': fields.KeywordField(), - } + }, ) subtypes = fields.ObjectField( properties={ diff --git a/apps/search_indexes/serializers.py b/apps/search_indexes/serializers.py index 962c4032..4fc5a027 100644 --- a/apps/search_indexes/serializers.py +++ b/apps/search_indexes/serializers.py @@ -108,6 +108,9 @@ class ProductTypeSerializer(serializers.Serializer): return get_translated_value(obj.get('name')) return get_translated_value(obj.name) + def get_attribute(self, instance): + return instance.product_type if instance and instance.product_type else None + class CityDocumentShortSerializer(serializers.Serializer): """City serializer for ES Document,""" @@ -169,12 +172,6 @@ class PSAddressDocumentSerializer(serializers.Serializer): street_name_1 = serializers.CharField() postal_code = serializers.CharField() - def to_representation(self, instance): - if instance != AttrDict(d={}) or \ - (isinstance(instance, dict) and len(instance) != 0): - return super().to_representation(instance) - return None - class ProductEstablishmentSerializer(serializers.Serializer): """Related to Product Establishment ES document serializer.""" @@ -311,7 +308,7 @@ class ProductDocumentSerializer(InFavoritesMixin): subtypes = ProductSubtypeDocumentSerializer(many=True, allow_null=True) wine_colors = TagDocumentSerializer(many=True) grape_variety = TagDocumentSerializer(many=True) - # product_type = ProductTypeSerializer(allow_null=True) + product_type = ProductTypeSerializer(allow_null=True) establishment_detail = ProductEstablishmentSerializer(source='establishment', allow_null=True) wine_origins = WineOriginSerializer(many=True) From a254160f439c67481815676e37be6eed5c218ed7 Mon Sep 17 00:00:00 2001 From: littlewolf Date: Wed, 11 Dec 2019 12:10:26 +0300 Subject: [PATCH 08/23] Add account csv endpoint --- apps/account/views/back.py | 74 +++++++++++++++++++++++++++++++------- 1 file changed, 61 insertions(+), 13 deletions(-) diff --git a/apps/account/views/back.py b/apps/account/views/back.py index c1eb32b5..92dca84d 100644 --- a/apps/account/views/back.py +++ b/apps/account/views/back.py @@ -3,6 +3,7 @@ from rest_framework import generics, permissions from rest_framework.filters import OrderingFilter import csv from django.http import HttpResponse, HttpResponseNotFound +from rest_framework.authtoken.models import Token from account import models from account.models import User @@ -50,20 +51,67 @@ class UserRUDView(generics.RetrieveUpdateDestroyAPIView): lookup_field = 'id' -def get_user_csv(request, user_id): +def get_user_csv(request, id): + # fields = ["id", "uuid", "nickname", "locale", "country_code", "city", "role", "consent_purpose", "consent_at", + # "last_seen_at", "created_at", "updated_at", "email", "is_admin", "ezuser_id", "ez_user_id", + # "encrypted_password", "reset_password_token", "reset_password_sent_at", "remember_created_at", + # "sign_in_count", "current_sign_in_at", "last_sign_in_at", "current_sign_in_ip", "last_sign_in_ip", + # "confirmation_token", "confirmed_at", "confirmation_sent_at", "unconfirmed_email", "webpush_subscription"] + + # uuid == id + # + # Не найдены: + # consent_purpose + # consent_at + # ezuser_id + # ez_user_id + # remember_created_at + # sign_in_count + # current_sign_in_at + # current_sign_in_ip + # last_sign_in_ip + # confirmed_at + # confirmation_sent_at + # webpush_subscription + # + # country_code не получить - клиент не привязан к стране + try: - user = User.objects.get(id=user_id).values_list() + user = User.objects.get(id=id) except User.DoesNotExist: return HttpResponseNotFound("User not found") - # - # - # response = HttpResponse(content_type='text/csv') - # response['Content-Disposition'] = f'attachment; filename="{user}.csv"' - # - # writer = csv.writer(response) - # writer.writerow(['First row', 'Foo', 'Bar', 'Baz']) - # writer.writerow(['Second row', 'A', 'B', 'C', '"Testing"', "Here's a quote"]) - # - # return response - return HttpResponseNotFound(user) \ No newline at end of file + try: + roles = " ".join([role for role in user.roles]) + except: + roles = "" + + token, _ = Token.objects.get_or_create(user=user) + + fields = { + "id": user.id, + "uuid": user.id, + "username": getattr(user, "username", ""), + "locale": getattr(user, "locale", ""), + "city": getattr(user, "city", ""), + "role": roles, + "created_at": getattr(user, "date_joined", ""), + "updated_at": user.last_login, + "email": user.email, + "is_admin": user.is_superuser, + "encrypted_password": user.password, + "reset_password_token": token.key, + "reset_password_sent_at": token.created, # TODO: не уверен в назначении поля, лучше проверить + "last_sign_in_at": user.last_login, # Повтор? + "confirmation_token": user.confirm_email_token, + "unconfirmed_email": 1 if user.unconfirmed_email else 0 + } + + response = HttpResponse(content_type='text/csv') + response['Content-Disposition'] = f'attachment; filename="{user.email}.csv"' + + writer = csv.writer(response) + writer.writerow(fields.keys()) + writer.writerow(fields.values()) + + return response From 822b18e2f40b1e6c27525fcf94dd5db24b0cf19c Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 11 Dec 2019 14:58:31 +0300 Subject: [PATCH 09/23] create new image after crop --- apps/gallery/serializers.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/apps/gallery/serializers.py b/apps/gallery/serializers.py index 36360180..0aa4389f 100644 --- a/apps/gallery/serializers.py +++ b/apps/gallery/serializers.py @@ -1,8 +1,9 @@ from django.conf import settings from django.core.validators import MinValueValidator, MaxValueValidator +from django.core.files.base import ContentFile from rest_framework import serializers -from sorl.thumbnail.parsers import parse_crop -from sorl.thumbnail.parsers import ThumbnailParseError +from sorl.thumbnail import get_thumbnail +from sorl.thumbnail.parsers import parse_crop, ThumbnailParseError from django.utils.translation import gettext_lazy as _ from . import models @@ -88,15 +89,23 @@ class CropImageSerializer(ImageSerializer): quality = validated_data.pop('quality') crop = validated_data.pop('crop') + crop_params = { + 'geometry': f'{width}x{height}', + 'quality': quality, + 'crop': crop, + } + cropped_image = self._image.get_cropped_image(**crop_params) image = self._image + image.pk = None + crop_params['geometry_string'] = crop_params.pop('geometry') + resized = get_thumbnail(self._image.image, **crop_params) + image.image.save(resized.name, ContentFile(resized.read()), True) + image.save() if image and width and height: setattr(image, 'cropped_image', - image.get_cropped_image( - geometry=f'{width}x{height}', - quality=quality, - crop=crop)) + cropped_image) return image @property From 165509bc642d6bf1beac648ddc450abfc09386d9 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 11 Dec 2019 15:13:06 +0300 Subject: [PATCH 10/23] update news_gallery model --- apps/news/serializers.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/news/serializers.py b/apps/news/serializers.py index f22c7129..b45bfa3f 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -243,6 +243,15 @@ class NewsBackOfficeGallerySerializer(serializers.ModelSerializer): """Get url kwargs from request.""" return self.context.get('request').parser_context.get('kwargs') + def create(self, validated_data): + news_pk = self.get_request_kwargs().get('pk') + image_id = self.get_request_kwargs().get('image_id') + news_gallery_model = models.NewsGallery.objects.filter(image_id=image_id, news_id=news_pk).first() + if news_gallery_model: + news_gallery_model.update(**validated_data) + return news_gallery_model + return super().create(validated_data) + def validate(self, attrs): """Override validate method.""" news_pk = self.get_request_kwargs().get('pk') From 39c8f6d15294495e10a2c1723a2e0dcfd5a1d6d4 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 11 Dec 2019 15:47:08 +0300 Subject: [PATCH 11/23] remove carousel hardcode --- apps/main/views/common.py | 3 --- project/settings/base.py | 3 --- 2 files changed, 6 deletions(-) diff --git a/apps/main/views/common.py b/apps/main/views/common.py index 674d045e..20033661 100644 --- a/apps/main/views/common.py +++ b/apps/main/views/common.py @@ -70,9 +70,6 @@ class CarouselListView(generics.ListAPIView): def get_queryset(self): country_code = self.request.country_code - if hasattr(settings, 'CAROUSEL_ITEMS') and country_code in settings.INTERNATIONAL_COUNTRY_CODES: - qs = models.Carousel.objects.filter(id__in=settings.CAROUSEL_ITEMS) - return qs qs = models.Carousel.objects.is_parsed().active() if country_code: qs = qs.by_country_code(country_code) diff --git a/project/settings/base.py b/project/settings/base.py index 31b7b8f7..2dfe1ba1 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -516,9 +516,6 @@ PHONENUMBER_DEFAULT_REGION = "FR" FALLBACK_LOCALE = 'en-GB' -# TMP TODO remove it later -# Временный хардкод для демонстрации > 15 ноября, потом удалить! -CAROUSEL_ITEMS = [465] ESTABLISHMENT_CHOSEN_TAGS = ['gastronomic', 'en_vogue', 'terrace', 'streetfood', 'business', 'bar_cocktail', 'brunch', 'pop'] NEWS_CHOSEN_TAGS = ['eat', 'drink', 'cook', 'style', 'international', 'event', 'partnership'] INTERNATIONAL_COUNTRY_CODES = ['www', 'main', 'next'] From f0709fa409fc029d2ef1191ef7b4ff746b882c8b Mon Sep 17 00:00:00 2001 From: dormantman Date: Wed, 11 Dec 2019 16:16:08 +0300 Subject: [PATCH 12/23] Fixed tag filter type --- apps/tag/views.py | 77 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 23 deletions(-) diff --git a/apps/tag/views.py b/apps/tag/views.py index db3970e5..dad2582f 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -78,9 +78,26 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): result_list = serializer.data query_params = request.query_params - params_type = query_params['type'] + params_type = query_params.get('type') - if params_type == 'restaurant' and 'toque_number__in' in query_params: + week_days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") + flags = ('toque_number', 'wine_region', 'works_noon', 'works_evening', 'works_now') + filter_flags = {flag_name: False for flag_name in flags} + additional_flags = [] + + if params_type == 'restaurant': + additional_flags += ['toque_number', 'works_noon', 'works_evening', 'works_now'] + + elif params_type == 'winery': + additional_flags += ['wine_region'] + + elif params_type == 'artisan': + additional_flags += ['works_now', 'works_at_weekday'] + + for flag_name in additional_flags: + filter_flags[flag_name] = True + + if filter_flags['toque_number']: toques = { "index_name": "toque_number", "label_translated": "Toques", @@ -93,28 +110,29 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): } result_list.append(toques) - if params_type == 'winery' and 'wine_region_id__in' in query_params: - try: - wine_region_id = int(query_params['wine_region_id__in']) + if filter_flags['wine_region']: + wine_region_id = query_params.get('wine_region_id__in') - wine_regions = { - "index_name": "wine_region", - "label_translated": "Wine region", - "param_name": "wine_region_id__in", - "filters": [{ - "id": obj.id, - "index_name": obj.name.lower().replace(' ', '_'), - "label_translated": obj.name - } for obj in WineRegion.objects.filter(id=wine_region_id)] - } + if str(wine_region_id).isdigit(): + queryset = WineRegion.objects.filter(id=int(wine_region_id)) - result_list.append(wine_regions) + else: + queryset = WineRegion.objects.all() - except ValueError: - pass + wine_regions = { + "index_name": "wine_region", + "label_translated": "Wine region", + "param_name": "wine_region_id__in", + "filters": [{ + "id": obj.id, + "index_name": obj.name.lower().replace(' ', '_'), + "label_translated": obj.name + } for obj in queryset] + } - if params_type == 'restaurant' and 'works_noon__in' in query_params: - week_days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") + result_list.append(wine_regions) + + if filter_flags['works_noon']: works_noon = { "index_name": "works_noon", "label_translated": "Open noon", @@ -127,8 +145,8 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): } result_list.append(works_noon) - if params_type == 'restaurant' and 'works_evening__in' in query_params: - week_days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") + if filter_flags['works_evening']: + works_evening = { "index_name": "works_evening", "label_translated": "Open evening", @@ -141,7 +159,7 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): } result_list.append(works_evening) - if params_type in ('restaurant', 'artisan') and 'works_now' in query_params: + if filter_flags['works_now']: works_now = { "index_name": "open_now", "label_translated": "Open now", @@ -150,6 +168,19 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): } result_list.append(works_now) + if filter_flags['works_at_weekday']: + works_at_weekday = { + "index_name": "works_at_weekday", + "label_translated": "Works at weekday", + "param_name": "works_at_weekday__in", + "filters": [{ + "id": weekday, + "index_name": week_days[weekday].lower(), + "label_translated": week_days[weekday] + } for weekday in range(7)] + } + result_list.append(works_at_weekday) + if 'tags_id__in' in query_params: # filtering by params_type and tags id # todo: result_list.append( filtering_data ) From d8e3ad0bb5179c87d3afb85985de7b06ae8c02e1 Mon Sep 17 00:00:00 2001 From: dormantman Date: Wed, 11 Dec 2019 16:21:30 +0300 Subject: [PATCH 13/23] Added works_at_weekday --- apps/tag/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/tag/views.py b/apps/tag/views.py index dad2582f..75472fff 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -81,7 +81,7 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): params_type = query_params.get('type') week_days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") - flags = ('toque_number', 'wine_region', 'works_noon', 'works_evening', 'works_now') + flags = ('toque_number', 'wine_region', 'works_noon', 'works_evening', 'works_now', 'works_at_weekday') filter_flags = {flag_name: False for flag_name in flags} additional_flags = [] From 9f27b0c5d9e8246ae65b644ff724191fcdab615d Mon Sep 17 00:00:00 2001 From: dormantman Date: Wed, 11 Dec 2019 16:27:18 +0300 Subject: [PATCH 14/23] Added different types --- apps/tag/views.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/tag/views.py b/apps/tag/views.py index 75472fff..d506e23f 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -79,6 +79,10 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): query_params = request.query_params params_type = query_params.get('type') + if query_params.get('establishment_type'): + params_type = query_params.get('establishment_type') + elif query_params.get('product_type'): + params_type = query_params.get('product_type') week_days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") flags = ('toque_number', 'wine_region', 'works_noon', 'works_evening', 'works_now', 'works_at_weekday') @@ -97,6 +101,8 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): for flag_name in additional_flags: filter_flags[flag_name] = True + print(filter_flags) + if filter_flags['toque_number']: toques = { "index_name": "toque_number", From 0b1b960108b970c02562e15da1bf5e5a14b79ec5 Mon Sep 17 00:00:00 2001 From: dormantman Date: Wed, 11 Dec 2019 16:27:52 +0300 Subject: [PATCH 15/23] Remove excess output --- apps/tag/views.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/tag/views.py b/apps/tag/views.py index d506e23f..e56447bc 100644 --- a/apps/tag/views.py +++ b/apps/tag/views.py @@ -101,8 +101,6 @@ class FiltersTagCategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): for flag_name in additional_flags: filter_flags[flag_name] = True - print(filter_flags) - if filter_flags['toque_number']: toques = { "index_name": "toque_number", From f182ec4981a1fe9739613f29f435027a9437d5d8 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 11 Dec 2019 17:07:29 +0300 Subject: [PATCH 16/23] remove wrong unique constraint --- .../news/migrations/0041_auto_20191211_1406.py | 18 ++++++++++++++++++ apps/news/models.py | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 apps/news/migrations/0041_auto_20191211_1406.py diff --git a/apps/news/migrations/0041_auto_20191211_1406.py b/apps/news/migrations/0041_auto_20191211_1406.py new file mode 100644 index 00000000..70ca5cf1 --- /dev/null +++ b/apps/news/migrations/0041_auto_20191211_1406.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.7 on 2019-12-11 14:06 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('gallery', '0006_merge_20191027_1758'), + ('news', '0040_remove_news_slug'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='newsgallery', + unique_together={('news', 'image')}, + ), + ] diff --git a/apps/news/models.py b/apps/news/models.py index f54378b6..69ccf392 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -320,4 +320,4 @@ class NewsGallery(IntermediateGalleryModelMixin): """NewsGallery meta class.""" verbose_name = _('news gallery') verbose_name_plural = _('news galleries') - unique_together = (('news', 'is_main'), ('news', 'image')) + unique_together = (('news', 'image'),) From 90f75c6da53287bf3050a8787099f1b69660edd4 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 11 Dec 2019 17:54:53 +0300 Subject: [PATCH 17/23] update news gallery --- ..._20191211_1406.py => 0041_auto_20191211_1424.py} | 4 ++-- apps/news/models.py | 2 +- apps/news/serializers.py | 13 +++++++------ 3 files changed, 10 insertions(+), 9 deletions(-) rename apps/news/migrations/{0041_auto_20191211_1406.py => 0041_auto_20191211_1424.py} (75%) diff --git a/apps/news/migrations/0041_auto_20191211_1406.py b/apps/news/migrations/0041_auto_20191211_1424.py similarity index 75% rename from apps/news/migrations/0041_auto_20191211_1406.py rename to apps/news/migrations/0041_auto_20191211_1424.py index 70ca5cf1..87d3295d 100644 --- a/apps/news/migrations/0041_auto_20191211_1406.py +++ b/apps/news/migrations/0041_auto_20191211_1424.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.7 on 2019-12-11 14:06 +# Generated by Django 2.2.7 on 2019-12-11 14:24 from django.db import migrations @@ -13,6 +13,6 @@ class Migration(migrations.Migration): operations = [ migrations.AlterUniqueTogether( name='newsgallery', - unique_together={('news', 'image')}, + unique_together={('news', 'image'),}, ), ] diff --git a/apps/news/models.py b/apps/news/models.py index 69ccf392..b4e1b5d5 100644 --- a/apps/news/models.py +++ b/apps/news/models.py @@ -320,4 +320,4 @@ class NewsGallery(IntermediateGalleryModelMixin): """NewsGallery meta class.""" verbose_name = _('news gallery') verbose_name_plural = _('news galleries') - unique_together = (('news', 'image'),) + unique_together = [['news', 'image'],] diff --git a/apps/news/serializers.py b/apps/news/serializers.py index b45bfa3f..41b6ebc4 100644 --- a/apps/news/serializers.py +++ b/apps/news/serializers.py @@ -246,10 +246,11 @@ class NewsBackOfficeGallerySerializer(serializers.ModelSerializer): def create(self, validated_data): news_pk = self.get_request_kwargs().get('pk') image_id = self.get_request_kwargs().get('image_id') - news_gallery_model = models.NewsGallery.objects.filter(image_id=image_id, news_id=news_pk).first() - if news_gallery_model: - news_gallery_model.update(**validated_data) - return news_gallery_model + qs = models.NewsGallery.objects.filter(image_id=image_id, news_id=news_pk) + instance = qs.first() + if instance: + qs.update(**validated_data) + return instance return super().create(validated_data) def validate(self, attrs): @@ -268,8 +269,8 @@ class NewsBackOfficeGallerySerializer(serializers.ModelSerializer): news = news_qs.first() image = image_qs.first() - if image in news.gallery.all(): - raise serializers.ValidationError({'detail': _('Image is already added.')}) + # if image in news.gallery.all(): + # raise serializers.ValidationError({'detail': _('Image is already added.')}) attrs['news'] = news attrs['image'] = image From 56501d76f28297b6bb5b722e553533457f5c710b Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 11 Dec 2019 18:20:53 +0300 Subject: [PATCH 18/23] order images --- apps/gallery/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/gallery/models.py b/apps/gallery/models.py index baed48fc..22c5b5e7 100644 --- a/apps/gallery/models.py +++ b/apps/gallery/models.py @@ -34,6 +34,7 @@ class Image(ProjectBaseMixin, SORLImageMixin, PlatformMixin): """Meta class.""" verbose_name = _('Image') verbose_name_plural = _('Images') + ordering = ['-modified'] def __str__(self): """String representation""" From 370f9cc7ce21300a3b39120e5f5e4420f4282f65 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 11 Dec 2019 18:26:29 +0300 Subject: [PATCH 19/23] remove wrong migration --- .../news/migrations/0041_auto_20191211_1424.py | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 apps/news/migrations/0041_auto_20191211_1424.py diff --git a/apps/news/migrations/0041_auto_20191211_1424.py b/apps/news/migrations/0041_auto_20191211_1424.py deleted file mode 100644 index 87d3295d..00000000 --- a/apps/news/migrations/0041_auto_20191211_1424.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2.7 on 2019-12-11 14:24 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('gallery', '0006_merge_20191027_1758'), - ('news', '0040_remove_news_slug'), - ] - - operations = [ - migrations.AlterUniqueTogether( - name='newsgallery', - unique_together={('news', 'image'),}, - ), - ] From 60f6c8d811266d3153b0386ef368d214f5320e21 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 11 Dec 2019 19:15:19 +0300 Subject: [PATCH 20/23] fix migrations --- .../news/migrations/0041_auto_20191211_1528.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 apps/news/migrations/0041_auto_20191211_1528.py diff --git a/apps/news/migrations/0041_auto_20191211_1528.py b/apps/news/migrations/0041_auto_20191211_1528.py new file mode 100644 index 00000000..0093d406 --- /dev/null +++ b/apps/news/migrations/0041_auto_20191211_1528.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.7 on 2019-12-11 15:28 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('gallery', '0007_auto_20191211_1528'), + ('news', '0040_remove_news_slug'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='newsgallery', + unique_together={('news', 'image')}, + ), + ] From 9d2b7ff77cdfc5a972562370de2d97cee926fbd0 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Wed, 11 Dec 2019 19:18:04 +0300 Subject: [PATCH 21/23] add migration --- .../migrations/0007_auto_20191211_1528.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 apps/gallery/migrations/0007_auto_20191211_1528.py diff --git a/apps/gallery/migrations/0007_auto_20191211_1528.py b/apps/gallery/migrations/0007_auto_20191211_1528.py new file mode 100644 index 00000000..714ef664 --- /dev/null +++ b/apps/gallery/migrations/0007_auto_20191211_1528.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.7 on 2019-12-11 15:28 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('gallery', '0006_merge_20191027_1758'), + ] + + operations = [ + migrations.AlterModelOptions( + name='image', + options={'ordering': ['-modified'], 'verbose_name': 'Image', 'verbose_name_plural': 'Images'}, + ), + ] From 3e83bdff59f848b6df86ccd81a4d65e5d96c3362 Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 12 Dec 2019 15:34:36 +0300 Subject: [PATCH 22/23] pagination & ordering & country code --- apps/collection/views/back.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/collection/views/back.py b/apps/collection/views/back.py index a989ec56..ff924073 100644 --- a/apps/collection/views/back.py +++ b/apps/collection/views/back.py @@ -9,11 +9,17 @@ from utils.views import BindObjectMixin class CollectionViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): """ViewSet for Collection model.""" - pagination_class = None + # pagination_class = None permission_classes = (permissions.AllowAny,) - queryset = models.Collection.objects.all() serializer_class = serializers.CollectionBackOfficeSerializer + def get_queryset(self): + """Overridden method 'get_queryset'.""" + qs = models.Collection.objects.all().order_by('-created') + if self.request.country_code: + qs = qs.by_country_code(self.request.country_code) + return qs + class CollectionBackOfficeViewSet(mixins.CreateModelMixin, mixins.UpdateModelMixin, From b03cd4fe0951e6529ba2a6ed8eb089105fbc7b28 Mon Sep 17 00:00:00 2001 From: Dmitriy Kuzmenko Date: Thu, 12 Dec 2019 15:37:42 +0300 Subject: [PATCH 23/23] add transfer command, models and crud api. --- apps/main/management/commands/add_panels.py | 38 ++++++++++++++++ apps/main/migrations/0042_panel.py | 36 ++++++++++++++++ apps/main/models.py | 43 +++++++++++++++++++ apps/main/serializers.py | 28 +++++++++++- apps/main/urls/back.py | 6 ++- apps/main/views/back.py | 20 ++++++++- .../migrations/0021_auto_20191212_0926.py | 19 ++++++++ apps/transfer/models.py | 17 ++++++++ apps/utils/methods.py | 8 ++++ 9 files changed, 212 insertions(+), 3 deletions(-) create mode 100644 apps/main/management/commands/add_panels.py create mode 100644 apps/main/migrations/0042_panel.py create mode 100644 apps/product/migrations/0021_auto_20191212_0926.py diff --git a/apps/main/management/commands/add_panels.py b/apps/main/management/commands/add_panels.py new file mode 100644 index 00000000..85e50616 --- /dev/null +++ b/apps/main/management/commands/add_panels.py @@ -0,0 +1,38 @@ +from django.core.management.base import BaseCommand +from tqdm import tqdm + +from account.models import User +from main.models import Panel, SiteSettings +from transfer.models import Panels + + +class Command(BaseCommand): + help = '''Add panels from legacy DB.''' + + def handle(self, *args, **kwargs): + objects = [] + deleted = 0 + panels_list = Panels.objects.filter(name__isnull=False) + # remove existing panel + exist_panel = Panel.objects.filter(old_id__isnull=False) + if exist_panel.exists(): + deleted = exist_panel.count() + exist_panel.delete() + + for old_panel in tqdm(panels_list, desc='Add panels'): + site = SiteSettings.objects.filter(old_id=old_panel.site_id).first() + user = User.objects.filter(old_id=old_panel.site_id).first() + if site: + new_panel = Panel( + old_id=old_panel.id, + user=user, + site=site, + name=old_panel.name, + display=old_panel.display, + description=old_panel.description, + query=old_panel.query, + ) + objects.append(new_panel) + Panel.objects.bulk_create(objects) + self.stdout.write( + self.style.WARNING(f'Created {len(objects)}/Deleted {deleted} footer objects.')) diff --git a/apps/main/migrations/0042_panel.py b/apps/main/migrations/0042_panel.py new file mode 100644 index 00000000..3b2da2a0 --- /dev/null +++ b/apps/main/migrations/0042_panel.py @@ -0,0 +1,36 @@ +# Generated by Django 2.2.7 on 2019-12-12 12:00 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('main', '0041_auto_20191211_0631'), + ] + + operations = [ + migrations.CreateModel( + name='Panel', + 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')), + ('name', models.CharField(max_length=255, verbose_name='name')), + ('display', models.CharField(blank=True, choices=[('table', 'table'), ('table', 'mailing')], default=None, max_length=255, null=True, verbose_name='display')), + ('description', models.CharField(blank=True, default=None, max_length=255, null=True, verbose_name='description')), + ('query', models.TextField(blank=True, default=None, null=True, verbose_name='query')), + ('old_id', models.IntegerField(blank=True, default=None, null=True, verbose_name='old id')), + ('site', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='main.SiteSettings', verbose_name='site')), + ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='user')), + ], + options={ + 'verbose_name': 'panel', + 'verbose_name_plural': 'panels', + }, + ), + ] diff --git a/apps/main/models.py b/apps/main/models.py index ae4168ea..b39a6037 100644 --- a/apps/main/models.py +++ b/apps/main/models.py @@ -361,3 +361,46 @@ class Footer(ProjectBaseMixin): ) about_us = models.TextField(_('about_us')) copyright = models.TextField(_('copyright')) + + +class PanelQuerySet(models.QuerySet): + """Panels QuerySet.""" + + +class Panel(ProjectBaseMixin): + """Custom panel model with stored SQL query.""" + TABLE = 'table' + MAILING = 'table' + + DISPLAY_CHOICES = ( + (TABLE, _('table')), + (MAILING, _('mailing')) + ) + name = models.CharField(_('name'), max_length=255) + display = models.CharField( + _('display'), max_length=255, choices=DISPLAY_CHOICES, + blank=True, null=True, default=None + ) + description = models.CharField( + _('description'), max_length=255, blank=True, null=True, default=None) + query = models.TextField(_('query'), blank=True, null=True, default=None) + user = models.ForeignKey( + 'account.User', verbose_name=_('user'), null=True, + on_delete=models.SET_NULL) + site = models.ForeignKey( + 'main.SiteSettings', verbose_name=_('site'), null=True, + on_delete=models.SET_NULL) + old_id = models.IntegerField( + _('old id'), null=True, blank=True, default=None) + + objects = PanelQuerySet.as_manager() + + class Meta: + verbose_name = _('panel') + verbose_name_plural = _('panels') + + def __str__(self): + return self.name + + def execute_query(self): + pass diff --git a/apps/main/serializers.py b/apps/main/serializers.py index d586d71a..39c51845 100644 --- a/apps/main/serializers.py +++ b/apps/main/serializers.py @@ -5,6 +5,8 @@ from rest_framework import serializers from location.serializers import CountrySerializer from main import models from utils.serializers import ProjectModelSerializer, TranslatedField, RecursiveFieldSerializer +from account.serializers.back import BackUserSerializer +from account.models import User class FeatureSerializer(serializers.ModelSerializer): @@ -265,8 +267,32 @@ class PageTypeBaseSerializer(serializers.ModelSerializer): class ContentTypeBackSerializer(serializers.ModelSerializer): - """Serializer fro model ContentType.""" + """Serializer for model ContentType.""" class Meta: model = ContentType fields = '__all__' + + +class PanelSerializer(serializers.ModelSerializer): + """Serializer for Custom panel.""" + user_id = serializers.PrimaryKeyRelatedField( + queryset=User.objects.all(), + source='user', + write_only=True + ) + user = BackUserSerializer(read_only=True) + + class Meta: + model = models.Panel + fields = [ + 'id', + 'name', + 'display', + 'description', + 'query', + 'created', + 'modified', + 'user', + 'user_id' + ] diff --git a/apps/main/urls/back.py b/apps/main/urls/back.py index b4e196a3..26afd1a6 100644 --- a/apps/main/urls/back.py +++ b/apps/main/urls/back.py @@ -21,7 +21,11 @@ urlpatterns = [ path('footer/', views.FooterBackView.as_view(), name='footer-list-create'), path('footer//', views.FooterRUDBackView.as_view(), name='footer-rud'), path('page-types/', views.PageTypeListCreateView.as_view(), - name='page-types-list-create') + name='page-types-list-create'), + path('panels/', views.PanelsListCreateView.as_view(), name='panels'), + path('panels//', views.PanelsListCreateView.as_view(), name='panels-rud'), + # path('panels//execute/', views.PanelsView.as_view(), name='panels-execute') + ] diff --git a/apps/main/views/back.py b/apps/main/views/back.py index f6f987af..0a2b7377 100644 --- a/apps/main/views/back.py +++ b/apps/main/views/back.py @@ -4,7 +4,7 @@ from rest_framework import generics, permissions from main import serializers from main.filters import AwardFilter -from main.models import Award, Footer, PageType +from main.models import Award, Footer, PageType, Panel from main.views import SiteSettingsView, SiteListView @@ -89,3 +89,21 @@ class PageTypeListCreateView(generics.ListCreateAPIView): pagination_class = None serializer_class = serializers.PageTypeBaseSerializer queryset = PageType.objects.all() + + +class PanelsListCreateView(generics.ListCreateAPIView): + """Custom panels view.""" + permission_classes = ( + permissions.IsAdminUser, + ) + serializer_class = serializers.PanelSerializer + queryset = Panel.objects.all() + + +class PanelsRUDView(generics.RetrieveUpdateDestroyAPIView): + """Custom panels view.""" + permission_classes = ( + permissions.IsAdminUser, + ) + serializer_class = serializers.PanelSerializer + queryset = Panel.objects.all() \ No newline at end of file diff --git a/apps/product/migrations/0021_auto_20191212_0926.py b/apps/product/migrations/0021_auto_20191212_0926.py new file mode 100644 index 00000000..749ffa08 --- /dev/null +++ b/apps/product/migrations/0021_auto_20191212_0926.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.7 on 2019-12-12 09:26 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('product', '0020_merge_20191209_0911'), + ] + + operations = [ + migrations.AlterField( + model_name='product', + name='product_type', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='products', to='product.ProductType', verbose_name='Type'), + ), + ] diff --git a/apps/transfer/models.py b/apps/transfer/models.py index d8268d6d..7f640a5c 100644 --- a/apps/transfer/models.py +++ b/apps/transfer/models.py @@ -1222,3 +1222,20 @@ class Footers(MigrateMixin): class Meta: managed = False db_table = 'footers' + + +class Panels(MigrateMixin): + using = 'legacy' + + name = models.CharField(max_length=255, blank=True, null=True) + display = models.CharField(max_length=255, blank=True, null=True) + description = models.CharField(max_length=255, blank=True, null=True) + query = models.TextField(blank=True, null=True) + created_at = models.DateTimeField(blank=True, null=True) + updated_at = models.DateTimeField(blank=True, null=True) + account_id = models.IntegerField(blank=True, null=True) + site_id = models.IntegerField(blank=True, null=True) + + class Meta: + managed = False + db_table = 'panels' diff --git a/apps/utils/methods.py b/apps/utils/methods.py index dc027317..227bd1ee 100644 --- a/apps/utils/methods.py +++ b/apps/utils/methods.py @@ -3,6 +3,7 @@ import logging import random import re import string +from collections import namedtuple import requests from django.conf import settings @@ -124,3 +125,10 @@ def absolute_url_decorator(func): def get_point_from_coordinates(latitude: str, longitude: str): if latitude and longitude: return Point(x=longitude, y=latitude, srid=4326) + + +def namedtuplefetchall(cursor): + """Return all rows from a cursor as a namedtuple.""" + desc = cursor.description + nt_result = namedtuple('Result', [col[0] for col in desc]) + return [nt_result(*row) for row in cursor.fetchall()]