From b286bbb1fae4b047d1c7dbf046a24e788b78a7c7 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Tue, 4 Feb 2020 15:07:58 +0300 Subject: [PATCH] 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,