added field phone to User model, change establishment transfer, added filters (full-text search, role_country_code, country_code), change account serializers

This commit is contained in:
Anatoly 2020-01-21 17:36:31 +03:00
parent 6d627ac240
commit de78ee5d6e
10 changed files with 103 additions and 24 deletions

View File

@ -25,13 +25,13 @@ class UserAdmin(BaseUserAdmin):
'is_staff', 'is_superuser', 'email_confirmed') 'is_staff', 'is_superuser', 'email_confirmed')
list_filter = ('is_active', 'is_staff', 'is_superuser', 'email_confirmed', list_filter = ('is_active', 'is_staff', 'is_superuser', 'email_confirmed',
'groups',) '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') readonly_fields = ('last_login', 'date_joined', 'image_preview', 'cropped_image_preview', 'last_ip', 'last_country')
fieldsets = ( fieldsets = (
(None, {'fields': ('email', 'password',)}), (None, {'fields': ('email', 'password',)}),
(_('Personal info'), { (_('Personal info'), {
'fields': ('username', 'first_name', 'last_name', 'fields': ('username', 'first_name', 'last_name',
'image_url', 'image_preview', 'image_url', 'image_preview', 'phone',
'cropped_image_url', 'cropped_image_preview',)}), 'cropped_image_url', 'cropped_image_preview',)}),
(_('Subscription'), { (_('Subscription'), {
'fields': ( 'fields': (

View File

@ -8,8 +8,10 @@ from account import models
class AccountBackOfficeFilter(filters.FilterSet): class AccountBackOfficeFilter(filters.FilterSet):
"""Account filter set.""" """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, role = filters.MultipleChoiceFilter(choices=models.Role.ROLE_CHOICES,
method='filter_by_roles') method='by_roles')
class Meta: class Meta:
"""Meta class.""" """Meta class."""
@ -21,9 +23,33 @@ class AccountBackOfficeFilter(filters.FilterSet):
'is_staff', 'is_staff',
'is_active', 'is_active',
'is_superuser', '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: if value not in EMPTY_VALUES:
return queryset.by_roles(value) return queryset.by_roles(value)
return queryset 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',
]

View File

@ -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'),
),
]

View File

@ -20,6 +20,8 @@ from main.models import SiteSettings
from utils.models import GMTokenGenerator from utils.models import GMTokenGenerator
from utils.models import ImageMixin, ProjectBaseMixin, PlatformMixin from utils.models import ImageMixin, ProjectBaseMixin, PlatformMixin
from utils.tokens import GMRefreshToken from utils.tokens import GMRefreshToken
from django.contrib.postgres.search import SearchVector
from phonenumber_field.modelfields import PhoneNumberField
class RoleQuerySet(models.QuerySet): class RoleQuerySet(models.QuerySet):
@ -142,6 +144,24 @@ class UserQuerySet(models.QuerySet):
role = Role.objects.filter(role=Role.ESTABLISHMENT_MANAGER).first() role = Role.objects.filter(role=Role.ESTABLISHMENT_MANAGER).first()
return self.by_role(role).filter(userrole__establishment=establishment) 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): class User(AbstractUser):
"""Base user model.""" """Base user model."""
@ -178,6 +198,8 @@ class User(AbstractUser):
default=None, default=None,
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
) )
phone = PhoneNumberField(blank=True, null=True, default=None,
verbose_name=_('Phone'))
EMAIL_FIELD = 'email' EMAIL_FIELD = 'email'
USERNAME_FIELD = 'email' USERNAME_FIELD = 'email'

View File

@ -2,8 +2,7 @@
from rest_framework import serializers from rest_framework import serializers
from account import models from account import models
from account.models import User from account.serializers import RoleBaseSerializer, UserSerializer, subscriptions_handler
from account.serializers import RoleBaseSerializer, subscriptions_handler
from main.models import SiteSettings 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) last_country = _SiteSettingsSerializer(read_only=True)
roles = RoleBaseSerializer(many=True, 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: class Meta(UserSerializer.Meta):
model = User
fields = ( fields = (
'id', 'id',
'last_login', 'last_login',
@ -54,9 +45,12 @@ class BackUserSerializer(serializers.ModelSerializer):
'last_country', 'last_country',
'roles', 'roles',
'subscriptions', 'subscriptions',
'phone',
'phone_as_international',
) )
extra_kwargs = { extra_kwargs = {
'password': {'write_only': True}, 'password': {'write_only': True},
'phone': {'write_only': True},
} }
read_only_fields = ( read_only_fields = (
'old_password', 'old_password',
@ -82,8 +76,7 @@ class BackUserSerializer(serializers.ModelSerializer):
class BackDetailUserSerializer(BackUserSerializer): class BackDetailUserSerializer(BackUserSerializer):
class Meta: class Meta(BackUserSerializer.Meta):
model = User
fields = ( fields = (
'id', 'id',
'last_country', 'last_country',
@ -109,6 +102,8 @@ class BackDetailUserSerializer(BackUserSerializer):
'groups', 'groups',
'user_permissions', 'user_permissions',
'subscriptions', 'subscriptions',
'phone',
'phone_as_international',
) )
read_only_fields = ( read_only_fields = (
'old_password', 'old_password',

View File

@ -13,6 +13,7 @@ from notification.models import Subscribe, Subscriber
from utils import exceptions as utils_exceptions from utils import exceptions as utils_exceptions
from utils import methods as utils_methods from utils import methods as utils_methods
from utils.methods import generate_string_code from utils.methods import generate_string_code
from phonenumber_field.serializerfields import PhoneNumberField
def subscriptions_handler(subscriptions_list, user): def subscriptions_handler(subscriptions_list, user):
@ -43,6 +44,7 @@ class RoleBaseSerializer(serializers.ModelSerializer):
"""Serializer for model Role.""" """Serializer for model Role."""
role_display = serializers.CharField(source='get_role_display', read_only=True) role_display = serializers.CharField(source='get_role_display', read_only=True)
navigation_bar_permission = NavigationBarPermissionBaseSerializer(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: class Meta:
"""Meta class.""" """Meta class."""
@ -51,6 +53,7 @@ class RoleBaseSerializer(serializers.ModelSerializer):
'id', 'id',
'role_display', 'role_display',
'navigation_bar_permission', 'navigation_bar_permission',
'country_code',
] ]
@ -75,6 +78,9 @@ class UserSerializer(serializers.ModelSerializer):
required=False, required=False,
help_text='list of subscription_types id', help_text='list of subscription_types id',
) )
phone_as_international = PhoneNumberField(source='phone.as_international',
allow_null=True,
read_only=True)
class Meta: class Meta:
model = models.User model = models.User
@ -90,6 +96,8 @@ class UserSerializer(serializers.ModelSerializer):
'newsletter', 'newsletter',
'roles', 'roles',
'subscriptions', 'subscriptions',
'phone',
'phone_as_international',
] ]
extra_kwargs = { extra_kwargs = {
'first_name': {'required': False, 'write_only': True, }, 'first_name': {'required': False, 'write_only': True, },
@ -98,6 +106,7 @@ class UserSerializer(serializers.ModelSerializer):
'image_url': {'required': False, }, 'image_url': {'required': False, },
'cropped_image_url': {'required': False, }, 'cropped_image_url': {'required': False, },
'newsletter': {'required': False, }, 'newsletter': {'required': False, },
'phone': {'required': False, 'write_only': True, }
} }
def create(self, validated_data): def create(self, validated_data):
@ -113,7 +122,7 @@ class UserSerializer(serializers.ModelSerializer):
def validate_email(self, value): def validate_email(self, value):
"""Validate email 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.') raise serializers.ValidationError(detail='Equal email address.')
return value return value

View File

@ -15,6 +15,7 @@ from account.serializers.common import RoleBaseSerializer
class RoleListView(generics.ListCreateAPIView): class RoleListView(generics.ListCreateAPIView):
serializer_class = RoleBaseSerializer serializer_class = RoleBaseSerializer
queryset = models.Role.objects.all() queryset = models.Role.objects.all()
filter_class = filters.RoleListFilter
class RoleTabRetrieveView(generics.GenericAPIView): class RoleTabRetrieveView(generics.GenericAPIView):

View File

@ -54,6 +54,3 @@ class Image(ProjectBaseMixin, SORLImageMixin, PlatformMixin):
if completely: if completely:
# Delete an instance of image # Delete an instance of image
super().delete() super().delete()

View File

@ -12,6 +12,7 @@ from timetable.models import Timetable
from utils.legacy_parser import parse_legacy_schedule_content from utils.legacy_parser import parse_legacy_schedule_content
from utils.serializers import TimeZoneChoiceField from utils.serializers import TimeZoneChoiceField
from utils.slug_generator import generate_unique_slug from utils.slug_generator import generate_unique_slug
from phonenumber_field.phonenumber import PhoneNumber
class EstablishmentSerializer(serializers.ModelSerializer): class EstablishmentSerializer(serializers.ModelSerializer):
@ -58,6 +59,7 @@ class EstablishmentSerializer(serializers.ModelSerializer):
def validate(self, data): def validate(self, data):
old_type = data.pop('type', None) old_type = data.pop('type', None)
phone = data.pop('phone', None)
data.update({ data.update({
'slug': generate_unique_slug(Establishment, data['slug'] if data['slug'] else data['name']), '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), 'establishment_type_id': self.get_type(old_type),
'is_publish': data.get('state') == 'published', 'is_publish': data.get('state') == 'published',
'subtype': self.get_subtype(old_type), 'subtype': self.get_subtype(old_type),
'phone': self.get_phone(phone),
}) })
data.pop('location') data.pop('location')
data.pop('state') data.pop('state')
@ -179,6 +182,13 @@ class EstablishmentSerializer(serializers.ModelSerializer):
establishment_type_id=establishment_type_id) establishment_type_id=establishment_type_id)
return subtype 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): class EstablishmentNoteSerializer(serializers.ModelSerializer):

View File

@ -517,7 +517,7 @@ STATICFILES_DIRS = (
# MEDIA # MEDIA
MEDIA_LOCATION = 'media' MEDIA_LOCATION = 'media'
PHONENUMBER_DB_FORMAT = 'NATIONAL' PHONENUMBER_DB_FORMAT = 'INTERNATIONAL'
PHONENUMBER_DEFAULT_REGION = "FR" PHONENUMBER_DEFAULT_REGION = "FR"
FALLBACK_LOCALE = 'en-GB' FALLBACK_LOCALE = 'en-GB'