From 15b8241213cef273b6e438128cf274203f8f446a Mon Sep 17 00:00:00 2001 From: Anatoly Date: Mon, 3 Feb 2020 13:10:02 +0300 Subject: [PATCH] refactored serializers that retrieving phone numbers --- .gitignore | 3 +- apps/account/models.py | 4 +- apps/account/serializers/back.py | 4 +- apps/account/serializers/common.py | 10 ++-- apps/establishment/admin.py | 10 ++-- apps/establishment/models.py | 65 ++++++++++++++++++------ apps/establishment/serializers/back.py | 45 +++++++++++----- apps/establishment/serializers/common.py | 12 +---- apps/utils/models.py | 15 ++++++ apps/utils/serializers.py | 6 +++ project/settings/local.py | 10 ++-- 11 files changed, 127 insertions(+), 57 deletions(-) diff --git a/.gitignore b/.gitignore index ce1cc82c..2dabe48f 100644 --- a/.gitignore +++ b/.gitignore @@ -26,7 +26,8 @@ celerybeat-schedule local_files celerybeat.pid /gm_viktor.dump +/gm_victor.dump /docker-compose.dump.yml /gm_production_20191029.sql /apps/transfer/log -/gm_production_*.sql +/gm_production_*.sql \ No newline at end of file diff --git a/apps/account/models.py b/apps/account/models.py index 4f03c4d9..8f35541c 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -22,7 +22,7 @@ from establishment.models import Establishment, EstablishmentSubType from location.models import Country from main.models import SiteSettings from utils.models import GMTokenGenerator -from utils.models import ImageMixin, ProjectBaseMixin, PlatformMixin +from utils.models import ImageMixin, ProjectBaseMixin, PlatformMixin, PhoneModelMixin from utils.tokens import GMRefreshToken @@ -207,7 +207,7 @@ class UserQuerySet(models.QuerySet): return self.filter(userrole__role__country__code=country_code).distinct() -class User(AbstractUser): +class User(PhoneModelMixin, AbstractUser): """Base user model.""" username = models.CharField( _('username'), diff --git a/apps/account/serializers/back.py b/apps/account/serializers/back.py index f5dcb061..a84359fc 100644 --- a/apps/account/serializers/back.py +++ b/apps/account/serializers/back.py @@ -45,7 +45,9 @@ class BackUserSerializer(UserSerializer): 'roles', 'subscriptions', 'phone', - 'phone_as_international', + 'country_calling_code', + 'national_calling_number', + ) extra_kwargs = { 'password': {'write_only': True}, diff --git a/apps/account/serializers/common.py b/apps/account/serializers/common.py index 4e34ecc8..b2eed176 100644 --- a/apps/account/serializers/common.py +++ b/apps/account/serializers/common.py @@ -3,7 +3,6 @@ from django.conf import settings from django.contrib.auth import password_validation as password_validators from django.utils.translation import gettext_lazy as _ from fcm_django.models import FCMDevice -from phonenumber_field.serializerfields import PhoneNumberField from rest_framework import exceptions from rest_framework import serializers from rest_framework import validators as rest_validators @@ -14,6 +13,7 @@ from notification.models import Subscribe, Subscriber from utils import exceptions as utils_exceptions from utils import methods as utils_methods from utils.methods import generate_string_code +from utils.serializers import PhoneMixinSerializer def subscriptions_handler(subscriptions_list, user): @@ -60,7 +60,7 @@ class RoleBaseSerializer(serializers.ModelSerializer): ] -class UserSerializer(serializers.ModelSerializer): +class UserSerializer(PhoneMixinSerializer, serializers.ModelSerializer): """User serializer.""" # RESPONSE fullname = serializers.CharField(source='get_full_name', read_only=True) @@ -81,9 +81,6 @@ class UserSerializer(serializers.ModelSerializer): required=False, help_text='list of subscription_types id', ) - phone_as_international = PhoneNumberField(source='phone.as_international', - allow_null=True, - read_only=True) is_superuser = serializers.BooleanField(read_only=True) class Meta: @@ -102,7 +99,8 @@ class UserSerializer(serializers.ModelSerializer): 'roles', 'subscriptions', 'phone', - 'phone_as_international', + 'country_calling_code', + 'national_calling_number', 'is_superuser', ] extra_kwargs = { diff --git a/apps/establishment/admin.py b/apps/establishment/admin.py index 5792cf94..f83fcb64 100644 --- a/apps/establishment/admin.py +++ b/apps/establishment/admin.py @@ -4,11 +4,11 @@ from django.contrib.contenttypes.admin import GenericTabularInline from django.utils.translation import gettext_lazy as _ from comment.models import Comment -from utils.admin import BaseModelAdminMixin from establishment import models from main.models import Award from product.models import Product, PurchasedProduct from review import models as review_models +from utils.admin import BaseModelAdminMixin @admin.register(models.EstablishmentType) @@ -87,11 +87,11 @@ class EstablishmentAdmin(BaseModelAdminMixin, admin.ModelAdmin): search_fields = ['id', 'name', 'index_name', 'slug'] list_filter = ['public_mark', 'toque_number'] inlines = [CompanyInline, EstablishmentNote, GalleryImageInline, - PurchasedProductInline, ] + PurchasedProductInline, ContactPhoneInline, ] # inlines = [ # AwardInline, ContactPhoneInline, ContactEmailInline, # ReviewInline, CommentInline, ProductInline] - raw_id_fields = ('address', 'collections', 'tags', 'schedule') + raw_id_fields = ('address', 'collections', 'tags', 'schedule', ) @admin.register(models.Position) @@ -142,3 +142,7 @@ class CompanyAdmin(BaseModelAdminMixin, admin.ModelAdmin): """Admin conf for Company model.""" raw_id_fields = ['establishment', 'address', ] + +@admin.register(models.Employee) +class EmployeeAdmin(BaseModelAdminMixin, admin.ModelAdmin): + raw_id_fields = ['establishments', 'tags', 'user', 'photo', ] diff --git a/apps/establishment/models.py b/apps/establishment/models.py index 1d8cee7b..fc734e8c 100644 --- a/apps/establishment/models.py +++ b/apps/establishment/models.py @@ -14,13 +14,14 @@ from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.indexes import GinIndex from django.contrib.postgres.search import TrigramSimilarity from django.core.exceptions import ValidationError -from django.core.validators import FileExtensionValidator, MaxValueValidator, MinValueValidator +from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.db.models import Case, ExpressionWrapper, F, Prefetch, Q, Subquery, When from django.shortcuts import get_object_or_404 from django.utils import timezone from django.utils.translation import gettext_lazy as _ from phonenumber_field.modelfields import PhoneNumberField +from phonenumber_field.phonenumber import PhoneNumber from timezone_field import TimeZoneField from location.models import Address @@ -30,8 +31,9 @@ from tag.models import Tag from timetable.models import Timetable from utils.methods import transform_into_readable_str from utils.models import ( - BaseAttributes, FavoritesMixin, FileMixin, GalleryMixin, HasTagsMixin, IntermediateGalleryModelMixin, - ProjectBaseMixin, TJSONField, TranslatedFieldsMixin, TypeDefaultImageMixin, URLImageMixin, default_menu_bool_array + BaseAttributes, FavoritesMixin, FileMixin, GalleryMixin, HasTagsMixin, + IntermediateGalleryModelMixin, ProjectBaseMixin, TJSONField, TranslatedFieldsMixin, + TypeDefaultImageMixin, URLImageMixin, default_menu_bool_array, PhoneModelMixin, ) @@ -1124,7 +1126,7 @@ class EmployeeQuerySet(models.QuerySet): ) -class Employee(BaseAttributes): +class Employee(PhoneModelMixin, BaseAttributes): """Employee model.""" user = models.OneToOneField('account.User', on_delete=models.PROTECT, @@ -1210,7 +1212,7 @@ class EstablishmentScheduleQuerySet(models.QuerySet): """QuerySet for model EstablishmentSchedule""" -class ContactPhone(models.Model): +class ContactPhone(PhoneModelMixin, models.Model): """Contact phone model.""" establishment = models.ForeignKey( Establishment, related_name='phones', on_delete=models.CASCADE) @@ -1223,16 +1225,6 @@ class ContactPhone(models.Model): def __str__(self): return f'{self.phone.as_e164}' - @property - def country_calling_code(self): - """Return phone code from PhonеNumberField.""" - return f'+{self.phone.country_code}' if self.phone and hasattr(self, 'phone') else None - - @property - def national_calling_number(self): - """Return phone national number from from PhonеNumberField.""" - return self.phone.national_number if self.phone and hasattr(self, 'phone') else None - class ContactEmail(models.Model): """Contact email model.""" @@ -1571,3 +1563,46 @@ class Company(ProjectBaseMixin): """Meta class.""" verbose_name = _('company') verbose_name_plural = _('companies') + + def _phonenumber_constructor(self, field_name: str) -> list: + array = [] + if field_name in [field.name for field in self._meta.fields]: + values = getattr(self, field_name, None) + if values: + for number in values: + try: + obj = PhoneNumber.from_string( + phone_number=number, + region=None if number.startswith('+') + else settings.PHONENUMBER_DEFAULT_REGION) + except Exception as e: + pass + else: + array.append(obj) if obj.is_valid() else None + return array + + def phones_(self) -> list: + """Return PhoneNumber objects.""" + return self._phonenumber_constructor('phones') + + def faxes_(self) -> list: + """Return PhoneNumber objects.""" + return self._phonenumber_constructor('faxes') + + def _array_constructor(self, field_name: str) -> list: + array = [] + for obj in self._phonenumber_constructor(field_name): + if hasattr(obj, 'country_code') and hasattr(obj, 'national_number'): + array.append({ + 'country_calling_code': f'+{obj.country_code}', + 'national_calling_number': str(obj.national_number), + }) + return array + + @property + def phones_array(self) -> list: + return self._array_constructor('phones') + + @property + def faxes_array(self) -> list: + return self._array_constructor('faxes') diff --git a/apps/establishment/serializers/back.py b/apps/establishment/serializers/back.py index 969e1b1d..6b8bdd7b 100644 --- a/apps/establishment/serializers/back.py +++ b/apps/establishment/serializers/back.py @@ -14,7 +14,8 @@ 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.serializers import ImageBaseSerializer, ProjectModelSerializer, TimeZoneChoiceField +from utils.serializers import ImageBaseSerializer, ProjectModelSerializer, TimeZoneChoiceField, \ + PhoneMixinSerializer def phones_handler(phones_list, establishment): @@ -136,6 +137,17 @@ class EstablishmentPositionListSerializer(model_serializers.EstablishmentBaseSer ] +class EstablishmentContactPhoneSerializer(PhoneMixinSerializer, serializers.ModelSerializer): + """Establishment contact phone serializer.""" + class Meta: + """Meta class.""" + model = models.ContactPhone + fields = [ + 'country_calling_code', + 'national_calling_number', + ] + + class EstablishmentRUDSerializer(model_serializers.EstablishmentBaseSerializer): """Establishment create serializer""" @@ -156,16 +168,9 @@ 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 = serializers.ListField( - source='contact_phones', - allow_null=True, - allow_empty=True, - child=serializers.CharField(max_length=128), - required=False, - ) + phones = EstablishmentContactPhoneSerializer(read_only=True, many=True) - class Meta: - model = models.Establishment + class Meta(model_serializers.EstablishmentBaseSerializer.Meta): fields = [ 'id', 'slug', @@ -248,10 +253,8 @@ class PlatesSerializers(model_serializers.PlateSerializer): ] -class ContactPhoneBackSerializers(model_serializers.PlateSerializer): +class ContactPhoneBackSerializers(PhoneMixinSerializer, serializers.ModelSerializer): """ContactPhone serializers.""" - country_calling_code = serializers.CharField(read_only=True, allow_null=True) - national_calling_number = serializers.CharField(read_only=True, allow_null=True) class Meta: model = models.ContactPhone @@ -262,6 +265,9 @@ class ContactPhoneBackSerializers(model_serializers.PlateSerializer): 'country_calling_code', 'national_calling_number', ] + extra_kwarg = { + 'phone': {'write_only': True} + } class ContactEmailBackSerializers(model_serializers.PlateSerializer): @@ -293,7 +299,7 @@ class PositionBackSerializer(serializers.ModelSerializer): # TODO: test decorator @with_base_attributes -class EmployeeBackSerializers(serializers.ModelSerializer): +class EmployeeBackSerializers(PhoneMixinSerializer, serializers.ModelSerializer): """Employee serializers.""" public_mark = serializers.SerializerMethodField() positions = serializers.SerializerMethodField() @@ -385,11 +391,16 @@ class EmployeeBackSerializers(serializers.ModelSerializer): 'birth_date', 'email', 'phone', + 'country_calling_code', + 'national_calling_number', 'toque_number', 'available_for_events', 'photo', 'photo_id', ] + extra_kwargs = { + 'phone': {'write_only': True} + } class EstablishmentEmployeeBackSerializer(serializers.ModelSerializer): @@ -411,6 +422,7 @@ class EstablishmentEmployeeBackSerializer(serializers.ModelSerializer): class EstEmployeeBackSerializer(EmployeeBackSerializers): + @property def request_kwargs(self): """Get url kwargs from request.""" @@ -451,10 +463,15 @@ class EstEmployeeBackSerializer(EmployeeBackSerializers): 'birth_date', 'email', 'phone', + 'country_calling_code', + 'national_calling_number', 'toque_number', 'available_for_events', 'photo', ] + extra_kwargs = { + 'phone': {'write_only': True} + } class EstablishmentBackOfficeGallerySerializer(serializers.ModelSerializer): diff --git a/apps/establishment/serializers/common.py b/apps/establishment/serializers/common.py index 9f557a28..6f71fb6c 100644 --- a/apps/establishment/serializers/common.py +++ b/apps/establishment/serializers/common.py @@ -633,8 +633,8 @@ class EstablishmentCarouselCreateSerializer(CarouselCreateSerializer): class CompanyBaseSerializer(serializers.ModelSerializer): """Company base serializer""" - phone_list = serializers.SerializerMethodField(source='phones', read_only=True) - fax_list = serializers.SerializerMethodField(source='faxes', read_only=True) + phone_list = serializers.ListField(source='phones_array', read_only=True) + fax_list = serializers.ListField(source='faxes_array', read_only=True) address_detail = AddressDetailSerializer(source='address', read_only=True) class Meta: @@ -662,14 +662,6 @@ class CompanyBaseSerializer(serializers.ModelSerializer): 'address': {'write_only': True} } - def get_phone_list(self, instance): - """Return list of phone numbers.""" - return instance.phones - - def get_fax_list(self, instance): - """Return list of fax numbers.""" - return instance.faxes - def validate(self, attrs): """Overridden validate method""" phones = [str_to_phonenumber(phone).as_national for phone in attrs.get('phones')] diff --git a/apps/utils/models.py b/apps/utils/models.py index a3892554..7721138c 100644 --- a/apps/utils/models.py +++ b/apps/utils/models.py @@ -511,3 +511,18 @@ class TypeDefaultImageMixin: def default_menu_bool_array(): return [False] * 7 + + +class PhoneModelMixin: + """Mixin for PhoneNumberField.""" + @property + def country_calling_code(self): + """Return phone code from PhonеNumberField.""" + if hasattr(self, 'phone') and self.phone: + return f'+{self.phone.country_code}' + + @property + def national_calling_number(self): + """Return phone national number from from PhonеNumberField.""" + if hasattr(self, 'phone') and (self.phone and hasattr(self.phone, 'national_number')): + return self.phone.national_number diff --git a/apps/utils/serializers.py b/apps/utils/serializers.py index 503c970b..2922d4cf 100644 --- a/apps/utils/serializers.py +++ b/apps/utils/serializers.py @@ -137,3 +137,9 @@ class ImageBaseSerializer(serializers.Serializer): original_url = serializers.URLField() orientation_display = serializers.CharField() auto_crop_images = serializers.DictField(allow_null=True) + + +class PhoneMixinSerializer(serializers.Serializer): + """Phone mixin serializer.""" + country_calling_code = serializers.CharField(read_only=True, allow_null=True) + national_calling_number = serializers.CharField(read_only=True, allow_null=True) diff --git a/project/settings/local.py b/project/settings/local.py index 2c822c53..7278bff4 100644 --- a/project/settings/local.py +++ b/project/settings/local.py @@ -86,11 +86,11 @@ LOGGING = { 'py.warnings': { 'handlers': ['console'], }, - 'django.db.backends': { - 'handlers': ['console', ], - 'level': 'DEBUG', - 'propagate': False, - }, + # 'django.db.backends': { + # 'handlers': ['console', ], + # 'level': 'DEBUG', + # 'propagate': False, + # }, } }