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