"""Establishment models.""" from functools import reduce from django.contrib.contenttypes import fields as generic from django.core.exceptions import ValidationError from django.db import models from django.utils import timezone from django.utils.translation import gettext_lazy as _ from phonenumber_field.modelfields import PhoneNumberField from location.models import Address from utils.models import (ProjectBaseMixin, ImageMixin, TJSONField, TraslatedFieldsMixin, BaseAttributes) # todo: establishment type&subtypes check class EstablishmentType(ProjectBaseMixin, TraslatedFieldsMixin): """Establishment type model.""" name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'), help_text='{"en-GB":"some text"}') use_subtypes = models.BooleanField(_('Use subtypes'), default=True) class Meta: """Meta class.""" verbose_name = _('Establishment type') verbose_name_plural = _('Establishment types') class EstablishmentSubTypeManager(models.Manager): """Extended manager for establishment subtype.""" def make(self, name, establishment_type): obj = self.model(name=name, establishment_type=establishment_type) obj.full_clean() obj.save() return obj class EstablishmentSubType(ProjectBaseMixin, TraslatedFieldsMixin): """Establishment type model.""" name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'), help_text='{"en-GB":"some text"}') establishment_type = models.ForeignKey(EstablishmentType, on_delete=models.CASCADE, verbose_name=_('Type')) objects = EstablishmentSubTypeManager() class Meta: """Meta class.""" verbose_name = _('Establishment subtype') verbose_name_plural = _('Establishment subtypes') def clean_fields(self, exclude=None): if not self.establishment_type.use_subtypes: raise ValidationError(_('Establishment type is not use subtypes.')) class EstablishmentQuerySet(models.QuerySet): """Extended queryset for Establishment model.""" def search(self, value, locale=None): """Search text in JSON fields.""" if locale is not None: filters = [ {f'name__{locale}__icontains': value}, {f'description__{locale}__icontains': value} ] return self.filter(reduce(lambda x, y: x | y, [models.Q(**i) for i in filters])) else: return self.none() def prefetch_actual_employees(self): """Prefetch actual employees.""" return self.prefetch_related( models.Prefetch('establishmentemployee_set', queryset=EstablishmentEmployee.objects.actual().select_related( 'position'), to_attr='actual_establishment_employees')) class Establishment(ProjectBaseMixin, ImageMixin, TraslatedFieldsMixin): """Establishment model.""" name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Name'), help_text='{"en-GB":"some text"}') description = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'), help_text='{"en-GB":"some text"}') public_mark = models.PositiveIntegerField(blank=True, null=True, default=None, verbose_name=_('Public mark'),) toque_number = models.PositiveIntegerField(blank=True, null=True, default=None, verbose_name=_('Toque number'),) establishment_type = models.ForeignKey(EstablishmentType, related_name='establishment', on_delete=models.PROTECT, verbose_name=_('Type')) establishment_subtypes = models.ManyToManyField(EstablishmentSubType, related_name='subtype_establishment', verbose_name=_('Subtype')) address = models.ForeignKey(Address, blank=True, null=True, default=None, on_delete=models.PROTECT, verbose_name=_('Address')) price_level = models.PositiveIntegerField(blank=True, null=True, default=None, verbose_name=_('Price level')) awards = generic.GenericRelation(to='main.Award') tags = generic.GenericRelation(to='main.MetaDataContent') reviews = generic.GenericRelation(to='review.Review') objects = EstablishmentQuerySet.as_manager() class Meta: """Meta class.""" verbose_name = _('Establishment') verbose_name_plural = _('Establishments') # todo: recalculate toque_number def recalculate_toque_number(self): self.toque_number = 4 def recalculate_price_level(self, low_price=None, high_price=None): if low_price is None or high_price is None: low_price, high_price = self.get_price_level() # todo: calculate price level self.price_level = 3 def get_price_level(self): country = self.address.city.country return country.low_price, country.high_price # todo: make via prefetch @property def subtypes(self): return EstablishmentSubType.objects.filter( subtype_establishment=self, establishment_type=self.establishment_type, establishment_type__use_subtypes=True) def set_establishment_type(self, establishment_type): self.establishment_type = establishment_type self.establishment_subtypes.exclude( establishement_type=establishment_type).delete() def add_establishment_subtype(self, establishment_subtype): if establishment_subtype.establishment_type != self.establishment_type: raise ValidationError('Establishment type of subtype does not match') self.establishment_subtypes.add(establishment_subtype) class Position(BaseAttributes, TraslatedFieldsMixin): """Position model.""" name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'), help_text='{"en":"some text"}') class Meta: """Meta class.""" verbose_name = _('Position') verbose_name_plural = _('Positions') class EstablishmentEmployeeQuerySet(models.QuerySet): """Extended queryset for EstablishmEntemployee model.""" def actual(self): """Actual objects..""" now = timezone.now() return self.filter(models.Q(from_date__lte=now), (models.Q(to_date__gte=now) | models.Q(to_date__isnull=True))) class EstablishmentEmployee(BaseAttributes): """EstablishmentEmployee model.""" establishment = models.ForeignKey(Establishment, on_delete=models.PROTECT, verbose_name=_('Establishment')) employee = models.ForeignKey('establishment.Employee', on_delete=models.PROTECT, verbose_name=_('Employee')) from_date = models.DateTimeField(default=timezone.now, verbose_name=_('From date')) to_date = models.DateTimeField(blank=True, null=True, default=None, verbose_name=_('To date')) position = models.ForeignKey(Position, on_delete=models.PROTECT, verbose_name=_('Position')) objects = EstablishmentEmployeeQuerySet.as_manager() class Employee(BaseAttributes): """Employee model.""" user = models.OneToOneField('account.User', on_delete=models.PROTECT, null=True, blank=True, default=None, verbose_name=_('User')) name = models.CharField(max_length=255, verbose_name=_('Last name')) establishments = models.ManyToManyField(Establishment, related_name='employees', through=EstablishmentEmployee) awards = generic.GenericRelation(to='main.Award') tags = generic.GenericRelation(to='main.MetaDataContent') class Meta: """Meta class.""" verbose_name = _('Employee') verbose_name_plural = _('Employees') class EstablishmentScheduleQuerySet(models.QuerySet): """QuerySet for model EstablishmentSchedule""" class EstablishmentSchedule(BaseAttributes): """Establishment schedule model.""" establishment = models.OneToOneField(Establishment, related_name='schedule', on_delete=models.CASCADE, verbose_name=_('Establishment')) schedule = models.ManyToManyField(to='timetable.Timetable', verbose_name=_('Establishment schedule')) objects = EstablishmentScheduleQuerySet.as_manager() class Meta: """Meta class""" verbose_name = _('Establishment schedule') verbose_name_plural = _('Establishment schedules') class ContactPhone(models.Model): """Contact phone model.""" establishment = models.ForeignKey( Establishment, related_name='phones', on_delete=models.CASCADE) phone = PhoneNumberField() class Meta: verbose_name = _('contact phone') verbose_name_plural = _('contact phones') def __str__(self): return f'{self.phone.as_e164}' class ContactEmail(models.Model): """Contact email model.""" establishment = models.ForeignKey( Establishment, related_name='emails', on_delete=models.CASCADE) email = models.EmailField() class Meta: verbose_name = _('contact email') verbose_name_plural = _('contact emails') def __str__(self): return f'{self.email}' # # class Wine(TraslatedFieldsMixin, models.Model): # """Wine model.""" # establishment = models.ForeignKey( # 'establishment.Establishment', verbose_name=_('establishment'), # on_delete=models.CASCADE) # bottles = models.IntegerField(_('bottles')) # price_min = models.DecimalField( # _('price min'), max_digits=14, decimal_places=2) # price_max = models.DecimalField( # _('price max'), max_digits=14, decimal_places=2) # by_glass = models.BooleanField(_('by glass')) # price_glass_min = models.DecimalField( # _('price min'), max_digits=14, decimal_places=2) # price_glass_max = models.DecimalField( # _('price max'), max_digits=14, decimal_places=2) # class Plate(TraslatedFieldsMixin, models.Model): """Plate model.""" name = TJSONField( blank=True, null=True, default=None, verbose_name=_('name'), help_text='{"en-GB":"some text"}') description = TJSONField( blank=True, null=True, default=None, verbose_name=_('description'), help_text='{"en-GB":"some text"}') price = models.DecimalField( _('price'), max_digits=14, decimal_places=2) is_signature_plate = models.BooleanField(_('is signature plate')) currency = models.ForeignKey( 'main.Currency', verbose_name=_('currency'), on_delete=models.CASCADE) menu = models.ForeignKey( 'establishment.Menu', verbose_name=_('menu'), on_delete=models.CASCADE) class Meta: verbose_name = _('plate') verbose_name_plural = _('plates') def __str__(self): return f'plate_id:{self.id}' class Menu(TraslatedFieldsMixin, BaseAttributes): """Menu model.""" category = TJSONField( blank=True, null=True, default=None, verbose_name=_('category'), help_text='{"en-GB":"some text"}') establishment = models.ForeignKey( 'establishment.Establishment', verbose_name=_('establishment'), on_delete=models.CASCADE) class Meta: verbose_name = _('menu') verbose_name_plural = _('menu') class CommentQuerySet(models.QuerySet): """QuerySets for Comment model.""" def by_author(self, author): """Return comments by author""" return self.filter(author=author) class Comment(ProjectBaseMixin): """Comment model.""" text = models.TextField(verbose_name=_('Comment text')) mark = models.PositiveIntegerField(blank=True, null=True, default=None, verbose_name=_('Mark')) author = models.ForeignKey('account.User', related_name='comments', on_delete=models.CASCADE, verbose_name=_('Author')) establishment = models.ForeignKey(Establishment, related_name='comments', on_delete=models.CASCADE, verbose_name=_('Establishment')) objects = CommentQuerySet.as_manager() class Meta: """Meta class""" verbose_name = _('Comment') verbose_name_plural = _('Comments') def __str__(self): """String representation""" return str(self.author)