820 lines
32 KiB
Python
820 lines
32 KiB
Python
"""Establishment models."""
|
|
from datetime import datetime
|
|
from functools import reduce
|
|
from operator import or_
|
|
|
|
import elasticsearch_dsl
|
|
from django.conf import settings
|
|
from django.contrib.contenttypes import fields as generic
|
|
from django.contrib.gis.db.models.functions import Distance
|
|
from django.contrib.gis.geos import Point
|
|
from django.contrib.gis.measure import Distance as DistanceMeasure
|
|
from django.core.exceptions import ValidationError
|
|
from django.db import models
|
|
from django.db.models import When, Case, F, ExpressionWrapper, Subquery, Q
|
|
from django.utils import timezone
|
|
from django.utils.translation import gettext_lazy as _
|
|
from phonenumber_field.modelfields import PhoneNumberField
|
|
from timezone_field import TimeZoneField
|
|
|
|
from collection.models import Collection
|
|
from location.models import Address
|
|
from main.models import Award, Currency
|
|
from review.models import Review
|
|
from utils.models import (ProjectBaseMixin, TJSONField, URLImageMixin,
|
|
TranslatedFieldsMixin, BaseAttributes)
|
|
|
|
|
|
# todo: establishment type&subtypes check
|
|
class EstablishmentType(TranslatedFieldsMixin, ProjectBaseMixin):
|
|
"""Establishment type model."""
|
|
|
|
STR_FIELD_NAME = 'name'
|
|
|
|
# EXAMPLE OF INDEX NAME CHOICES
|
|
RESTAURANT = 'restaurant'
|
|
ARTISAN = 'artisan'
|
|
PRODUCER = 'producer'
|
|
|
|
name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'),
|
|
help_text='{"en-GB":"some text"}')
|
|
index_name = models.CharField(max_length=50, unique=True, db_index=True,
|
|
verbose_name=_('Index name'))
|
|
use_subtypes = models.BooleanField(_('Use subtypes'), default=True)
|
|
tag_categories = models.ManyToManyField('tag.TagCategory',
|
|
related_name='establishment_types',
|
|
verbose_name=_('Tag'))
|
|
|
|
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, TranslatedFieldsMixin):
|
|
"""Establishment type model."""
|
|
|
|
# EXAMPLE OF INDEX NAME CHOICES
|
|
WINERY = 'winery'
|
|
|
|
name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'),
|
|
help_text='{"en-GB":"some text"}')
|
|
index_name = models.CharField(max_length=50, unique=True, db_index=True,
|
|
verbose_name=_('Index name'))
|
|
establishment_type = models.ForeignKey(EstablishmentType,
|
|
on_delete=models.CASCADE,
|
|
verbose_name=_('Type'))
|
|
tag_categories = models.ManyToManyField('tag.TagCategory',
|
|
related_name='establishment_subtypes',
|
|
verbose_name=_('Tag'))
|
|
|
|
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 with_base_related(self):
|
|
"""Return qs with related objects."""
|
|
return self.select_related('address', 'establishment_type').\
|
|
prefetch_related('tags')
|
|
|
|
def with_schedule(self):
|
|
"""Return qs with related schedule."""
|
|
return self.prefetch_related('schedule')
|
|
|
|
def with_currency_related(self):
|
|
"""Return qs with related """
|
|
return self.prefetch_related('currency')
|
|
|
|
def with_extended_address_related(self):
|
|
"""Return qs with deeply related address models."""
|
|
return self.select_related('address__city', 'address__city__region', 'address__city__region__country',
|
|
'address__city__country')
|
|
|
|
def with_extended_related(self):
|
|
return self.select_related('establishment_type').\
|
|
prefetch_related('establishment_subtypes', 'awards', 'schedule',
|
|
'phones').\
|
|
prefetch_actual_employees()
|
|
|
|
def with_type_related(self):
|
|
return self.prefetch_related('establishment_subtypes')
|
|
|
|
def with_es_related(self):
|
|
"""Return qs with related for ES indexing objects."""
|
|
return self.select_related('address', 'establishment_type', 'address__city', 'address__city__country').\
|
|
prefetch_related('tags', 'schedule')
|
|
|
|
def search(self, value, locale=None):
|
|
"""Search text in JSON fields."""
|
|
if locale is not None:
|
|
filters = [
|
|
{f'name__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 es_search(self, value, locale=None):
|
|
"""Search text via ElasticSearch."""
|
|
from search_indexes.documents import EstablishmentDocument
|
|
search = EstablishmentDocument.search().filter(
|
|
elasticsearch_dsl.Q('match', name=value) |
|
|
elasticsearch_dsl.Q('match', **{f'description.{locale}': value})
|
|
).execute()
|
|
ids = [result.meta.id for result in search]
|
|
return self.filter(id__in=ids)
|
|
|
|
def by_country(self, country):
|
|
"""Return establishments by country code"""
|
|
return self.filter(address__city__country=country)
|
|
|
|
def by_country_code(self, code):
|
|
"""Return establishments by country code"""
|
|
return self.filter(address__city__country__code=code)
|
|
|
|
def published(self):
|
|
"""
|
|
Return QuerySet with published establishments.
|
|
"""
|
|
return self.filter(is_publish=True)
|
|
|
|
def has_published_reviews(self):
|
|
"""
|
|
Return QuerySet establishments with published reviews.
|
|
"""
|
|
return self.filter(reviews__status=Review.READY,)
|
|
|
|
def annotate_distance(self, point: Point = None):
|
|
"""
|
|
Return QuerySet with annotated field - distance
|
|
Description:
|
|
|
|
"""
|
|
return self.annotate(distance=Distance('address__coordinates', point,
|
|
srid=settings.GEO_DEFAULT_SRID))
|
|
|
|
def annotate_intermediate_public_mark(self):
|
|
"""
|
|
Return QuerySet with annotated field - intermediate_public_mark.
|
|
Description:
|
|
If establishments in collection POP and its mark is null, then
|
|
intermediate_mark is set to 10;
|
|
"""
|
|
return self.annotate(intermediate_public_mark=Case(
|
|
When(
|
|
collections__collection_type=Collection.POP,
|
|
public_mark__isnull=True,
|
|
then=settings.DEFAULT_ESTABLISHMENT_PUBLIC_MARK
|
|
),
|
|
default='public_mark',
|
|
output_field=models.FloatField()))
|
|
|
|
def annotate_mark_similarity(self, mark):
|
|
"""
|
|
Return a QuerySet with annotated field - mark_similarity
|
|
Description:
|
|
Similarity mark determined by comparison with compared establishment mark
|
|
"""
|
|
return self.annotate(mark_similarity=ExpressionWrapper(
|
|
mark - F('intermediate_public_mark'),
|
|
output_field=models.FloatField()
|
|
))
|
|
|
|
def similar(self, establishment_slug: str):
|
|
"""
|
|
Return QuerySet with objects that similar to Establishment.
|
|
:param establishment_slug: str Establishment slug
|
|
"""
|
|
establishment_qs = self.filter(slug=establishment_slug,
|
|
public_mark__isnull=False)
|
|
if establishment_qs.exists():
|
|
establishment = establishment_qs.first()
|
|
subquery_filter_by_distance = Subquery(
|
|
self.exclude(slug=establishment_slug)
|
|
.filter(image_url__isnull=False, public_mark__gte=10)
|
|
.has_published_reviews()
|
|
.annotate_distance(point=establishment.location)
|
|
.order_by('distance')[:settings.LIMITING_QUERY_OBJECTS]
|
|
.values('id')
|
|
)
|
|
return self.filter(id__in=subquery_filter_by_distance) \
|
|
.annotate_intermediate_public_mark() \
|
|
.annotate_mark_similarity(mark=establishment.public_mark) \
|
|
.order_by('mark_similarity') \
|
|
.distinct('mark_similarity', 'id')
|
|
else:
|
|
return self.none()
|
|
|
|
def last_reviewed(self, point: Point):
|
|
"""
|
|
Return QuerySet with last reviewed establishments.
|
|
:param point: location Point object, needs to ordering
|
|
"""
|
|
subquery_filter_by_distance = Subquery(
|
|
self.filter(image_url__isnull=False, public_mark__gte=10)
|
|
.has_published_reviews()
|
|
.annotate_distance(point=point)
|
|
.order_by('distance')[:settings.LIMITING_QUERY_OBJECTS]
|
|
.values('id')
|
|
)
|
|
return self.filter(id__in=subquery_filter_by_distance) \
|
|
.order_by('-reviews__published_at')
|
|
|
|
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'))
|
|
|
|
def annotate_in_favorites(self, user):
|
|
"""Annotate flag in_favorites"""
|
|
favorite_establishment_ids = []
|
|
if user.is_authenticated:
|
|
favorite_establishment_ids = user.favorite_establishment_ids
|
|
return self.annotate(in_favorites=Case(
|
|
When(
|
|
id__in=favorite_establishment_ids,
|
|
then=True),
|
|
default=False,
|
|
output_field=models.BooleanField(default=False)))
|
|
|
|
def by_distance_from_point(self, center, radius, unit='m'):
|
|
"""
|
|
Returns nearest establishments
|
|
|
|
:param center: point from which to find nearby establishments
|
|
:param radius: the maximum distance within the radius of which to look for establishments
|
|
:return: all establishments within the specified radius of specified point
|
|
:param unit: length unit e.g. m, km. Default is 'm'.
|
|
"""
|
|
kwargs = {unit: radius}
|
|
return self.filter(address__coordinates__distance_lte=(center, DistanceMeasure(**kwargs)))
|
|
|
|
def artisans(self):
|
|
"""Return artisans."""
|
|
return self.filter(establishment_type__index_name__icontains=EstablishmentType.ARTISAN)
|
|
|
|
def producers(self):
|
|
"""Return producers."""
|
|
return self.filter(establishment_type__index_name__icontains=EstablishmentType.PRODUCER)
|
|
|
|
def restaurants(self):
|
|
"""Return restaurants."""
|
|
return self.filter(establishment_type__index_name__icontains=EstablishmentType.RESTAURANT)
|
|
|
|
def wineries(self):
|
|
"""Return wineries."""
|
|
return self.producers().filter(
|
|
establishment_subtypes__index_name__icontains=EstablishmentSubType.WINERY)
|
|
|
|
def by_type(self, value):
|
|
"""Return QuerySet with type by value."""
|
|
return self.filter(establishment_type__index_name__icontains=value)
|
|
|
|
def by_subtype(self, value):
|
|
"""Return QuerySet with subtype by value."""
|
|
return self.filter(establishment_subtypes__index_name__icontains=value)
|
|
|
|
def by_public_mark_range(self, min_value, max_value):
|
|
"""Filter by public mark range."""
|
|
return self.filter(public_mark__gte=min_value, public_mark__lte=max_value)
|
|
|
|
def exclude_public_mark_ranges(self, ranges):
|
|
"""Exclude public mark ranges."""
|
|
return self.exclude(reduce(or_, [Q(public_mark__gte=r[0],
|
|
public_mark__lte=r[1])
|
|
for r in ranges]))
|
|
|
|
def exclude_countries(self, countries):
|
|
"""Exclude countries."""
|
|
return self.exclude(address__city__country__in=countries)
|
|
|
|
|
|
class Establishment(ProjectBaseMixin, URLImageMixin, TranslatedFieldsMixin):
|
|
"""Establishment model."""
|
|
|
|
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
|
|
name = models.CharField(_('name'), max_length=255, default='')
|
|
transliterated_name = models.CharField(default='', max_length=255,
|
|
verbose_name=_('Transliterated name'))
|
|
index_name = models.CharField(_('Index name'), max_length=255, default='')
|
|
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'),)
|
|
# todo: set default 0
|
|
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,
|
|
blank=True,
|
|
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'))
|
|
website = models.URLField(blank=True, null=True, default=None, max_length=255,
|
|
verbose_name=_('Web site URL'))
|
|
facebook = models.URLField(blank=True, null=True, default=None, max_length=255,
|
|
verbose_name=_('Facebook URL'))
|
|
twitter = models.URLField(blank=True, null=True, default=None, max_length=255,
|
|
verbose_name=_('Twitter URL'))
|
|
lafourchette = models.URLField(blank=True, null=True, default=None, max_length=255,
|
|
verbose_name=_('Lafourchette URL'))
|
|
guestonline_id = models.PositiveIntegerField(blank=True, verbose_name=_('guestonline id'),
|
|
null=True, default=None,)
|
|
lastable_id = models.TextField(blank=True, verbose_name=_('lastable id'), unique=True,
|
|
null=True, default=None,)
|
|
booking = models.URLField(blank=True, null=True, default=None, max_length=255,
|
|
verbose_name=_('Booking URL'))
|
|
is_publish = models.BooleanField(default=False, verbose_name=_('Publish status'))
|
|
schedule = models.ManyToManyField(to='timetable.Timetable',
|
|
verbose_name=_('Establishment schedule'),
|
|
related_name='schedule')
|
|
# holidays_from = models.DateTimeField(verbose_name=_('Holidays from'),
|
|
# help_text=_('Holidays closing date from'))
|
|
# holidays_to = models.DateTimeField(verbose_name=_('Holidays to'),
|
|
# help_text=_('Holidays closing date to'))
|
|
transportation = models.TextField(blank=True, null=True, default=None,
|
|
verbose_name=_('Transportation'))
|
|
collections = models.ManyToManyField(to='collection.Collection',
|
|
related_name='establishments',
|
|
blank=True, default=None,
|
|
verbose_name=_('Collections'))
|
|
preview_image_url = models.URLField(verbose_name=_('Preview image URL path'), max_length=255,
|
|
blank=True, null=True, default=None)
|
|
slug = models.SlugField(unique=True, max_length=255, null=True,
|
|
verbose_name=_('Establishment slug'))
|
|
tz = TimeZoneField(default=settings.TIME_ZONE)
|
|
|
|
awards = generic.GenericRelation(to='main.Award', related_query_name='establishment')
|
|
tags = models.ManyToManyField('tag.Tag', related_name='establishments',
|
|
verbose_name=_('Tag'))
|
|
reviews = generic.GenericRelation(to='review.Review')
|
|
comments = generic.GenericRelation(to='comment.Comment')
|
|
favorites = generic.GenericRelation(to='favorites.Favorites')
|
|
currency = models.ForeignKey(Currency, blank=True, null=True, default=None,
|
|
on_delete=models.PROTECT,
|
|
verbose_name=_('currency'))
|
|
|
|
objects = EstablishmentQuerySet.as_manager()
|
|
|
|
class Meta:
|
|
"""Meta class."""
|
|
|
|
verbose_name = _('Establishment')
|
|
verbose_name_plural = _('Establishments')
|
|
|
|
def __str__(self):
|
|
return f'id:{self.id}-{self.name}'
|
|
|
|
# todo: recalculate toque_number
|
|
def recalculate_toque_number(self):
|
|
toque_number = 0
|
|
if self.address and self.public_mark:
|
|
toque_number = RatingStrategy.objects. \
|
|
get_toque_number(country=self.address.city.country,
|
|
public_mark=self.public_mark)
|
|
self.toque_number = toque_number
|
|
self.save()
|
|
|
|
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)
|
|
|
|
@property
|
|
def vintage_year(self):
|
|
last_review = self.reviews.by_status(Review.READY).last()
|
|
if last_review:
|
|
return last_review.vintage
|
|
|
|
@property
|
|
def best_price_menu(self):
|
|
return 150
|
|
|
|
@property
|
|
def best_price_carte(self):
|
|
return 200
|
|
|
|
@property
|
|
def range_price_menu(self):
|
|
plates = self.menu_set.filter(
|
|
models.Q(category={'en-GB': 'formulas'})
|
|
).aggregate(
|
|
max=models.Max('plate__price', output_field=models.FloatField()),
|
|
min=models.Min('plate__price', output_field=models.FloatField()))
|
|
return plates
|
|
|
|
@property
|
|
def range_price_carte(self):
|
|
plates = self.menu_set.filter(
|
|
models.Q(category={'en-GB': 'starter'}) |
|
|
models.Q(category={'en-GB': 'main_course'}) |
|
|
models.Q(category={'en-GB': 'dessert'})
|
|
).aggregate(
|
|
max=models.Max('plate__price', output_field=models.FloatField()),
|
|
min=models.Min('plate__price', output_field=models.FloatField()),
|
|
)
|
|
return plates
|
|
|
|
@property
|
|
def works_noon(self):
|
|
""" Used for indexing working by day """
|
|
return [ret.weekday for ret in self.schedule.all() if ret.works_at_noon]
|
|
|
|
@property
|
|
def works_at_weekday(self):
|
|
""" Used for indexing by working whole day criteria """
|
|
return [ret.weekday for ret in self.schedule.all()]
|
|
|
|
@property
|
|
def works_evening(self):
|
|
""" Used for indexing working by day """
|
|
return [ret.weekday for ret in self.schedule.all() if ret.works_at_afternoon]
|
|
|
|
@property
|
|
def works_now(self):
|
|
""" Is establishment working now """
|
|
now_at_est_tz = datetime.now(tz=self.tz)
|
|
current_week = now_at_est_tz.weekday()
|
|
schedule_for_today = self.schedule.filter(weekday=current_week).first()
|
|
if schedule_for_today is None or schedule_for_today.opening_time is None or schedule_for_today.ending_time is None:
|
|
return False
|
|
time_at_est_tz = now_at_est_tz.time()
|
|
return schedule_for_today.ending_time > time_at_est_tz > schedule_for_today.opening_time
|
|
|
|
@property
|
|
def tags_indexing(self):
|
|
return [{'id': tag.metadata.id,
|
|
'label': tag.metadata.label} for tag in self.tags.all()]
|
|
|
|
@property
|
|
def last_published_review(self):
|
|
"""Return last published review"""
|
|
return self.reviews.published()\
|
|
.order_by('-published_at').first()
|
|
|
|
@property
|
|
def location(self):
|
|
"""
|
|
Return Point object of establishment location
|
|
"""
|
|
return self.address.coordinates
|
|
|
|
@property
|
|
def the_most_recent_award(self):
|
|
return Award.objects.filter(Q(establishment=self) | Q(employees__establishments=self)) \
|
|
.latest(field_name='vintage_year')
|
|
|
|
@property
|
|
def country_id(self):
|
|
"""
|
|
Return Country id of establishment location
|
|
"""
|
|
return self.address.country_id
|
|
|
|
@property
|
|
def establishment_id(self):
|
|
"""
|
|
Return establishment id of establishment location
|
|
"""
|
|
return self.id
|
|
|
|
@property
|
|
def wines(self):
|
|
"""Return list products with type wine"""
|
|
return self.products.wines()
|
|
|
|
|
|
class EstablishmentNoteQuerySet(models.QuerySet):
|
|
"""QuerySet for model EstablishmentNote."""
|
|
|
|
|
|
class EstablishmentNote(ProjectBaseMixin):
|
|
"""Note model for Establishment entity."""
|
|
old_id = models.PositiveIntegerField(null=True, blank=True)
|
|
text = models.TextField(verbose_name=_('text'))
|
|
establishment = models.ForeignKey(Establishment, on_delete=models.PROTECT,
|
|
related_name='establishment_notes',
|
|
verbose_name=_('establishment'))
|
|
user = models.ForeignKey('account.User', on_delete=models.PROTECT,
|
|
null=True,
|
|
related_name='establishment_notes',
|
|
verbose_name=_('author'))
|
|
|
|
objects = EstablishmentNoteQuerySet.as_manager()
|
|
|
|
class Meta:
|
|
"""Meta class."""
|
|
verbose_name_plural = _('product note')
|
|
verbose_name = _('product notes')
|
|
|
|
|
|
class Position(BaseAttributes, TranslatedFieldsMixin):
|
|
"""Position model."""
|
|
|
|
STR_FIELD_NAME = 'name'
|
|
|
|
name = TJSONField(blank=True, null=True, default=None, verbose_name=_('Description'),
|
|
help_text='{"en":"some text"}')
|
|
|
|
priority = models.IntegerField(unique=True, null=True, default=None)
|
|
|
|
index_name = models.CharField(max_length=255, db_index=True, unique=True,
|
|
null=True, verbose_name=_('Index name'))
|
|
|
|
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'),
|
|
null=True, blank=True)
|
|
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'))
|
|
# old_id = affiliations_id
|
|
old_id = models.IntegerField(verbose_name=_('Old id'), null=True, blank=True)
|
|
|
|
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', related_query_name='employees')
|
|
tags = models.ManyToManyField('tag.Tag', related_name='employees',
|
|
verbose_name=_('Tags'))
|
|
# old_id = profile_id
|
|
old_id = models.IntegerField(verbose_name=_('Old id'), null=True, blank=True)
|
|
|
|
class Meta:
|
|
"""Meta class."""
|
|
|
|
verbose_name = _('Employee')
|
|
verbose_name_plural = _('Employees')
|
|
|
|
|
|
class EstablishmentScheduleQuerySet(models.QuerySet):
|
|
"""QuerySet for model EstablishmentSchedule"""
|
|
|
|
|
|
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(TranslatedFieldsMixin, 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(TranslatedFieldsMixin, models.Model):
|
|
"""Plate model."""
|
|
STR_FIELD_NAME = 'name'
|
|
|
|
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'), default=False)
|
|
currency = models.ForeignKey(
|
|
'main.Currency', verbose_name=_('currency'), on_delete=models.CASCADE,
|
|
blank=True, null=True, default=None)
|
|
currency_code = models.CharField(
|
|
_('currency code'), max_length=250, blank=True, null=True, default=None)
|
|
|
|
menu = models.ForeignKey(
|
|
'establishment.Menu', verbose_name=_('menu'), on_delete=models.CASCADE)
|
|
|
|
@property
|
|
def establishment_id(self):
|
|
return self.menu.establishment.id
|
|
|
|
class Meta:
|
|
verbose_name = _('plate')
|
|
verbose_name_plural = _('plates')
|
|
|
|
|
|
class Menu(TranslatedFieldsMixin, BaseAttributes):
|
|
"""Menu model."""
|
|
|
|
STR_FIELD_NAME = 'category'
|
|
|
|
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 SocialNetwork(models.Model):
|
|
old_id = models.PositiveIntegerField(_('old id'), blank=True, null=True, default=None)
|
|
establishment = models.ForeignKey(
|
|
'Establishment', verbose_name=_('establishment'),
|
|
related_name='socials', on_delete=models.CASCADE)
|
|
title = models.CharField(_('title'), max_length=255)
|
|
url = models.URLField(_('URL'), max_length=255)
|
|
|
|
class Meta:
|
|
verbose_name = _('social network')
|
|
verbose_name_plural = _('social networks')
|
|
|
|
def __str__(self):
|
|
return self.title
|
|
|
|
|
|
class RatingStrategyManager(models.Manager):
|
|
"""Extended manager for RatingStrategy."""
|
|
|
|
def get_toque_number(self, country, public_mark):
|
|
"""Get toque number for country and public_mark."""
|
|
qs = self.model.objects.by_country(country)
|
|
if not qs.exists():
|
|
qs = self.model.objects.by_country(None)
|
|
obj = qs.for_public_mark(public_mark).first()
|
|
if obj:
|
|
return obj.toque_number
|
|
return 0
|
|
|
|
|
|
class RatingStrategyQuerySet(models.QuerySet):
|
|
"""Extended queryset for RatingStrategy."""
|
|
|
|
def by_country(self, country):
|
|
"""Filter by country."""
|
|
return self.filter(country=country)
|
|
|
|
def with_country(self, switcher=True):
|
|
"""With country."""
|
|
return self.exclude(country__isnull=switcher)
|
|
|
|
def for_public_mark(self, public_mark):
|
|
"""Filter for value."""
|
|
return self.filter(public_mark_min_value__lte=public_mark,
|
|
public_mark_max_value__gte=public_mark)
|
|
|
|
|
|
class RatingStrategy(ProjectBaseMixin):
|
|
"""Rating Strategy model."""
|
|
|
|
TOQUE_NUMBER_CHOICES = (
|
|
(1, _('One')),
|
|
(2, _('Two')),
|
|
(3, _('Three')),
|
|
(4, _('Four')),
|
|
(5, _('Five')),
|
|
)
|
|
|
|
country = models.ForeignKey('location.Country', null=True, blank=True,
|
|
default=None, on_delete=models.CASCADE,
|
|
verbose_name=_('Country'))
|
|
toque_number = models.IntegerField(choices=TOQUE_NUMBER_CHOICES)
|
|
public_mark_min_value = models.IntegerField()
|
|
public_mark_max_value = models.IntegerField()
|
|
|
|
objects = RatingStrategyManager.from_queryset(RatingStrategyQuerySet)()
|
|
|
|
class Meta:
|
|
"""Meta class."""
|
|
|
|
verbose_name = _('Rating strategy')
|
|
verbose_name_plural = _('Rating strategy')
|
|
unique_together = ('country', 'toque_number')
|
|
|
|
def __str__(self):
|
|
return f'{self.country.code if self.country else "Other country"}. ' \
|
|
f'"{self.toque_number}": {self.public_mark_min_value}-' \
|
|
f'{self.public_mark_max_value}' |