From de78ee5d6e096a19b6652e5d3686d9102f1a4ded Mon Sep 17 00:00:00 2001 From: Anatoly Date: Tue, 21 Jan 2020 17:36:31 +0300 Subject: [PATCH] added field phone to User model, change establishment transfer, added filters (full-text search, role_country_code, country_code), change account serializers --- apps/account/admin.py | 4 +-- apps/account/filters.py | 30 ++++++++++++++++++++-- apps/account/migrations/0033_user_phone.py | 19 ++++++++++++++ apps/account/models.py | 22 ++++++++++++++++ apps/account/serializers/back.py | 25 ++++++++---------- apps/account/serializers/common.py | 11 +++++++- apps/account/views/back.py | 1 + apps/gallery/models.py | 3 --- apps/transfer/serializers/establishment.py | 10 ++++++++ project/settings/base.py | 2 +- 10 files changed, 103 insertions(+), 24 deletions(-) create mode 100644 apps/account/migrations/0033_user_phone.py diff --git a/apps/account/admin.py b/apps/account/admin.py index b3302eae..3661b3e5 100644 --- a/apps/account/admin.py +++ b/apps/account/admin.py @@ -25,13 +25,13 @@ class UserAdmin(BaseUserAdmin): 'is_staff', 'is_superuser', 'email_confirmed') list_filter = ('is_active', 'is_staff', 'is_superuser', 'email_confirmed', 'groups',) - search_fields = ('email', 'first_name', 'last_name') + search_fields = ('email', 'first_name', 'last_name', 'phone',) readonly_fields = ('last_login', 'date_joined', 'image_preview', 'cropped_image_preview', 'last_ip', 'last_country') fieldsets = ( (None, {'fields': ('email', 'password',)}), (_('Personal info'), { 'fields': ('username', 'first_name', 'last_name', - 'image_url', 'image_preview', + 'image_url', 'image_preview', 'phone', 'cropped_image_url', 'cropped_image_preview',)}), (_('Subscription'), { 'fields': ( diff --git a/apps/account/filters.py b/apps/account/filters.py index 996c09b8..322b8b35 100644 --- a/apps/account/filters.py +++ b/apps/account/filters.py @@ -8,8 +8,10 @@ from account import models class AccountBackOfficeFilter(filters.FilterSet): """Account filter set.""" + search = filters.CharFilter(method='search_text') + role_country_code = filters.CharFilter(method='by_role_country_code') role = filters.MultipleChoiceFilter(choices=models.Role.ROLE_CHOICES, - method='filter_by_roles') + method='by_roles') class Meta: """Meta class.""" @@ -21,9 +23,33 @@ class AccountBackOfficeFilter(filters.FilterSet): 'is_staff', 'is_active', 'is_superuser', + 'search', + 'role_country_code', ) - def filter_by_roles(self, queryset, name, value): + def by_roles(self, queryset, name, value): if value not in EMPTY_VALUES: return queryset.by_roles(value) return queryset + + def search_text(self, queryset, name, value): + if value not in EMPTY_VALUES: + return queryset.search_text(value) + return queryset + + def by_role_country_code(self, queryset, name, value): + if value not in EMPTY_VALUES: + return queryset.by_role_country_code(value) + return queryset + + +class RoleListFilter(filters.FilterSet): + """Role filter set.""" + country_code = filters.CharFilter(field_name='country__code') + + class Meta: + """Meta class.""" + model = models.Role + fields = [ + 'country_code', + ] diff --git a/apps/account/migrations/0033_user_phone.py b/apps/account/migrations/0033_user_phone.py new file mode 100644 index 00000000..f024f116 --- /dev/null +++ b/apps/account/migrations/0033_user_phone.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.7 on 2020-01-20 09:39 + +from django.db import migrations +import phonenumber_field.modelfields + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0032_auto_20200114_1311'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='phone', + field=phonenumber_field.modelfields.PhoneNumberField(blank=True, default=None, max_length=128, null=True, verbose_name='Phone'), + ), + ] diff --git a/apps/account/models.py b/apps/account/models.py index e39e77a4..373624e5 100644 --- a/apps/account/models.py +++ b/apps/account/models.py @@ -20,6 +20,8 @@ from main.models import SiteSettings from utils.models import GMTokenGenerator from utils.models import ImageMixin, ProjectBaseMixin, PlatformMixin from utils.tokens import GMRefreshToken +from django.contrib.postgres.search import SearchVector +from phonenumber_field.modelfields import PhoneNumberField class RoleQuerySet(models.QuerySet): @@ -142,6 +144,24 @@ class UserQuerySet(models.QuerySet): role = Role.objects.filter(role=Role.ESTABLISHMENT_MANAGER).first() return self.by_role(role).filter(userrole__establishment=establishment) + def annotate_search_text(self): + """Full-text search""" + return self.annotate(search_text=SearchVector( + 'username', + 'first_name', + 'last_name', + 'email', + 'phone', + )) + + def search_text(self, value: str): + """Filter by annotated search vector.""" + return self.annotate_search_text().filter(search_text=value) + + def by_role_country_code(self, country_code: str): + """Filter by role country code.""" + return self.filter(userrole__role__country__code=country_code).distinct() + class User(AbstractUser): """Base user model.""" @@ -178,6 +198,8 @@ class User(AbstractUser): default=None, on_delete=models.SET_NULL, ) + phone = PhoneNumberField(blank=True, null=True, default=None, + verbose_name=_('Phone')) EMAIL_FIELD = 'email' USERNAME_FIELD = 'email' diff --git a/apps/account/serializers/back.py b/apps/account/serializers/back.py index 05f4ece1..b38b9042 100644 --- a/apps/account/serializers/back.py +++ b/apps/account/serializers/back.py @@ -2,8 +2,7 @@ from rest_framework import serializers from account import models -from account.models import User -from account.serializers import RoleBaseSerializer, subscriptions_handler +from account.serializers import RoleBaseSerializer, UserSerializer, subscriptions_handler from main.models import SiteSettings @@ -17,20 +16,12 @@ class _SiteSettingsSerializer(serializers.ModelSerializer): ) -class BackUserSerializer(serializers.ModelSerializer): +class BackUserSerializer(UserSerializer): + last_country = _SiteSettingsSerializer(read_only=True) roles = RoleBaseSerializer(many=True, read_only=True) - subscriptions = serializers.ListField( - source='subscription_types', - allow_null=True, - allow_empty=True, - child=serializers.IntegerField(min_value=1), - required=False, - help_text='list of subscription_types id', - ) - class Meta: - model = User + class Meta(UserSerializer.Meta): fields = ( 'id', 'last_login', @@ -54,9 +45,12 @@ class BackUserSerializer(serializers.ModelSerializer): 'last_country', 'roles', 'subscriptions', + 'phone', + 'phone_as_international', ) extra_kwargs = { 'password': {'write_only': True}, + 'phone': {'write_only': True}, } read_only_fields = ( 'old_password', @@ -82,8 +76,7 @@ class BackUserSerializer(serializers.ModelSerializer): class BackDetailUserSerializer(BackUserSerializer): - class Meta: - model = User + class Meta(BackUserSerializer.Meta): fields = ( 'id', 'last_country', @@ -109,6 +102,8 @@ class BackDetailUserSerializer(BackUserSerializer): 'groups', 'user_permissions', 'subscriptions', + 'phone', + 'phone_as_international', ) read_only_fields = ( 'old_password', diff --git a/apps/account/serializers/common.py b/apps/account/serializers/common.py index 02d75950..1673bef9 100644 --- a/apps/account/serializers/common.py +++ b/apps/account/serializers/common.py @@ -13,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 phonenumber_field.serializerfields import PhoneNumberField def subscriptions_handler(subscriptions_list, user): @@ -43,6 +44,7 @@ class RoleBaseSerializer(serializers.ModelSerializer): """Serializer for model Role.""" role_display = serializers.CharField(source='get_role_display', read_only=True) navigation_bar_permission = NavigationBarPermissionBaseSerializer(read_only=True) + country_code = serializers.CharField(source='country.code', read_only=True, allow_null=True) class Meta: """Meta class.""" @@ -51,6 +53,7 @@ class RoleBaseSerializer(serializers.ModelSerializer): 'id', 'role_display', 'navigation_bar_permission', + 'country_code', ] @@ -75,6 +78,9 @@ 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) class Meta: model = models.User @@ -90,6 +96,8 @@ class UserSerializer(serializers.ModelSerializer): 'newsletter', 'roles', 'subscriptions', + 'phone', + 'phone_as_international', ] extra_kwargs = { 'first_name': {'required': False, 'write_only': True, }, @@ -98,6 +106,7 @@ class UserSerializer(serializers.ModelSerializer): 'image_url': {'required': False, }, 'cropped_image_url': {'required': False, }, 'newsletter': {'required': False, }, + 'phone': {'required': False, 'write_only': True, } } def create(self, validated_data): @@ -113,7 +122,7 @@ class UserSerializer(serializers.ModelSerializer): def validate_email(self, value): """Validate email value""" - if value == self.instance.email: + if hasattr(self.instance, 'email') and self.instance.email and value == self.instance.email: raise serializers.ValidationError(detail='Equal email address.') return value diff --git a/apps/account/views/back.py b/apps/account/views/back.py index 1a3f5ccd..a8b5a1f7 100644 --- a/apps/account/views/back.py +++ b/apps/account/views/back.py @@ -15,6 +15,7 @@ from account.serializers.common import RoleBaseSerializer class RoleListView(generics.ListCreateAPIView): serializer_class = RoleBaseSerializer queryset = models.Role.objects.all() + filter_class = filters.RoleListFilter class RoleTabRetrieveView(generics.GenericAPIView): diff --git a/apps/gallery/models.py b/apps/gallery/models.py index 2d2362ba..ebca56ce 100644 --- a/apps/gallery/models.py +++ b/apps/gallery/models.py @@ -54,6 +54,3 @@ class Image(ProjectBaseMixin, SORLImageMixin, PlatformMixin): if completely: # Delete an instance of image super().delete() - - - diff --git a/apps/transfer/serializers/establishment.py b/apps/transfer/serializers/establishment.py index 1db16711..638ba4ff 100644 --- a/apps/transfer/serializers/establishment.py +++ b/apps/transfer/serializers/establishment.py @@ -12,6 +12,7 @@ from timetable.models import Timetable from utils.legacy_parser import parse_legacy_schedule_content from utils.serializers import TimeZoneChoiceField from utils.slug_generator import generate_unique_slug +from phonenumber_field.phonenumber import PhoneNumber class EstablishmentSerializer(serializers.ModelSerializer): @@ -58,6 +59,7 @@ class EstablishmentSerializer(serializers.ModelSerializer): def validate(self, data): old_type = data.pop('type', None) + phone = data.pop('phone', None) data.update({ 'slug': generate_unique_slug(Establishment, data['slug'] if data['slug'] else data['name']), @@ -65,6 +67,7 @@ class EstablishmentSerializer(serializers.ModelSerializer): 'establishment_type_id': self.get_type(old_type), 'is_publish': data.get('state') == 'published', 'subtype': self.get_subtype(old_type), + 'phone': self.get_phone(phone), }) data.pop('location') data.pop('state') @@ -179,6 +182,13 @@ class EstablishmentSerializer(serializers.ModelSerializer): establishment_type_id=establishment_type_id) return subtype + @staticmethod + def get_phone(phone: str): + phone_obj = PhoneNumber.from_string(phone_number=phone, region='FR') + if phone_obj.is_valid(): + fmt = PhoneNumber.format_map[getattr(settings, 'PHONENUMBER_DB_FORMAT', 'INTERNATIONAL')] + return phone_obj.format_as(fmt) + class EstablishmentNoteSerializer(serializers.ModelSerializer): diff --git a/project/settings/base.py b/project/settings/base.py index 755aa830..2b716225 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -517,7 +517,7 @@ STATICFILES_DIRS = ( # MEDIA MEDIA_LOCATION = 'media' -PHONENUMBER_DB_FORMAT = 'NATIONAL' +PHONENUMBER_DB_FORMAT = 'INTERNATIONAL' PHONENUMBER_DEFAULT_REGION = "FR" FALLBACK_LOCALE = 'en-GB'