gault-millau/apps/establishment/serializers/back.py

934 lines
30 KiB
Python

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
from establishment import models, serializers as model_serializers
from establishment.models import ContactEmail, ContactPhone, EstablishmentEmployee
from establishment.serializers.common import ContactPhonesSerializer
from gallery.models import Image
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
def phones_handler(phones_list, establishment):
"""
create or update phones for establishment
"""
ContactPhone.objects.filter(establishment=establishment).delete()
for new_phone in phones_list:
ContactPhone.objects.create(establishment=establishment, phone=new_phone)
def emails_handler(emails_list, establishment):
"""
create or update emails for establishment
"""
ContactEmail.objects.filter(establishment=establishment).delete()
for new_email in emails_list:
ContactEmail.objects.create(establishment=establishment, email=new_email)
class EstablishmentListCreateSerializer(model_serializers.EstablishmentBaseSerializer):
"""Establishment create serializer"""
type_id = serializers.PrimaryKeyRelatedField(
source='establishment_type',
queryset=models.EstablishmentType.objects.all(),
write_only=True
)
address_id = serializers.PrimaryKeyRelatedField(
source='address',
queryset=models.Address.objects.all(),
write_only=True
)
transliterated_name = serializers.CharField(
required=False, allow_null=True, allow_blank=True
)
socials = model_serializers.SocialNetworkRelatedSerializers(
read_only=True,
many=True,
)
type = model_serializers.EstablishmentTypeBaseSerializer(
source='establishment_type',
read_only=True,
)
tz = TimeZoneChoiceField()
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)
emails = serializers.ListField(
source='contact_emails',
allow_null=True,
allow_empty=True,
child=serializers.CharField(max_length=128),
required=False,
)
class Meta(model_serializers.EstablishmentBaseSerializer.Meta):
fields = [
'id',
'name',
'transliterated_name',
'index_name',
'website',
'phones',
'contact_phones',
'emails',
'price_level',
'toque_number',
'type_id',
'type',
'socials',
'image_url',
'slug',
'facebook',
'twitter',
'instagram',
# TODO: check in admin filters
'is_publish',
'guestonline_id',
'lastable_id',
'tags',
'tz',
'address_id',
'status',
'status_display',
]
def to_representation(self, instance):
data = super(EstablishmentListCreateSerializer, self).to_representation(instance)
data['phones'] = data.pop('contact_phones', None)
return data
def create(self, validated_data):
phones_list = []
if 'contact_phones' in validated_data:
phones_list = validated_data.pop('contact_phones')
emails_list = []
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)
return instance
class EstablishmentPositionListSerializer(model_serializers.EstablishmentBaseSerializer):
"""Establishment position serializer"""
class Meta:
model = models.Establishment
fields = [
'id',
'name',
'transliterated_name',
'index_name',
]
class EstablishmentRUDSerializer(model_serializers.EstablishmentBaseSerializer):
"""Establishment create serializer"""
type_id = serializers.PrimaryKeyRelatedField(
source='establishment_type',
queryset=models.EstablishmentType.objects.all(), write_only=True
)
address = AddressDetailSerializer(read_only=True)
emails = serializers.ListField(
source='contact_emails',
allow_null=True,
allow_empty=True,
child=serializers.CharField(max_length=128),
required=False,
)
socials = model_serializers.SocialNetworkRelatedSerializers(read_only=True,
many=True, )
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=PhoneNumberField(),
required=False,
write_only=True,
)
contact_phones = ContactPhonesSerializer(source='phones', read_only=True, many=True)
class Meta(model_serializers.EstablishmentBaseSerializer.Meta):
fields = [
'id',
'slug',
'transliterated_name',
'name',
'index_name',
'website',
'phones',
'contact_phones',
'emails',
'price_level',
'toque_number',
'type_id',
'type',
'subtypes',
'socials',
'image_url',
'facebook',
'twitter',
'instagram',
# TODO: check in admin filters
'is_publish',
'address',
'transportation',
'tags',
'status',
'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:
phones_list = validated_data.pop('contact_phones')
emails_list = []
if 'contact_emails' in validated_data:
emails_list = validated_data.pop('contact_emails')
instance = super().update(instance, validated_data)
phones_handler(phones_list, instance)
emails_handler(emails_list, instance)
return instance
class SocialChoiceSerializers(serializers.ModelSerializer):
"""SocialChoice serializers."""
class Meta:
model = models.SocialChoice
fields = '__all__'
class SocialNetworkSerializers(serializers.ModelSerializer):
"""Social network serializers."""
class Meta:
model = models.SocialNetwork
fields = [
'id',
'establishment',
'network',
'url',
]
class PlatesSerializers(model_serializers.PlateSerializer):
"""Plates serializers."""
currency_id = serializers.PrimaryKeyRelatedField(
source='currency',
queryset=Currency.objects.all(), write_only=True
)
class Meta:
"""Meta class."""
model = models.Plate
fields = model_serializers.PlateSerializer.Meta.fields + [
'name',
'currency_id',
'menu'
]
class ContactPhoneBackSerializers(PhoneMixinSerializer, serializers.ModelSerializer):
"""ContactPhone serializers."""
class Meta:
model = models.ContactPhone
fields = [
'id',
'establishment',
'phone',
'country_calling_code',
'national_calling_number',
]
extra_kwarg = {
'phone': {'write_only': True}
}
class ContactEmailBackSerializers(model_serializers.PlateSerializer):
"""ContactEmail serializers."""
class Meta:
model = models.ContactEmail
fields = [
'id',
'establishment',
'email'
]
class PositionBackSerializer(serializers.ModelSerializer):
"""Position Back serializer."""
name_translated = TranslatedField()
class Meta:
model = models.Position
fields = [
'id',
'name_translated',
'priority',
'index_name',
]
# TODO: test decorator
@with_base_attributes
class EmployeeBackSerializers(PhoneMixinSerializer, serializers.ModelSerializer):
"""Employee serializers."""
public_mark = serializers.SerializerMethodField()
positions = serializers.SerializerMethodField()
establishment = serializers.SerializerMethodField()
awards = AwardSerializer(many=True, read_only=True)
toque_number = serializers.SerializerMethodField()
photo = ImageBaseSerializer(source='crop_image', read_only=True)
photo_id = serializers.PrimaryKeyRelatedField(source='photo', write_only=True, required=False,
queryset=Image.objects.all(), allow_null=True)
@staticmethod
@lru_cache(maxsize=32)
def get_qs(obj):
return obj.establishmentemployee_set.actual().annotate(
public_mark=F('establishment__public_mark'),
est_id=F('establishment__id'),
est_slug=F('establishment__slug'),
toque_number=F('establishment__toque_number'),
).order_by('-from_date').first()
def get_public_mark(self, obj):
"""Get last list actual public_mark"""
if hasattr(obj, 'prefetched_establishment_employee'):
return obj.prefetched_establishment_employee[0].establishment.public_mark if len(
obj.prefetched_establishment_employee) else None
qs = self.get_qs(obj)
if qs:
return qs.public_mark
return None
def get_toque_number(self, obj):
if hasattr(obj, 'prefetched_establishment_employee'):
return obj.prefetched_establishment_employee[0].establishment.toque_number if len(
obj.prefetched_establishment_employee) else None
qs = self.get_qs(obj)
if qs:
return qs.toque_number
return None
def get_positions(self, obj):
"""Get last list actual positions"""
if hasattr(obj, 'prefetched_establishment_employee'):
if not len(obj.prefetched_establishment_employee):
return []
return [PositionBackSerializer(ee.position).data for ee in obj.prefetched_establishment_employee]
est_id = self.get_qs(obj)
if not est_id:
return None
qs = obj.establishmentemployee_set.actual() \
.filter(establishment_id=est_id.establishment_id) \
.prefetch_related('position').values('position')
positions = models.Position.objects.filter(id__in=[q['position'] for q in qs])
return [PositionBackSerializer(p).data for p in positions]
def get_establishment(self, obj):
"""Get last actual establishment"""
if hasattr(obj, 'prefetched_establishment_employee'):
return {
'id': obj.prefetched_establishment_employee[0].establishment.pk,
'slug': obj.prefetched_establishment_employee[0].establishment.slug,
} if len(obj.prefetched_establishment_employee) else None
est = self.get_qs(obj)
if not est:
return None
return {
"id": est.est_id,
"slug": est.est_slug
}
class Meta:
model = models.Employee
fields = [
'id',
'name',
'last_name',
'user',
'public_mark',
'positions',
'awards',
'establishment',
'sex',
'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):
"""Establishment Employee serializer."""
employee = EmployeeBackSerializers()
position = PositionBackSerializer()
class Meta:
model = models.EstablishmentEmployee
fields = [
'id',
'employee',
'from_date',
'to_date',
'position',
'status',
]
class EstEmployeeBackSerializer(EmployeeBackSerializers):
@property
def request_kwargs(self):
"""Get url kwargs from request."""
return self.context.get('request').parser_context.get('kwargs')
def get_positions(self, obj):
establishment_id = self.request_kwargs.get('establishment_id')
establishment = self.context['view'].kwargs['establishment_instance']
establishment_type = self.context['view'].kwargs['establishment_instance_type']
establishment_subtypes = self.context['view'].kwargs['establishment_instance_subtypes']
es_emp = EstablishmentEmployee.objects.filter(
employee=obj,
establishment_id=establishment_id,
).distinct().order_by('from_date').prefetch_related('position', 'position__establishment_type',
'position__establishment_subtype')
result = []
for item in es_emp:
if item.position.establishment_type == establishment_type or \
item.position.establishment_subtype in establishment_subtypes or \
(item.position.establishment_subtype is None and item.position.establishment_type is None):
# show only relevant for current establishment positions
result.append({
'id': item.id,
'from_date': item.from_date,
'to_date': item.to_date,
'status': item.status,
'position_id': item.position_id,
'position_priority': item.position.priority,
'position_index_name': item.position.index_name,
'position_name_translated': item.position.name_translated,
})
return result
class Meta:
model = models.Employee
fields = [
'id',
'name',
'last_name',
'user',
'public_mark',
'positions',
'awards',
'sex',
'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):
"""Serializer class for model EstablishmentGallery."""
class Meta:
"""Meta class"""
model = models.EstablishmentGallery
fields = [
'id',
'is_main',
]
@property
def request_kwargs(self):
"""Get url kwargs from request."""
return self.context.get('request').parser_context.get('kwargs')
def validate(self, attrs):
"""Override validate method."""
establishment_pk = self.request_kwargs.get('pk')
establishment_slug = self.request_kwargs.get('slug')
search_kwargs = {'pk': establishment_pk} if establishment_pk else {'slug': establishment_slug}
image_id = self.request_kwargs.get('image_id')
establishment_qs = models.Establishment.objects.filter(**search_kwargs)
image_qs = Image.objects.filter(id=image_id)
if not establishment_qs.exists():
raise serializers.ValidationError({'detail': _('Establishment not found')})
if not image_qs.exists():
raise serializers.ValidationError({'detail': _('Image not found')})
establishment = establishment_qs.first()
image = image_qs.first()
if image in establishment.gallery.all():
raise serializers.ValidationError({'detail': _('Image is already added.')})
attrs['establishment'] = establishment
attrs['image'] = image
return attrs
class EstablishmentCompanyListCreateSerializer(model_serializers.CompanyBaseSerializer):
"""Serializer for linking page w/ advertisement."""
class Meta(model_serializers.CompanyBaseSerializer.Meta):
"""Meta class."""
model_serializers.CompanyBaseSerializer.Meta.extra_kwargs.update({
'establishment': {'required': False}
})
def create(self, validated_data):
"""Overridden create method."""
validated_data['establishment'] = self.context.get('view').get_object()
return super().create(validated_data)
class EstablishmentNoteBaseSerializer(serializers.ModelSerializer):
"""Serializer for model EstablishmentNote."""
user_detail = UserShortSerializer(read_only=True, source='user')
class Meta:
"""Meta class."""
model = models.EstablishmentNote
fields = [
'id',
'created',
'modified',
'text',
'user',
'user_detail',
'establishment',
]
extra_kwargs = {
'created': {'read_only': True},
'modified': {'read_only': True},
'establishment': {'required': False, 'write_only': True},
'user': {'required': False, 'write_only': True},
}
@property
def serializer_view(self):
"""Return view instance."""
return self.context.get('view')
class EstablishmentNoteListCreateSerializer(EstablishmentNoteBaseSerializer):
"""Serializer for List|Create action for model EstablishmentNote."""
def create(self, validated_data):
"""Overridden create method."""
validated_data['user'] = self.user
validated_data['establishment'] = self.establishment
return super().create(validated_data)
@property
def user(self):
"""Return user instance from view."""
if self.serializer_view:
return self.serializer_view.request.user
@property
def establishment(self):
"""Return establishment instance from view."""
if self.serializer_view:
return self.serializer_view.get_object()
class EstablishmentAdminListSerializer(UserShortSerializer):
"""Establishment admin serializer."""
class Meta:
model = UserShortSerializer.Meta.model
fields = [
'id',
'username',
'email'
]
class EstablishmentGuideSerializer(serializers.ModelSerializer):
"""Serializer for model Guide. """
class Meta:
"""Meta class."""
model = Guide
fields = [
'id',
]
class EstablishmentEmployeePositionsSerializer(serializers.ModelSerializer):
"""Establishments from employee serializer"""
restaurant_name = serializers.CharField(read_only=True, source='establishment.name')
position = PositionBackSerializer(read_only=True)
state = serializers.CharField(read_only=True, source='status')
start = serializers.DateTimeField(read_only=True, source='from_date')
end = serializers.DateTimeField(read_only=True, source='to_date')
class Meta:
model = models.EstablishmentEmployee
fields = [
'restaurant_name',
'position',
'state',
'start',
'end',
]
class _PlateSerializer(ProjectModelSerializer):
name_translated = TranslatedField()
class Meta:
model = models.Plate
fields = [
'name_translated',
'price',
]
class _MenuUploadsSerializer(serializers.Serializer):
id = serializers.IntegerField()
title = serializers.CharField()
original_url = serializers.URLField()
class MenuDishesSerializer(ProjectModelSerializer):
"""for dessert, main_course and starter category"""
plates = _PlateSerializer(read_only=True, many=True, source='plate_set', allow_null=True)
category_translated = serializers.CharField(read_only=True)
last_update = serializers.DateTimeField(source='created')
gallery = ImageBaseSerializer(read_only=True, source='crop_gallery', many=True)
class Meta:
model = models.Menu
fields = [
'id',
'category',
'category_translated',
'establishment',
'is_drinks_included',
'schedule',
'plates',
'last_update',
'gallery',
]
class MenuDishesRUDSerializers(ProjectModelSerializer):
"""for dessert, main_course and starter category"""
plates = _PlateSerializer(read_only=True, many=True, source='plate_set', allow_null=True)
gallery = ImageBaseSerializer(read_only=True, source='crop_gallery', many=True)
class Meta:
model = models.Menu
fields = [
'id',
'category',
'plates',
'establishment',
'is_drinks_included',
'schedule',
'gallery',
]
class MenuGallerySerializer(serializers.ModelSerializer):
"""Serializer class for model MenuGallery."""
class Meta:
"""Meta class"""
model = models.MenuGallery
fields = [
'id',
'is_main',
]
@property
def request_kwargs(self):
"""Get url kwargs from request."""
return self.context.get('request').parser_context.get('kwargs')
def create(self, validated_data):
menu_pk = self.request_kwargs.get('pk')
image_id = self.request_kwargs.get('image_id')
qs = models.MenuGallery.objects.filter(image_id=image_id, menu_id=menu_pk)
instance = qs.first()
if instance:
qs.update(**validated_data)
return instance
return super().create(validated_data)
def validate(self, attrs):
"""Override validate method."""
menu_pk = self.request_kwargs.get('pk')
image_id = self.request_kwargs.get('image_id')
menu_qs = models.Menu.objects.filter(pk=menu_pk)
image_qs = Image.objects.filter(id=image_id)
if not menu_qs.exists():
raise serializers.ValidationError({'detail': _('Menu not found')})
if not image_qs.exists():
raise serializers.ValidationError({'detail': _('Image not found')})
menu = menu_qs.first()
image = image_qs.first()
attrs['menu'] = menu
attrs['image'] = image
return attrs
class MenuFilesSerializers(ProjectModelSerializer):
type = serializers.CharField(read_only=True)
class Meta:
model = models.MenuFiles
fields = [
'id',
'name',
'type',
'file',
]
def create(self, validated_data):
instance = models.MenuFiles.objects.create(**validated_data)
return instance
def validate(self, attrs):
extension = attrs['file'].name.split('.')[-1]
attrs['type'] = 'image' if extension in ('jpg', 'jpeg', 'png') else 'file'
if 'name' not in attrs or not attrs['name']:
attrs['name'] = attrs['file'].name
return attrs
class MenuBackOfficeSerializer(serializers.ModelSerializer):
name = serializers.CharField(source='category_translated')
drinks_included = serializers.BooleanField(source='is_drinks_included')
establishment_id = serializers.IntegerField(source='establishment.id')
establishment_slug = serializers.CharField(source='establishment.slug', read_only=True)
uploads = MenuFilesSerializers(many=True, read_only=True)
class Meta:
model = models.Menu
fields = [
'id',
'type',
'name',
'establishment_id',
'establishment_slug',
'price',
'drinks_included',
'diner',
'lunch',
'last_update',
'uploads',
'uploads_ids',
]
extra_kwargs = {
'last_update': {'read_only': True},
'uploads_ids': {'write_only': True, 'source': 'uploads', 'required': True},
}
def validate(self, attrs):
attrs['category'] = {'en-GB': attrs.pop('category_translated')}
return attrs
def update(self, instance, validated_data):
uploaded_files_ids = validated_data.pop('uploads')
establishment_kwargs = validated_data.pop('establishment')
establishment = get_object_or_404(models.Establishment, **establishment_kwargs)
validated_data['establishment'] = establishment
instance = super().update(instance, validated_data)
instance.uploads.set(uploaded_files_ids)
return instance
def create(self, validated_data):
uploaded_files_ids = validated_data.pop('uploads')
establishment_kwargs = validated_data.pop('establishment')
establishment = get_object_or_404(models.Establishment, **establishment_kwargs)
validated_data['establishment'] = establishment
instance = models.Menu.objects.create(**validated_data)
instance.uploads.set(uploaded_files_ids)
return instance
class EstablishmentBackOfficeWineSerializer(serializers.ModelSerializer):
establishment_id = serializers.PrimaryKeyRelatedField(
source='establishment',
queryset=models.Establishment.objects.all(),
write_only=True
)
"""BackOffice wine serializer"""
class Meta:
model = models.EstablishmentBackOfficeWine
fields = (
'id',
'bottles',
'price_from',
'price_to',
'price_from_for_one',
'price_to_for_one',
'is_glass',
'establishment_id',
)
class CardAndWinesPlatesSerializer(serializers.ModelSerializer):
"""Serializer for card&wines backoffice section"""
type = serializers.CharField(source='menu.type')
class Meta:
model = models.Plate
fields = (
'id',
'name',
'is_signature_plate',
'price',
'type',
)
extra_kwargs = {
}
def create(self, validated_data):
establishment = get_object_or_404(models.Establishment, pk=self.context['view'].kwargs['establishment_id'])
menu_type = validated_data.pop('menu')['type']
if menu_type not in models.Menu.VALID_CARD_AND_WINES_CHOICES:
raise serializers.ValidationError({
'detail':
f'Incorrect type: {menu_type}. Valid types: [{", ".join(models.Menu.VALID_CARD_AND_WINES_CHOICES)}]'
})
menu_to_bind = establishment.menu_set.filter(type=menu_type).first()
if menu_to_bind is None:
menu_to_bind = models.Menu.objects.create(type=menu_type, category={'en-GB': menu_type},
establishment=establishment)
validated_data.update({'menu': menu_to_bind})
return super().create(validated_data)
def update(self, instance, validated_data):
menu_type = validated_data.pop('menu')['type']
if menu_type not in models.Menu.VALID_CARD_AND_WINES_CHOICES:
raise serializers.ValidationError({
'detail':
f'Incorrect type: {menu_type}. Valid types: [{", ".join(models.Menu.VALID_CARD_AND_WINES_CHOICES)}]'
})
if instance.menu.type != menu_type: # type changed
establishment = instance.menu.establishment
menu_to_bind = establishment.menu_set.filter(type=menu_type).first()
if menu_to_bind is None:
menu_to_bind = models.Menu.objects.create(type=menu_type, category={'en-GB': menu_type},
establishment=establishment)
validated_data.update({'menu': menu_to_bind})
return super().update(instance, validated_data)
class CardAndWinesSerializer(serializers.ModelSerializer):
"""View to show menus(not formulas) with dishes for certain establishment"""
wine = EstablishmentBackOfficeWineSerializer(allow_null=True, read_only=True, source='back_office_wine')
dishes = CardAndWinesPlatesSerializer(many=True, source='card_and_wine_plates')
class Meta:
model = models.Establishment
fields = (
'wine',
'dishes',
)