gault-millau/apps/product/models.py

299 lines
10 KiB
Python

"""Product app models."""
from django.db import models
from django.contrib.gis.db import models as gis_models
from django.contrib.contenttypes import fields as generic
from django.core.exceptions import ValidationError
from django.contrib.postgres.fields import JSONField
from django.utils.translation import gettext_lazy as _
from utils.models import (BaseAttributes, ProjectBaseMixin,
TranslatedFieldsMixin, TJSONField)
class ProductType(TranslatedFieldsMixin, ProjectBaseMixin):
"""ProductType model."""
STR_FIELD_NAME = 'name'
# INDEX NAME CHOICES
FOOD = 'food'
WINE = 'wine'
LIQUOR = 'liquor'
INDEX_NAME_TYPES = (
(FOOD, _('Food')),
(WINE, _('Wine')),
(LIQUOR, _('Liquor')),
)
name = TJSONField(blank=True, null=True, default=None,
verbose_name=_('Name'), help_text='{"en-GB":"some text"}')
index_name = models.CharField(max_length=50, choices=INDEX_NAME_TYPES,
unique=True, db_index=True,
verbose_name=_('Index name'))
use_subtypes = models.BooleanField(_('Use subtypes'), default=True)
class Meta:
"""Meta class."""
verbose_name = _('Product type')
verbose_name_plural = _('Product types')
class ProductSubType(TranslatedFieldsMixin, ProjectBaseMixin):
"""ProductSubtype model."""
STR_FIELD_NAME = 'name'
# INDEX NAME CHOICES
RUM = 'rum'
OTHER = 'other'
EXTRA_BRUT = 'extra brut'
BRUT = 'brut'
BRUT_NATURE = 'brut nature'
DEMI_SEC = 'demi-sec'
EXTRA_DRY = 'Extra Dry'
DOSAGE_ZERO = 'dosage zero'
SEC = 'sec'
DOUX = 'doux'
MOELLEUX= 'moelleux'
INDEX_NAME_TYPES = (
(RUM, _('Rum')),
(OTHER, _('Other')),
(EXTRA_BRUT, _('extra brut')),
(BRUT, _('brut')),
(BRUT_NATURE, _('brut nature')),
(DEMI_SEC, _('demi-sec')),
(EXTRA_DRY, _('Extra Dry')),
(DOSAGE_ZERO, _('dosage zero')),
(SEC, _('sec')),
(DOUX, _('doux')),
(MOELLEUX, _('moelleux'))
)
product_type = models.ForeignKey(ProductType, on_delete=models.CASCADE,
related_name='subtypes',
verbose_name=_('Product type'))
name = TJSONField(blank=True, null=True, default=None,
verbose_name=_('Name'), help_text='{"en-GB":"some text"}')
index_name = models.CharField(max_length=50, choices=INDEX_NAME_TYPES,
unique=True, db_index=True,
verbose_name=_('Index name'))
class Meta:
"""Meta class."""
verbose_name = _('Product subtype')
verbose_name_plural = _('Product subtypes')
def clean_fields(self, exclude=None):
if not self.product_type.use_subtypes:
raise ValidationError(_('Product type is not use subtypes.'))
class ProductManager(models.Manager):
"""Extended manager for Product model."""
class ProductQuerySet(models.QuerySet):
"""Product queryset."""
def with_base_related(self):
return self.select_related('product_type', 'establishment') \
.prefetch_related('product_type__subtypes', 'country')
def common(self):
return self.filter(category=self.model.COMMON)
def online(self):
return self.filter(category=self.model.ONLINE)
def wines(self):
return self.filter(type__index_name=ProductType.WINE)
def by_product_type(self, product_type: str):
"""Filter by type."""
return self.filter(product_type__index_name=product_type)
def by_product_subtype(self, product_subtype: str):
"""Filter by subtype."""
return self.filter(subtypes__index_name=product_subtype)
class Product(TranslatedFieldsMixin, BaseAttributes):
"""Product models."""
COMMON = 0
ONLINE = 1
CATEGORY_CHOICES = (
(COMMON, _('Common')),
(ONLINE, _('Online')),
)
category = models.PositiveIntegerField(choices=CATEGORY_CHOICES,
default=COMMON)
name = models.CharField(max_length=255,
default=None, null=True,
verbose_name=_('name'))
description = TJSONField(_('Description'), null=True, blank=True,
default=None, help_text='{"en-GB":"some text"}')
characteristics = models.ManyToManyField('Characteristic',
verbose_name=_('characteristics'))
country = models.ManyToManyField('location.Country',
verbose_name=_('Country'))
available = models.BooleanField(_('Available'), default=True)
product_type = models.ForeignKey(ProductType, on_delete=models.PROTECT,
related_name='products', verbose_name=_('Type'))
subtypes = models.ManyToManyField(ProductSubType, blank=True,
related_name='products',
verbose_name=_('Subtypes'))
establishment = models.ForeignKey('establishment.Establishment', on_delete=models.PROTECT,
blank=True, null=True,
related_name='products',
verbose_name=_('establishment'))
public_mark = models.PositiveIntegerField(blank=True, null=True, default=None,
verbose_name=_('public mark'),)
wine_region = models.ForeignKey('location.WineRegion', on_delete=models.PROTECT,
related_name='wines',
blank=True, null=True,
verbose_name=_('wine region'))
wine_standard = models.ForeignKey('product.WineStandard', on_delete=models.PROTECT,
blank=True, null=True,
verbose_name=_('wine standard'))
wine_village = models.ForeignKey('location.WineVillage', on_delete=models.PROTECT,
blank=True, null=True,
verbose_name=_('wine appellation'))
slug = models.SlugField(unique=True, max_length=255, null=True,
verbose_name=_('Establishment slug'))
favorites = generic.GenericRelation(to='favorites.Favorites')
objects = ProductManager.from_queryset(ProductQuerySet)()
class Meta:
"""Meta class."""
verbose_name = _('Product')
verbose_name_plural = _('Products')
def __str__(self):
"""Override str dunder method."""
return self.name
def clean_fields(self, exclude=None):
super().clean_fields(exclude=exclude)
if self.product_type.index_name == ProductType.WINE and not self.wine_region:
raise ValidationError(_('wine_region field must be specified.'))
if not self.product_type.index_name == ProductType.WINE and self.wine_region:
raise ValidationError(_('wine_region field must not be specified.'))
if (self.wine_region and self.wine_appellation) and \
self.wine_appellation not in self.wine_region.appellations.all():
raise ValidationError(_('Wine appellation not exists in wine region.'))
class OnlineProductManager(ProductManager):
"""Extended manger for OnlineProduct model."""
def get_queryset(self):
"""Overridden get_queryset method."""
return super().get_queryset().online()
class OnlineProduct(Product):
"""Online product."""
objects = OnlineProductManager.from_queryset(ProductQuerySet)()
class Meta:
"""Meta class."""
proxy = True
verbose_name = _('Online product')
verbose_name_plural = _('Online products')
class Unit(models.Model):
"""Product unit model."""
name = models.CharField(max_length=255,
verbose_name=_('name'))
value = models.CharField(max_length=255,
verbose_name=_('value'))
class Meta:
"""Meta class."""
verbose_name = _('unit')
verbose_name_plural = _('units')
def __str__(self):
"""Overridden dunder method."""
return self.name
class Characteristic(TranslatedFieldsMixin, models.Model):
"""Characteristic model."""
STR_FIELD_NAME = 'name'
name = TJSONField(_('name'), null=True, blank=True,
help_text='{"en-GB":"some text"}')
value = models.CharField(max_length=255, null=True, blank=True,
verbose_name=_('value'))
# unit = models.ForeignKey('Unit', on_delete=models.PROTECT)
priority = models.IntegerField(unique=True, null=True, default=None)
class Meta:
"""Meta model."""
verbose_name = _('characteristic')
verbose_name_plural = _('characteristics')
class WineStandardQuerySet(models.QuerySet):
"""Wine appellation queryset."""
class WineStandard(models.Model):
"""Wine standard model."""
APPELLATION = 'Appellation'
CLASSIFICATION = 'Classification'
WINEQUALITY = 'WineQuality'
YARDCLASSIFICATION = 'YardClassification'
STANDARDS = (
(APPELLATION, _('Appellation')),
(CLASSIFICATION, _('Classification')),
(WINEQUALITY, _('Wine quality')),
(YARDCLASSIFICATION, _('Yard classification')),
)
name = models.CharField(_('name'), max_length=255)
standard_type = models.CharField(max_length=30, choices=STANDARDS,
verbose_name=_('standard type'))
coordinates = gis_models.PointField(
_('Coordinates'), blank=True, null=True, default=None)
objects = WineStandardQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name_plural = _('wine standards')
verbose_name = _('wine standard')
class WineClassificationQuerySet(models.QuerySet):
"""Wine classification QuerySet."""
class WineClassification(models.Model):
"""Wine classification model."""
name = models.CharField(_('name'), max_length=255)
standard = models.ForeignKey(WineStandard, on_delete=models.PROTECT,
verbose_name=_('standard'))
objects = WineClassificationQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name = _('wine classification')
verbose_name_plural = _('wine classifications')