"""Location app models.""" from django.conf import settings from django.contrib.gis.db import models from django.db.models.signals import post_save from django.db.transaction import on_commit from django.dispatch import receiver from django.utils.translation import gettext_lazy as _ from functools import reduce from typing import List from translation.models import Language from utils.models import (ProjectBaseMixin, SVGImageMixin, TJSONField, TranslatedFieldsMixin, get_current_locale, IntermediateGalleryModelMixin, GalleryMixin) class CountryQuerySet(models.QuerySet): """Country queryset.""" def active(self, switcher=True): """Filter only active users.""" return self.filter(is_active=switcher) class Country(TranslatedFieldsMixin, SVGImageMixin, ProjectBaseMixin): """Country model.""" STR_FIELD_NAME = 'name' TWELVE_HOURS_FORMAT_COUNTRIES = [ 'ca', # Canada 'au', # Australia 'us', # USA 'nz', # New Zealand ] name = TJSONField(null=True, blank=True, default=None, verbose_name=_('Name'), help_text='{"en-GB":"some text"}') code = models.CharField(max_length=255, unique=True, verbose_name=_('Code')) low_price = models.IntegerField(default=25, verbose_name=_('Low price')) high_price = models.IntegerField(default=50, verbose_name=_('High price')) languages = models.ManyToManyField(Language, verbose_name=_('Languages')) is_active = models.BooleanField(_('is active'), default=True) old_id = models.IntegerField(null=True, blank=True, default=None) objects = CountryQuerySet.as_manager() @property def time_format(self): if self.code.lower() not in self.TWELVE_HOURS_FORMAT_COUNTRIES: return 'HH:mm' return 'hh:mmA' @property def country_id(self): return self.id class Meta: """Meta class.""" verbose_name_plural = _('Countries') verbose_name = _('Country') def __str__(self): str_name = self.code if isinstance(self.name, dict): translated_name = self.name.get(get_current_locale()) if translated_name: str_name = translated_name return str_name class RegionQuerySet(models.QuerySet): """QuerySet for model Region.""" def without_parent_region(self, switcher: bool = True): """Filter regions by parent region.""" return self.filter(parent_region__isnull=switcher) def by_region_id(self, region_id): """Filter regions by region id.""" return self.filter(id=region_id) def by_sub_region_id(self, sub_region_id): """Filter sub regions by sub region id.""" return self.filter(parent_region_id=sub_region_id) def sub_regions_by_region_id(self, region_id): """Filter regions by sub region id.""" return self.filter(parent_region_id=region_id) class Region(models.Model): """Region model.""" name = models.CharField(_('name'), max_length=250) code = models.CharField(_('code'), max_length=250) parent_region = models.ForeignKey( 'self', verbose_name=_('parent region'), null=True, blank=True, default=None, on_delete=models.CASCADE) country = models.ForeignKey( Country, verbose_name=_('country'), on_delete=models.CASCADE) old_id = models.IntegerField(null=True, blank=True, default=None) objects = RegionQuerySet.as_manager() class Meta: """Meta class.""" verbose_name_plural = _('regions') verbose_name = _('region') def __str__(self): return self.name class CityQuerySet(models.QuerySet): """Extended queryset for City model.""" def _generic_search(self, value, filter_fields_names: List[str]): """Generic method for searching value in specified fields""" filters = [ {f'{field}__icontains': value} for field in filter_fields_names ] return self.filter(reduce(lambda x, y: x | y, [models.Q(**i) for i in filters])) def search_by_name(self, value): """Search by name or last_name.""" return self._generic_search(value, ['name', 'code', 'postal_code']) def by_country_code(self, code): """Return establishments by country code""" return self.filter(country__code=code) class City(GalleryMixin, models.Model): """Region model.""" name = models.CharField(_('name'), max_length=250) code = models.CharField(_('code'), max_length=250) region = models.ForeignKey( Region, verbose_name=_('parent region'), on_delete=models.CASCADE) country = models.ForeignKey( Country, verbose_name=_('country'), on_delete=models.CASCADE) postal_code = models.CharField( _('postal code'), max_length=10, default='', help_text=_('Ex.: 350018')) is_island = models.BooleanField(_('is island'), default=False) old_id = models.IntegerField(null=True, blank=True, default=None) gallery = models.ManyToManyField('gallery.Image', through='CityGallery') objects = CityQuerySet.as_manager() class Meta: verbose_name_plural = _('cities') verbose_name = _('city') def __str__(self): return self.name class CityGallery(IntermediateGalleryModelMixin): """Gallery for model City.""" city = models.ForeignKey(City, null=True, related_name='city_gallery', on_delete=models.CASCADE, verbose_name=_('city')) image = models.ForeignKey('gallery.Image', null=True, related_name='city_gallery', on_delete=models.CASCADE, verbose_name=_('image')) class Meta: """CityGallery meta class.""" verbose_name = _('city gallery') verbose_name_plural = _('city galleries') unique_together = (('city', 'is_main'), ('city', 'image')) class Address(models.Model): """Address model.""" city = models.ForeignKey(City, verbose_name=_('city'), on_delete=models.CASCADE) street_name_1 = models.CharField( _('street name 1'), max_length=500, blank=True, default='') street_name_2 = models.CharField( _('street name 2'), max_length=500, blank=True, default='') number = models.IntegerField(_('number'), blank=True, default=0) postal_code = models.CharField( _('postal code'), max_length=10, blank=True, default='', help_text=_('Ex.: 350018')) coordinates = models.PointField( _('Coordinates'), blank=True, null=True, default=None) old_id = models.IntegerField(null=True, blank=True, default=None) class Meta: """Meta class.""" verbose_name_plural = _('Address') verbose_name = _('Address') def __str__(self): return f'{self.id}: {self.get_street_name()[:50]}' def get_street_name(self): return self.street_name_1 or self.street_name_2 @property def latitude(self): return self.coordinates.y if self.coordinates else float(0) @property def longitude(self): return self.coordinates.x if self.coordinates else float(0) @property def location_field_indexing(self): return {'lat': self.latitude, 'lon': self.longitude} @property def country_id(self): return self.city.country_id class WineRegionQuerySet(models.QuerySet): """Wine region queryset.""" def with_sub_region_related(self): return self.prefetch_related('wine_sub_region') def having_wines(self, value=True): """Return qs with regions, which have any wine related to them""" return self.exclude(wineoriginaddress__product__isnull=value) class WineRegion(TranslatedFieldsMixin, models.Model): """Wine region model.""" name = models.CharField(_('name'), max_length=255) country = models.ForeignKey(Country, on_delete=models.PROTECT, blank=True, null=True, default=None, verbose_name=_('country')) coordinates = models.PointField( _('Coordinates'), blank=True, null=True, default=None) old_id = models.PositiveIntegerField(_('old id'), default=None, blank=True, null=True) description = TJSONField(blank=True, null=True, default=None, verbose_name=_('description'), help_text='{"en-GB":"some text"}') tags = models.ManyToManyField('tag.Tag', blank=True, related_name='wine_regions', help_text='attribute from legacy db') objects = WineRegionQuerySet.as_manager() class Meta: """Meta class.""" verbose_name_plural = _('wine regions') verbose_name = _('wine region') def __str__(self): """Override dunder method.""" return self.name class WineSubRegionQuerySet(models.QuerySet): """Wine sub region QuerySet.""" class WineSubRegion(models.Model): """Wine sub region model.""" name = models.CharField(_('name'), max_length=255) wine_region = models.ForeignKey(WineRegion, on_delete=models.PROTECT, related_name='wine_sub_region', verbose_name=_('wine sub region')) old_id = models.PositiveIntegerField(_('old id'), default=None, blank=True, null=True) objects = WineSubRegionQuerySet.as_manager() class Meta: """Meta class.""" verbose_name_plural = _('wine sub regions') verbose_name = _('wine sub region') def __str__(self): """Override dunder method.""" return self.name class WineVillageQuerySet(models.QuerySet): """Wine village QuerySet.""" class WineVillage(models.Model): """ Wine village. Description: Imported from legacy DB. """ name = models.CharField(_('name'), max_length=255) wine_region = models.ForeignKey(WineRegion, on_delete=models.PROTECT, verbose_name=_('wine region')) old_id = models.PositiveIntegerField(_('old id'), default=None, blank=True, null=True) objects = WineVillageQuerySet.as_manager() class Meta: """Meta class.""" verbose_name = _('wine village') verbose_name_plural = _('wine villages') def __str__(self): """Override str dunder.""" return self.name class WineOriginAddressMixin(models.Model): """Model for wine origin address.""" wine_region = models.ForeignKey('location.WineRegion', on_delete=models.CASCADE, verbose_name=_('wine region')) wine_sub_region = models.ForeignKey('location.WineSubRegion', on_delete=models.CASCADE, blank=True, null=True, default=None, verbose_name=_('wine sub region')) class Meta: """Meta class.""" abstract = True class EstablishmentWineOriginAddressQuerySet(models.QuerySet): """QuerySet for EstablishmentWineOriginAddress model.""" class EstablishmentWineOriginAddress(WineOriginAddressMixin): """Establishment wine origin address model.""" establishment = models.ForeignKey('establishment.Establishment', on_delete=models.CASCADE, related_name='wine_origins', verbose_name=_('product')) objects = EstablishmentWineOriginAddressQuerySet.as_manager() class Meta: """Meta class.""" verbose_name = _('establishment wine origin address') verbose_name_plural = _('establishment wine origin addresses') class WineOriginAddressQuerySet(models.QuerySet): """QuerySet for WineOriginAddress model.""" class WineOriginAddress(WineOriginAddressMixin): """Wine origin address model.""" product = models.ForeignKey('product.Product', on_delete=models.CASCADE, related_name='wine_origins', verbose_name=_('product')) objects = WineOriginAddressQuerySet.as_manager() class Meta: """Meta class.""" verbose_name = _('wine origin address') verbose_name_plural = _('wine origin addresses') # todo: Make recalculate price levels @receiver(post_save, sender=Country) def run_recalculate_price_levels(sender, instance, **kwargs): from establishment.tasks import recalculate_price_levels_by_country if settings.USE_CELERY: on_commit(lambda: recalculate_price_levels_by_country.delay( country_id=instance.id)) else: on_commit(lambda: recalculate_price_levels_by_country( country_id=instance.id))