From f3a56051111a9ea28f790007f249d6367c56737b Mon Sep 17 00:00:00 2001 From: Kirill Date: Tue, 4 Feb 2020 15:05:59 +0300 Subject: [PATCH 1/5] filtration only in moderation --- apps/comment/views/back.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/comment/views/back.py b/apps/comment/views/back.py index 1f30e256..d9128e41 100644 --- a/apps/comment/views/back.py +++ b/apps/comment/views/back.py @@ -17,7 +17,7 @@ class CommentLstView(generics.ListCreateAPIView): "establishment": Establishment.__name__.lower() } - qs = models.Comment.objects.with_base_related().filter(status=models.Comment.WAITING) + qs = models.Comment.objects.with_base_related() if "object" in self.kwargs: qs = qs.by_object_id(self.kwargs["object"]) @@ -25,6 +25,8 @@ class CommentLstView(generics.ListCreateAPIView): if "type" in self.kwargs and self.kwargs["type"] in allowed: model = allowed[self.kwargs["type"]] qs = qs.by_content_type(self.kwargs["type"], model) + if self.kwargs["type"] == allowed["establishment"] and "object" not in self.kwargs: + qs = qs.filter(status=models.Comment.WAITING) return qs.order_by('-created') From b286bbb1fae4b047d1c7dbf046a24e788b78a7c7 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Tue, 4 Feb 2020 15:07:58 +0300 Subject: [PATCH 2/5] added migrations, refactored establishment serializers --- apps/authorization/views/common.py | 23 +++++++++++-- .../migrations/0009_auto_20200204_1205.py | 19 +++++++++++ .../migrations/0098_auto_20200204_1205.py | 24 ++++++++++++++ apps/establishment/models.py | 23 ++----------- apps/establishment/serializers/back.py | 33 +++++++++++++++++-- .../migrations/0026_auto_20200204_1205.py | 19 +++++++++++ apps/product/models.py | 8 +---- 7 files changed, 116 insertions(+), 33 deletions(-) create mode 100644 apps/comment/migrations/0009_auto_20200204_1205.py create mode 100644 apps/establishment/migrations/0098_auto_20200204_1205.py create mode 100644 apps/product/migrations/0026_auto_20200204_1205.py diff --git a/apps/authorization/views/common.py b/apps/authorization/views/common.py index f119b0fd..22668d26 100644 --- a/apps/authorization/views/common.py +++ b/apps/authorization/views/common.py @@ -182,7 +182,7 @@ class LoginByUsernameOrEmailView(JWTGenericViewMixin, generics.GenericAPIView): def post(self, request, *args, **kwargs): """ ## Login view. - POST-request data + ### POST-request data ``` { "username_or_email": , @@ -191,11 +191,12 @@ class LoginByUsernameOrEmailView(JWTGenericViewMixin, generics.GenericAPIView): "source": # 0 - Mobile, 1 - Web, 2 - All (by default used: 1) } ``` - ## Response + ### Response After a successful login, server side set up access_token and refresh token to cookies. In a payload of access token, the following information is being embed: see `User().get_user_info()`. + ### Description COOKIE Max-age are determined by `remember` flag: if `remember` is `True` then `Max-age` parameter taken from `settings.COOKIES_MAX_AGE` otherwise using session COOKIE Max-age. @@ -221,7 +222,23 @@ class LogoutView(JWTGenericViewMixin, generics.GenericAPIView): permission_classes = (IsAuthenticatedAndTokenIsValid, ) def post(self, request, *args, **kwargs): - """Override create method""" + """ + ## Logout view. + ### POST-request data + ``` + {} + ``` + ### Response + If user has *valid* `access_token` in COOKIES, then response return + blank response data with `HTTP_STATUS_CODE` *204*. + + ### Description + For complete logout, user must provide *valid* `access_token` + (`access_token` must be kept in `COOKIES`). + After successful request with valid access_token, token would be expired, + for reuse protection. + """ + # Get access token objs by JTI access_token = AccessToken(request.COOKIES.get('access_token')) access_token_obj = JWTAccessToken.objects.get(jti=access_token.payload.get('jti')) diff --git a/apps/comment/migrations/0009_auto_20200204_1205.py b/apps/comment/migrations/0009_auto_20200204_1205.py new file mode 100644 index 00000000..ca900d7d --- /dev/null +++ b/apps/comment/migrations/0009_auto_20200204_1205.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.7 on 2020-02-04 12:05 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('comment', '0008_comment_status'), + ] + + operations = [ + migrations.AlterField( + model_name='comment', + name='mark', + field=models.PositiveIntegerField(blank=True, default=None, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10)], verbose_name='Mark'), + ), + ] diff --git a/apps/establishment/migrations/0098_auto_20200204_1205.py b/apps/establishment/migrations/0098_auto_20200204_1205.py new file mode 100644 index 00000000..bccfa9e3 --- /dev/null +++ b/apps/establishment/migrations/0098_auto_20200204_1205.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2.7 on 2020-02-04 12:05 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('establishment', '0097_merge_20200204_1135'), + ] + + operations = [ + migrations.AlterField( + model_name='company', + name='establishment', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='companies', to='establishment.Establishment', verbose_name='establishment'), + ), + migrations.AlterField( + model_name='establishmentnote', + name='establishment', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notes', to='establishment.Establishment', verbose_name='establishment'), + ), + ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index bef4a3f2..eca705df 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -3,7 +3,6 @@ from datetime import datetime from functools import reduce from operator import or_ from typing import List -from slugify import slugify import elasticsearch_dsl from django.conf import settings @@ -668,24 +667,6 @@ class Establishment(GalleryMixin, ProjectBaseMixin, URLImageMixin, def __str__(self): return f'id:{self.id}-{self.name}' - def save(self, *args, **kwargs): - slugify_slug = slugify( - self.index_name, - word_boundary=True - ) - self.slug = slugify_slug - super(Establishment, self).save(*args, **kwargs) - - def delete(self, using=None, keep_parents=False): - """Overridden delete method""" - # TODO: If this does not contradict the plan, - # it is better to change it. - # Just add CASCADE to Company and Note in establishment fk field. - # Delete all related companies - self.companies.all().delete() - # Delete all related notes - self.notes.all().delete() - return super().delete(using, keep_parents) @property def visible_tags(self): @@ -953,7 +934,7 @@ class EstablishmentNote(ProjectBaseMixin): """Note model for Establishment entity.""" old_id = models.PositiveIntegerField(null=True, blank=True) text = models.TextField(verbose_name=_('text')) - establishment = models.ForeignKey(Establishment, on_delete=models.PROTECT, + establishment = models.ForeignKey(Establishment, on_delete=models.CASCADE, related_name='notes', verbose_name=_('establishment')) user = models.ForeignKey('account.User', on_delete=models.PROTECT, @@ -1563,7 +1544,7 @@ class CompanyQuerySet(models.QuerySet): class Company(ProjectBaseMixin): """Establishment company model.""" - establishment = models.ForeignKey(Establishment, on_delete=models.PROTECT, + establishment = models.ForeignKey(Establishment, on_delete=models.CASCADE, related_name='companies', verbose_name=_('establishment')) name = models.CharField(max_length=255, verbose_name=_('name')) diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index bca1c6ee..4175204f 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -3,7 +3,9 @@ from functools import lru_cache from django.db.models import F from django.shortcuts import get_object_or_404 from django.utils.translation import gettext_lazy as _ +from phonenumber_field.serializerfields import PhoneNumberField from rest_framework import serializers +from slugify import slugify from account.serializers.common import UserShortSerializer from collection.models import Guide @@ -15,6 +17,7 @@ from location.serializers import AddressDetailSerializer, TranslatedField from main.models import Currency from main.serializers import AwardSerializer from utils.decorators import with_base_attributes +from utils.methods import string_random from utils.serializers import ImageBaseSerializer, ProjectModelSerializer, TimeZoneChoiceField, \ PhoneMixinSerializer @@ -68,7 +71,7 @@ class EstablishmentListCreateSerializer(model_serializers.EstablishmentBaseSeria source='contact_phones', allow_null=True, allow_empty=True, - child=serializers.CharField(max_length=128), + child=PhoneNumberField(), required=False, write_only=True, ) @@ -126,6 +129,18 @@ class EstablishmentListCreateSerializer(model_serializers.EstablishmentBaseSeria if 'contact_emails' in validated_data: emails_list = validated_data.pop('contact_emails') + index_name = validated_data.get('index_name') + if 'slug' in validated_data and index_name: + slug = slugify( + index_name, + word_boundary=True + ) + while models.Establishment.objects.filter(slug=slug).exists(): + slug = slugify( + f'{index_name} {string_random()}', + word_boundary=True + ) + instance = super().create(validated_data) phones_handler(phones_list, instance) emails_handler(emails_list, instance) @@ -165,7 +180,15 @@ class EstablishmentRUDSerializer(model_serializers.EstablishmentBaseSerializer): subtypes = model_serializers.EstablishmentSubTypeBaseSerializer(source='establishment_subtypes', read_only=True, many=True) type = model_serializers.EstablishmentTypeBaseSerializer(source='establishment_type', read_only=True) - phones = ContactPhonesSerializer(read_only=True, many=True) + phones = serializers.ListField( + source='contact_phones', + allow_null=True, + allow_empty=True, + child=PhoneNumberField(), + required=False, + write_only=True, + ) + contact_phones = ContactPhonesSerializer(source='phones', read_only=True, many=True) class Meta(model_serializers.EstablishmentBaseSerializer.Meta): fields = [ @@ -176,6 +199,7 @@ class EstablishmentRUDSerializer(model_serializers.EstablishmentBaseSerializer): 'index_name', 'website', 'phones', + 'contact_phones', 'emails', 'price_level', 'toque_number', @@ -196,6 +220,11 @@ class EstablishmentRUDSerializer(model_serializers.EstablishmentBaseSerializer): 'status_display', ] + def to_representation(self, instance): + data = super(EstablishmentRUDSerializer, self).to_representation(instance) + data['phones'] = data.pop('contact_phones', None) + return data + def update(self, instance, validated_data): phones_list = [] if 'contact_phones' in validated_data: diff --git a/apps/product/migrations/0026_auto_20200204_1205.py b/apps/product/migrations/0026_auto_20200204_1205.py new file mode 100644 index 00000000..9d6a44ed --- /dev/null +++ b/apps/product/migrations/0026_auto_20200204_1205.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.7 on 2020-02-04 12:05 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('product', '0025_auto_20191227_1443'), + ] + + operations = [ + migrations.AlterField( + model_name='productnote', + name='product', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notes', to='product.Product', verbose_name='product'), + ), + ] diff --git a/apps/product/models.py b/apps/product/models.py index f7332f65..27e73947 100644 --- a/apps/product/models.py +++ b/apps/product/models.py @@ -344,12 +344,6 @@ class Product(GalleryMixin, TranslatedFieldsMixin, BaseAttributes, """Override str dunder method.""" return f'{self.name}' - def delete(self, using=None, keep_parents=False): - """Overridden delete method""" - # Delete all related notes - self.notes.all().delete() - return super().delete(using, keep_parents) - @property def product_type_translated_name(self): """Get translated name of product type.""" @@ -624,7 +618,7 @@ class ProductNote(ProjectBaseMixin): """Note model for Product entity.""" old_id = models.PositiveIntegerField(null=True, blank=True) text = models.TextField(verbose_name=_('text')) - product = models.ForeignKey(Product, on_delete=models.PROTECT, + product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='notes', verbose_name=_('product')) user = models.ForeignKey('account.User', on_delete=models.PROTECT, From 5aa74ff7c9aeea7a859ceb08d64269b8d10e4b16 Mon Sep 17 00:00:00 2001 From: Kuroshini Date: Tue, 4 Feb 2020 15:42:15 +0300 Subject: [PATCH 3/5] fix positions by establishment type/subtype --- apps/establishment/filters.py | 2 +- apps/establishment/models.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/establishment/filters.py b/apps/establishment/filters.py index f2754f39..e8d7d6d0 100644 --- a/apps/establishment/filters.py +++ b/apps/establishment/filters.py @@ -173,5 +173,5 @@ class PositionsByEstablishmentFilter(filters.FilterSet): def by_subtype(self, queryset, name, value): """filter by establishment subtype""" if value not in EMPTY_VALUES: - return queryset.by_establishment_subtype(value) + return queryset.by_establishment_subtypes(value.split('__')) return queryset diff --git a/apps/establishment/models.py b/apps/establishment/models.py index eca705df..519af701 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -970,10 +970,12 @@ class EstablishmentGallery(IntermediateGalleryModelMixin): class PositionQuerySet(models.QuerySet): def by_establishment_type(self, value: str): - return self.filter(establishment_type__index_name=value) + return self.filter(Q(establishment_type__index_name=value) | + Q(establishment_type__isnull=True, establishment_subtype__isnull=True)) - def by_establishment_subtype(self, value: str): - return self.filter(establishment_subtype__index_name=value) + def by_establishment_subtypes(self, value: List[str]): + return self.filter(Q(establishment_subtype__index_name__in=value) | + Q(establishment_type__isnull=True, establishment_subtype__isnull=True)) class Position(BaseAttributes, TranslatedFieldsMixin): From 6030b438a0270f37658693c36c9cb6ff29109bc7 Mon Sep 17 00:00:00 2001 From: Kirill Date: Tue, 4 Feb 2020 15:50:57 +0300 Subject: [PATCH 4/5] filtration only in moderation v2 --- apps/comment/views/back.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/comment/views/back.py b/apps/comment/views/back.py index d9128e41..bcc889be 100644 --- a/apps/comment/views/back.py +++ b/apps/comment/views/back.py @@ -19,14 +19,15 @@ class CommentLstView(generics.ListCreateAPIView): qs = models.Comment.objects.with_base_related() + if "object" not in self.kwargs and "type" not in self.kwargs: + qs = qs.filter(status=models.Comment.WAITING) + if "object" in self.kwargs: qs = qs.by_object_id(self.kwargs["object"]) if "type" in self.kwargs and self.kwargs["type"] in allowed: model = allowed[self.kwargs["type"]] qs = qs.by_content_type(self.kwargs["type"], model) - if self.kwargs["type"] == allowed["establishment"] and "object" not in self.kwargs: - qs = qs.filter(status=models.Comment.WAITING) return qs.order_by('-created') From a23c50a1303eabe1fd0ea09c2b0c1791d805ffc2 Mon Sep 17 00:00:00 2001 From: "a.gorbunov" Date: Tue, 4 Feb 2020 12:53:08 +0000 Subject: [PATCH 5/5] available_for_events not required --- apps/establishment/serializers/back.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index 4175204f..1448fafe 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -426,7 +426,8 @@ class EmployeeBackSerializers(PhoneMixinSerializer, serializers.ModelSerializer) 'photo_id', ] extra_kwargs = { - 'phone': {'write_only': True} + 'phone': {'write_only': True}, + 'available_for_events': {'required': False} }