457 lines
16 KiB
Python
457 lines
16 KiB
Python
"""Location app models."""
|
|
from functools import reduce
|
|
from json import dumps
|
|
from typing import List
|
|
|
|
from django.conf import settings
|
|
from django.contrib.gis.db import models
|
|
from django.contrib.postgres.fields import ArrayField
|
|
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 translation.models import Language
|
|
from utils.models import (ProjectBaseMixin, SVGImageMixin, TJSONField,
|
|
TranslatedFieldsMixin, get_current_locale,
|
|
IntermediateGalleryModelMixin)
|
|
|
|
|
|
class CountryQuerySet(models.QuerySet):
|
|
"""Country queryset."""
|
|
def active(self, switcher=True):
|
|
"""Filter only active users."""
|
|
return self.filter(is_active=switcher)
|
|
|
|
def by_country_code(self, code: str):
|
|
"""Filter QuerySet by country code."""
|
|
return self.filter(code__iexact=code)
|
|
|
|
def aggregate_country_codes(self):
|
|
"""Aggregate country codes."""
|
|
calling_codes = list(
|
|
self.model.objects.exclude(calling_code__isnull=True)
|
|
.exclude(code__iexact='aa')
|
|
.distinct()
|
|
.values_list('calling_code', flat=True)
|
|
)
|
|
# extend country calling code hardcoded codes
|
|
calling_codes.extend(settings.CALLING_CODES_ANTILLES_GUYANE_WEST_INDIES)
|
|
return [self.model.CALLING_NUMBER_MASK % i for i in calling_codes]
|
|
|
|
|
|
class Country(TranslatedFieldsMixin,
|
|
SVGImageMixin, ProjectBaseMixin):
|
|
"""Country model."""
|
|
|
|
STR_FIELD_NAME = 'name'
|
|
CALLING_NUMBER_MASK = '+%s'
|
|
|
|
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, 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)
|
|
calling_code = models.CharField(max_length=5,
|
|
blank=True, null=True, default=None,
|
|
verbose_name=_('calling code'),
|
|
help_text='i.e. "1-809"')
|
|
mysql_ids = ArrayField(models.IntegerField(), blank=True, null=True)
|
|
|
|
objects = CountryQuerySet.as_manager()
|
|
|
|
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
|
|
|
|
@property
|
|
def time_format(self):
|
|
if self.code.lower() not in self.TWELVE_HOURS_FORMAT_COUNTRIES:
|
|
return 'HH:mm'
|
|
return 'hh:mmA'
|
|
|
|
@property
|
|
def display_calling_code(self) -> list:
|
|
"""Return formatted calling code."""
|
|
array = []
|
|
if self.code and self.calling_code:
|
|
# hardcoded calling numbers for Antilles Guyane West Indies islands.
|
|
if self.code.lower() == 'aa':
|
|
array.extend([self.CALLING_NUMBER_MASK % i
|
|
for i in set(settings.CALLING_CODES_ANTILLES_GUYANE_WEST_INDIES)])
|
|
else:
|
|
array.append(self.CALLING_NUMBER_MASK % self.calling_code)
|
|
return array
|
|
|
|
@property
|
|
def default_calling_code(self) -> str:
|
|
"""Return default calling code based on phone number."""
|
|
if self.code and self.calling_code:
|
|
# hardcoded default calling number for Antilles Guyane West Indies islands.
|
|
if self.code.lower() == 'aa':
|
|
return (self.CALLING_NUMBER_MASK %
|
|
settings.DEFAULT_CALLING_CODE_ANTILLES_GUYANE_WEST_INDIES)
|
|
return self.CALLING_NUMBER_MASK % self.calling_code
|
|
|
|
|
|
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)
|
|
mysql_ids = ArrayField(models.IntegerField(), blank=True, null=True)
|
|
|
|
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)
|
|
|
|
def with_base_related(self):
|
|
return self.prefetch_related('country', 'region', 'region__country')
|
|
|
|
|
|
class City(models.Model, TranslatedFieldsMixin):
|
|
"""Region model."""
|
|
name = TJSONField(default=None, null=True, help_text='{"en-GB":"some city name"}',
|
|
verbose_name=_('City name json'))
|
|
code = models.CharField(_('code'), max_length=250)
|
|
region = models.ForeignKey(Region, on_delete=models.CASCADE,
|
|
blank=True, null=True,
|
|
verbose_name=_('parent region'))
|
|
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)
|
|
|
|
map1 = models.CharField(max_length=255, blank=True, null=True)
|
|
map2 = models.CharField(max_length=255, blank=True, null=True)
|
|
map_ref = models.CharField(max_length=255, blank=True, null=True)
|
|
situation = models.CharField(max_length=255, blank=True, null=True)
|
|
image = models.ForeignKey('gallery.Image', on_delete=models.SET_NULL,
|
|
blank=True, null=True, default=None,
|
|
related_name='city_image',
|
|
verbose_name=_('image instance of model Image'))
|
|
|
|
mysql_id = models.IntegerField(blank=True, null=True, default=None)
|
|
|
|
objects = CityQuerySet.as_manager()
|
|
|
|
class Meta:
|
|
verbose_name_plural = _('cities')
|
|
verbose_name = _('city')
|
|
|
|
def __str__(self):
|
|
return f'{self.id}: {self.code}'
|
|
|
|
@property
|
|
def name_dumped(self):
|
|
"""Used for indexing as string"""
|
|
return dumps(self.name)
|
|
|
|
@property
|
|
def image_object(self):
|
|
"""Return image object."""
|
|
return self.image.image if self.image else None
|
|
|
|
@property
|
|
def crop_image(self):
|
|
if hasattr(self, 'image') and hasattr(self, '_meta'):
|
|
if self.image:
|
|
image_property = {
|
|
'id': self.image.id,
|
|
'title': self.image.title,
|
|
'original_url': self.image.image.url,
|
|
'orientation_display': self.image.get_orientation_display(),
|
|
'auto_crop_images': {},
|
|
}
|
|
crop_parameters = [p for p in settings.SORL_THUMBNAIL_ALIASES
|
|
if p.startswith(self._meta.model_name.lower())]
|
|
for crop in crop_parameters:
|
|
image_property['auto_crop_images'].update(
|
|
{crop: self.image.get_image_url(crop)}
|
|
)
|
|
return image_property
|
|
|
|
|
|
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)
|
|
district_name = models.CharField(
|
|
_('District name'), max_length=500, blank=True, default=None, null=True)
|
|
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
|
|
|
|
@property
|
|
def full_address(self):
|
|
full_address = self.get_street_name()
|
|
if self.number and int(self.number):
|
|
full_address = f'{self.number} {self.get_street_name()}'
|
|
return full_address
|
|
|
|
|
|
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))
|