gault-millau/apps/collection/models.py
2020-02-04 18:42:03 +03:00

1078 lines
42 KiB
Python

import re
from django.conf import settings
from django.contrib.contenttypes.fields import ContentType
from django.contrib.postgres.fields import JSONField
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.utils.translation import gettext_lazy as _
from mptt.models import MPTTModel, TreeForeignKey
from slugify import slugify
from collection import tasks
from establishment.models import Establishment
from location.models import Country, Region, WineRegion, WineSubRegion, City
from product.models import Product
from review.models import Review
from translation.models import Language
from utils.methods import transform_into_section_name
from utils.models import (
ProjectBaseMixin, TJSONField, TranslatedFieldsMixin,
URLImageMixin, IntermediateGalleryModelMixin
)
from utils.querysets import RelatedObjectsCountMixin
# Mixins
class CollectionNameMixin(models.Model):
"""CollectionName mixin"""
name = models.CharField(_('name'), max_length=250)
class Meta:
"""Meta class"""
abstract = True
class CollectionDateMixin(models.Model):
"""CollectionDate mixin"""
start = models.DateTimeField(blank=True, null=True, default=None,
verbose_name=_('start'))
end = models.DateTimeField(blank=True, null=True, default=None,
verbose_name=_('end'))
class Meta:
"""Meta class"""
abstract = True
# Models
class CollectionQuerySet(RelatedObjectsCountMixin):
"""QuerySet for model Collection"""
def by_country_code(self, code):
"""Filter collection by country code."""
return self.filter(country__code=code)
def published(self):
"""Returned only published collection"""
return self.filter(is_publish=True)
def with_base_related(self):
"""Select relate objects"""
return self.select_related('country')
class Collection(ProjectBaseMixin, CollectionDateMixin,
TranslatedFieldsMixin, URLImageMixin):
"""Collection model."""
STR_FIELD_NAME = 'name'
ORDINARY = 0 # Ordinary collection
POP = 1 # POP collection
COLLECTION_TYPES = (
(ORDINARY, _('Ordinary')),
(POP, _('Pop')),
)
name = TJSONField(verbose_name=_('name'),
help_text='{"en-GB":"some text"}')
collection_type = models.PositiveSmallIntegerField(choices=COLLECTION_TYPES,
default=ORDINARY,
verbose_name=_('Collection type'))
is_publish = models.BooleanField(
default=False, verbose_name=_('Publish status'))
on_top = models.BooleanField(
default=False, verbose_name=_('Position on top'))
country = models.ForeignKey(
'location.Country', verbose_name=_('country'), on_delete=models.CASCADE)
block_size = JSONField(
_('collection block properties'), null=True, blank=True,
default=None, help_text='{"width": "250px", "height":"250px"}')
description = TJSONField(
_('description'), null=True, blank=True,
default=None, help_text='{"en-GB":"some text"}')
slug = models.SlugField(max_length=255, unique=True,
verbose_name=_('Collection slug'), editable=True, null=True)
old_id = models.IntegerField(null=True, blank=True)
rank = models.IntegerField(null=True, default=None)
objects = CollectionQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name = _('collection')
verbose_name_plural = _('collections')
@property
def _related_objects(self) -> list:
"""Return list of related objects."""
related_objects = []
instances = self._meta.related_objects
# get related objects
for related_object in instances.with_base_related() if hasattr(self, 'with_base_related') else instances:
related_objects.append(related_object)
return related_objects
@property
def count_related_objects(self) -> int:
"""Return count of related objects."""
counter = 0
# count of related objects
for related_object in [related_object.name for related_object in self._related_objects]:
counter += getattr(self, f'{related_object}').count()
return counter
@property
def related_object_names(self) -> list:
"""Return related object names."""
raw_objects = []
for related_object in [related_object.name for related_object in self._related_objects]:
instances = getattr(self, f'{related_object}')
if instances.exists():
for instance in instances.all():
raw_object = (instance.id, instance.establishment_type.index_name,
instance.slug) if \
hasattr(instance, 'slug') else (instance.id, None, None)
raw_objects.append(raw_object)
# parse slugs
related_objects = []
object_names = set()
re_pattern = r'[\w]+'
for object_id, object_type, raw_name, in raw_objects:
result = re.findall(re_pattern, raw_name)
if result:
name = ' '.join(result).capitalize()
if name not in object_names:
related_objects.append({
'id': object_id,
'establishment_type': object_type,
'name': name
})
object_names.add(name)
return related_objects
def save(self, *args, **kwargs):
if not self.pk:
slugify_slug = slugify(
next(iter(self.name.values())),
word_boundary=True
)
self.slug = slugify_slug
super(Collection, self).save(*args, **kwargs)
class GuideQuerySet(models.QuerySet):
"""QuerySet for Guide."""
def with_base_related(self):
"""Return QuerySet with related."""
return self.select_related('site', 'guidefilter',)
def with_extended_related(self):
"""Return QuerySet with extended related."""
return (
self.with_base_related()
.prefetch_related(
models.Prefetch(
'guideelement_set',
queryset=GuideElement.objects.select_related(
'guide_element_type',
'establishment',
'review',
'wine_region',
'product',
'city',
'wine_color_section',
'section',
'guide',
'parent',
'label_photo',
)
)
)
)
def by_country_id(self, country_id):
"""Return QuerySet filtered by country id."""
return self.filter(country_json__id__contains=country_id)
def by_country_code(self, country_code):
"""Return QuerySet filtered by country code."""
return self.filter(site__country__code=country_code)
def annotate_in_restaurant_section(self):
"""Annotate flag if GuideElement in RestaurantSectionNode."""
restaurant_guides = models.Subquery(
self.filter(
guideelement__guide_element_type__name='EstablishmentNode',
guideelement__parent__guide_element_type__name='RestaurantSectionNode',
).values_list('id', flat=True).distinct()
)
return self.annotate(
in_restaurant_section=models.Case(
models.When(
id__in=restaurant_guides,
then=True),
default=False,
output_field=models.BooleanField(default=False)
)
)
def annotate_in_shop_section(self):
"""Annotate flag if GuideElement in ShopSectionNode."""
shop_guides = models.Subquery(
self.filter(
guideelement__guide_element_type__name='EstablishmentNode',
guideelement__parent__guide_element_type__name='ShopSectionNode',
).values_list('guideelement__id', flat=True).distinct()
)
return self.annotate(
in_shop_section=models.Case(
models.When(
id__in=shop_guides,
then=True),
default=False,
output_field=models.BooleanField(default=False)
)
)
def annotate_restaurant_counter(self):
"""Return QuerySet with annotated field - restaurant_counter."""
return self.annotate_in_restaurant_section().annotate(
restaurant_counter=models.Count(
'guideelement__establishment',
filter=models.Q(in_restaurant_section=True) &
models.Q(guideelement__parent_id__isnull=True),
distinct=True
)
)
def annotate_shop_counter(self):
"""Return QuerySet with annotated field - shop_counter."""
return self.annotate_in_shop_section().annotate(
shop_counter=models.Count(
'guideelement__establishment',
filter=models.Q(in_shop_section=True) &
models.Q(guideelement__parent_id__isnull=True),
distinct=True
)
)
def annotate_wine_counter(self):
"""Return QuerySet with annotated field - shop_counter."""
return self.annotate_in_restaurant_section().annotate(
wine_counter=models.Count(
'guideelement__product',
filter=models.Q(guideelement__guide_element_type__name='WineNode') &
models.Q(guideelement__parent_id__isnull=False),
distinct=True
)
)
def annotate_present_objects_counter(self):
"""Return QuerySet with annotated field - present_objects_counter."""
return (
self.annotate(
present_objects_counter=(
models.F('restaurant_counter') +
models.F('shop_counter') +
models.F('wine_counter')
)
)
)
def annotate_counters(self):
return (
self.annotate_restaurant_counter()
.annotate_shop_counter()
.annotate_wine_counter()
.annotate_present_objects_counter()
)
def by_establishment_id(self, establishment_id: int):
return self.filter(guideelement__establishment=establishment_id).distinct()
class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin):
"""Guide model."""
BUILT = 0
WAITING = 1
REMOVING = 2
BUILDING = 3
STATE_CHOICES = (
(BUILT, 'built'),
(WAITING, 'waiting'),
(REMOVING, 'removing'),
(BUILDING, 'building'),
)
RESTAURANT = 0
ARTISAN = 1
WINE = 2
GUIDE_TYPE_CHOICES = (
(RESTAURANT, _('Restaurant')),
(ARTISAN, _('Artisan')),
(WINE, _('Wine')),
)
guide_type = models.PositiveSmallIntegerField(choices=GUIDE_TYPE_CHOICES,
default=RESTAURANT,
verbose_name=_('guide type'))
start = models.DateTimeField(null=True,
verbose_name=_('start'))
vintage = models.IntegerField(validators=[MinValueValidator(1900),
MaxValueValidator(2100)],
null=True,
verbose_name=_('guide vintage year'))
slug = models.SlugField(max_length=255, unique=True, null=True,
verbose_name=_('slug'))
site = models.ForeignKey('main.SiteSettings', on_delete=models.SET_NULL,
null=True,
verbose_name=_('site settings'))
state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES,
verbose_name=_('state'))
count_related_objects = models.PositiveIntegerField(
default=0,
help_text=_('* after rebuild guide, refresh count of related guide elements'),
verbose_name=_('count of related guide elements during initialization'))
old_id = models.IntegerField(blank=True, null=True)
objects = GuideQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name = _('guide')
verbose_name_plural = _('guides')
def __str__(self):
"""String method."""
return f'{self.name}'
# todo: for test use, use annotation instead
# @property
# def restaurant_counter_prop(self):
# counter = 0
# root_node = GuideElement.objects.get_root_node(self)
# if root_node:
# descendants = GuideElement.objects.get_root_node(self).get_descendants()
# if descendants:
# restaurant_section_nodes = descendants.filter(
# guide_element_type__name='RestaurantSectionNode')
# counter = descendants.filter(guide_element_type__name='EstablishmentNode',
# parent__in=restaurant_section_nodes,
# parent_id__isnull=True).count()
# return counter
#
# @property
# def shop_counter_prop(self):
# counter = 0
# root_node = GuideElement.objects.get_root_node(self)
# if root_node:
# descendants = GuideElement.objects.get_root_node(self).get_descendants()
# if descendants:
# shop_section_nodes = descendants.filter(
# guide_element_type__name='ShopSectionNode')
# counter = descendants.filter(guide_element_type__name='EstablishmentNode',
# parent__in=shop_section_nodes,
# parent_id__isnull=True).count()
# return counter
#
# @property
# def wine_counter_prop(self):
# counter = 0
# root_node = GuideElement.objects.get_root_node(self)
# if root_node:
# descendants = GuideElement.objects.get_root_node(self).get_descendants()
# if descendants:
# counter = descendants.filter(guide_element_type__name='WineNode',
# parent_id__isnull=True).count()
# return counter
def update_count_related_objects(self, nodes: set = ('EstablishmentNode', 'WineNode')):
"""Update count of related guide element objects."""
root_node = GuideElement.objects.get_root_node(self)
if root_node:
descendants = root_node.get_descendants()
if descendants:
updated_count = descendants.filter(
guide_element_type__name__in=nodes).count()
self.count_related_objects = updated_count
self.save()
def generate_elements(self):
if self.guidefilter:
if self.guide_type in [self.ARTISAN, self.RESTAURANT]:
if settings.USE_CELERY:
tasks.generate_establishment_guide_elements.delay(
guide_id=self.id,
filter_set=self.guidefilter.establishment_filter_set,
)
else:
tasks.generate_establishment_guide_elements(
guide_id=self.id,
filter_set=self.guidefilter.establishment_filter_set,
)
elif self.guide_type == self.WINE:
if settings.USE_CELERY:
tasks.generate_product_guide_elements.delay(
guide_id=self.id,
filter_set=self.guidefilter.product_filter_set,
)
else:
tasks.generate_product_guide_elements(
guide_id=self.id,
filter_set=self.guidefilter.product_filter_set,
)
def extend_establishment_guide(self, establishment_id: int):
if self.guide_type in [self.ARTISAN, self.RESTAURANT]:
if settings.USE_CELERY:
tasks.populate_establishment_guide.delay(
guide_id=self.id,
establishment_id=establishment_id,
)
else:
tasks.populate_establishment_guide(
guide_id=self.id,
establishment_id=establishment_id,
)
def remove_establishment(self, establishment_id: int):
if self.guide_type in [self.ARTISAN, self.RESTAURANT]:
if settings.USE_CELERY:
tasks.remove_establishment_guide.delay(
guide_id=self.id,
establishment_id=establishment_id,
)
else:
tasks.remove_establishment_guide(
guide_id=self.id,
establishment_id=establishment_id,
)
def regenerate_elements(self):
# get Root node
root_node = GuideElement.objects.get_root_node(self)
# get all descendants related to this guide and delete it
root_node.get_descendants().delete()
# re-generate elements
self.generate_elements()
# update count elements
self.update_count_related_objects()
def change_state(self, state: int):
self.state = state
self.save()
def export_to_file(self, user_id: int, file_type: str):
if settings.USE_CELERY:
tasks.export_guide.delay(guide_id=self.id, user_id=user_id, file_type=file_type)
else:
tasks.export_guide(guide_id=self.id, user_id=user_id, file_type=file_type)
class AdvertorialQuerySet(models.QuerySet):
"""QuerySet for model Advertorial."""
class Advertorial(ProjectBaseMixin):
"""Guide advertorial model."""
number_of_pages = models.PositiveIntegerField(
verbose_name=_('number of pages'),
help_text=_('the total number of reserved pages'))
right_pages = models.PositiveIntegerField(
verbose_name=_('number of right pages'),
help_text=_('the number of right pages (which are part of total number).'))
guide_element = models.OneToOneField('GuideElement', on_delete=models.CASCADE,
related_name='advertorial',
verbose_name=_('guide element'))
old_id = models.IntegerField(blank=True, null=True)
objects = AdvertorialQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name = _('advertorial')
verbose_name_plural = _('advertorials')
class GuideFilterQuerySet(models.QuerySet):
"""QuerySet for model GuideFilter."""
class GuideFilter(ProjectBaseMixin):
"""Guide filter model."""
establishment_type_json = JSONField(blank=True, null=True,
verbose_name='establishment types')
country_json = JSONField(blank=True, null=True,
verbose_name='countries')
region_json = JSONField(blank=True, null=True,
verbose_name='regions')
sub_region_json = JSONField(blank=True, null=True,
verbose_name='sub regions')
wine_region_json = JSONField(blank=True, null=True,
verbose_name='wine regions')
with_mark = models.BooleanField(default=True,
verbose_name=_('with mark'),
help_text=_('exclude empty marks?'))
locale_json = JSONField(blank=True, null=True,
verbose_name='locales')
max_mark = models.FloatField(verbose_name=_('max mark'),
null=True,
help_text=_('mark under'))
min_mark = models.FloatField(verbose_name=_('min mark'),
null=True,
help_text=_('mark over'))
review_vintage_json = JSONField(verbose_name='review vintage years')
review_state_json = JSONField(blank=True, null=True,
verbose_name='review states')
guide = models.OneToOneField(Guide, on_delete=models.CASCADE,
verbose_name=_('guide'))
old_id = models.IntegerField(blank=True, null=True)
objects = GuideFilterQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name = _('guide filter')
verbose_name_plural = _('guide filters')
def get_value_list(self, json_field: dict,
model: object,
lookup_field: str,
search_field: int = 'id') -> list:
"""
Function to return an array with correct values from ids.
Algorithm:
1 Get values from json_field
2 Try to find model instances by search field and value from json field
3 If instance was found, then put value into array from instance by lookup field
"""
value_list = []
if hasattr(model, 'objects') and json_field:
for value in getattr(json_field, 'get')(search_field):
qs = model.objects.filter(**{search_field: value})
if qs.exists():
value_list.append(getattr(qs.first(), lookup_field))
return value_list
@property
def establishment_types(self):
from establishment.models import EstablishmentType
return self.get_value_list(json_field=self.establishment_type_json,
model=EstablishmentType,
lookup_field='index_name')
@property
def establishment_type_ids(self):
from establishment.models import EstablishmentType
return self.get_value_list(json_field=self.establishment_type_json,
model=EstablishmentType,
lookup_field='id')
@property
def locales(self):
return self.get_value_list(json_field=self.locale_json,
model=Language,
lookup_field='locale')
@property
def review_states(self):
states = []
for state in self.review_state_json.get('state'):
status_field = [field for field in Review._meta.fields
if field.name == 'status'][0]
status_field_id = Review._meta.fields.index(status_field)
states.append(dict(Review._meta.fields[status_field_id].choices).get(state))
return states
@property
def country_names(self):
return self.get_value_list(json_field=self.country_json,
model=Country,
lookup_field='name_translated')
@property
def country_ids(self):
return self.get_value_list(json_field=self.country_json,
model=Country,
lookup_field='id')
@property
def region_names(self):
return self.get_value_list(json_field=self.region_json,
model=Region,
lookup_field='name')
@property
def region_ids(self):
return self.get_value_list(json_field=self.region_json,
model=Region,
lookup_field='id')
@property
def sub_region_names(self):
return self.get_value_list(json_field=self.sub_region_json,
model=Region,
lookup_field='name_translated')
@property
def sub_region_ids(self):
return self.get_value_list(json_field=self.sub_region_json,
model=Region,
lookup_field='id')
@property
def wine_region_ids(self):
return self.get_value_list(json_field=self.wine_region_json,
model=WineRegion,
lookup_field='id')
@property
def review_vintages(self):
return self.review_vintage_json.get('vintage')
@property
def available_filters(self):
filters = list()
for i in self._meta.fields:
if isinstance(i, JSONField):
has_values = list(getattr(self, f'{i.name}').values())[0]
if has_values:
filters.append(i.name)
return filters
@property
def establishment_filter_set(self):
filters = {
# establishment.Establishment
'public_mark__in': [self.min_mark, self.max_mark],
# review.Reviews
'reviews__vintage__in': self.review_vintages,
}
if self.establishment_type_ids:
filters.update({
# establishment.EstablishmentType
'establishment_type_id__in': self.establishment_type_ids,
})
if self.country_ids:
filters.update({
# location.Country
'address__city__country_id__in': self.country_ids,
})
if self.region_ids:
filters.update({
# location.Region
'address__city__region__parent_id__in': self.region_ids,
})
if self.sub_region_ids:
filters.update({
# location.Region
'address__city__region__parent_id__in': self.region_ids,
'address__city__region_id__in': self.sub_region_ids,
})
if self.wine_region_ids:
filters.update({
# location.WineRegion
'wine_region_id__in': self.wine_region_ids,
})
if self.with_mark:
filters.update({
# establishment.Establishment
'public_mark__isnull': False,
})
if self.locales:
filters.update({
'reviews__text__has_any_keys': self.locales,
})
return filters
@property
def product_filter_set(self):
filters = {
# establishment.Establishment
'establishment__public_mark__in': [self.min_mark, self.max_mark],
# review.Reviews
'reviews__vintage__in': self.review_vintages,
}
if self.establishment_type_ids:
filters.update({
# establishment.EstablishmentType
'establishment__establishment_type_id__in': self.establishment_type_ids,
})
if self.country_ids:
filters.update({
# location.Country
'establishment__address__city__country_id__in': self.country_ids,
})
if self.region_ids:
filters.update({
# location.Region
'establishment__address__city__region__parent_id__in': self.region_ids,
})
if self.sub_region_ids:
filters.update({
# location.Region
'establishment__address__city__region__parent_id__in': self.region_ids,
'establishment__address__city__region_id__in': self.sub_region_ids,
})
if self.wine_region_ids:
filters.update({
# location.WineRegion
'wine_origins__wine_region_id__in': self.wine_region_ids,
})
if self.with_mark:
filters.update({
# establishment.Establishment
'establishment__public_mark__isnull': False,
})
if self.locales:
filters.update({
'reviews__text__has_any_keys': self.locales,
})
return filters
class GuideElementType(models.Model):
"""Model for type of guide elements."""
name = models.CharField(max_length=50,
verbose_name=_('name'))
class Meta:
"""Meta class."""
verbose_name = _('guide element type')
verbose_name_plural = _('guide element types')
def __str__(self):
"""Overridden str dunder."""
return self.name
class GuideWineColorSectionQuerySet(models.QuerySet):
"""QuerySet for model GuideWineColorSection."""
class GuideWineColorSection(ProjectBaseMixin):
"""Sections for wine colors."""
name = models.CharField(max_length=255, verbose_name=_('section name'))
objects = GuideWineColorSectionQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name = _('guide wine color section')
verbose_name_plural = _('guide wine color sections')
class GuideElementSectionCategoryQuerySet(models.QuerySet):
"""QuerySet for model GuideElementSectionCategory."""
class GuideElementSectionCategory(ProjectBaseMixin):
"""Section category for guide element."""
name = models.CharField(max_length=255,
verbose_name=_('category name'))
objects = GuideElementSectionCategoryQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name = _('guide element section category')
verbose_name_plural = _('guide element section categories')
class GuideElementSectionQuerySet(models.QuerySet):
"""QuerySet for model GuideElementSection."""
class GuideElementSection(ProjectBaseMixin):
"""Sections for guide element."""
name = models.CharField(max_length=255, verbose_name=_('section name'))
category = models.ForeignKey(GuideElementSectionCategory, on_delete=models.PROTECT,
verbose_name=_('category'))
old_id = models.PositiveIntegerField(blank=True, null=True, default=None,
verbose_name=_('old id'))
objects = GuideElementSectionQuerySet.as_manager()
class Meta:
"""Meta class."""
verbose_name = _('guide element section')
verbose_name_plural = _('guide element sections')
class GuideElementManager(models.Manager):
"""Manager for model GuideElement."""
def get_root_node(self, guide):
"""Return guide root element node."""
qs = self.filter(guide=guide, guide_element_type__name='Root')
if qs.exists():
return qs.first()
def get_or_create_root_node(self, guide_id: int):
"""Get or Create RootNode."""
guide_element_type_qs = GuideElementType.objects.filter(name='Root')
guide_qs = Guide.objects.filter(id=guide_id)
if guide_element_type_qs.exists() and guide_qs.exists():
guide = guide_qs.first()
return self.get_or_create(guide_id=guide_qs.first(),
guide_element_type=guide_element_type_qs.first(),
defaults={
'guide_id': guide.id,
'guide_element_type': guide_element_type_qs.first()})
return None, False
def get_or_create_city_node(self, root_node_id: int, city_id: int):
"""Get or Create CityNode."""
parent_node_qs = GuideElement.objects.filter(id=root_node_id)
guide_element_type_qs = GuideElementType.objects.filter(name='CityNode')
city_qs = City.objects.filter(id=city_id)
if parent_node_qs.exists() and city_qs.exists() and guide_element_type_qs.exists():
return self.get_or_create(guide_element_type=guide_element_type_qs.first(),
parent=parent_node_qs.first(),
city=city_qs.first())
return None, False
def get_or_create_establishment_section_node(self, city_node_id: int, establishment_node_name: str,
guide_id: int):
"""Get or Create (Restaurant|Shop...)SectionNode."""
parent_node_qs = GuideElement.objects.filter(id=city_node_id)
guide_element_type_qs = GuideElementType.objects.filter(name__iexact=establishment_node_name)
guide_qs = Guide.objects.filter(id=guide_id)
if parent_node_qs.exists() and guide_element_type_qs.exists() and guide_qs.exists():
parent_node = parent_node_qs.first()
return self.get_or_create(guide_element_type=guide_element_type_qs.first(),
parent=parent_node,
guide=guide_qs.first())
return None, False
def get_or_create_establishment_node(self, restaurant_section_node_id: int, guide_id: int,
establishment_id: int, review_id: int = None):
"""Get or Create EstablishmentNode."""
from establishment.models import Establishment
data = {}
guide_element_type_qs = GuideElementType.objects.filter(name='EstablishmentNode')
parent_node_qs = GuideElement.objects.filter(id=restaurant_section_node_id)
establishment_qs = Establishment.objects.filter(id=establishment_id)
guide_qs = Guide.objects.filter(id=guide_id)
if (parent_node_qs.exists() and establishment_qs.exists()
and guide_element_type_qs.exists() and guide_qs.exists()):
establishment = establishment_qs.first()
parent_node = parent_node_qs.first()
data.update({
'guide_element_type': guide_element_type_qs.first(),
'parent': parent_node,
'guide': guide_qs.first(),
'establishment': establishment
})
if review_id:
review_qs = Review.objects.filter(id=review_id)
if review_qs.exists(): data.update({'review_id': review_qs.first().id})
return self.get_or_create(**data)
return None, False
def get_or_create_wine_region_node(self, root_node_id: int, wine_region_id: int, guide_id: int):
"""Get or Create WineRegionNode."""
guide_element_type_qs = GuideElementType.objects.filter(name='RegionNode')
parent_node_qs = GuideElement.objects.filter(id=root_node_id)
wine_region_qs = WineRegion.objects.filter(id=wine_region_id)
guide_qs = Guide.objects.filter(id=guide_id)
if (parent_node_qs.exists() and parent_node_qs.first().guide and
wine_region_qs.exists() and guide_element_type_qs.exists() and
guide_qs.exists()):
root_node = parent_node_qs.first()
return self.get_or_create(guide_element_type=guide_element_type_qs.first(),
parent=root_node,
guide=guide_qs.first(),
wine_region=wine_region_qs.first())
return None, False
def get_or_create_yard_node(self, product_id: int, wine_region_node_id: int, guide_id: int):
"""Make YardNode."""
from establishment.models import Establishment
guide_element_type_qs = GuideElementType.objects.filter(name='YardNode')
wine_region_node_qs = GuideElement.objects.filter(id=wine_region_node_id)
product_qs = Product.objects.filter(id=product_id)
guide_qs = Guide.objects.filter(id=guide_id)
if product_qs.exists() and wine_region_node_qs.exists() and guide_qs.exists():
wine_region_node = wine_region_node_qs.first()
product = product_qs.first()
return self.get_or_create(guide_element_type=guide_element_type_qs.first(),
parent=wine_region_node,
guide=guide_qs.first(),
establishment=product.establishment)
return None, False
def get_or_create_color_wine_section_node(self, wine_color_name: str,
yard_node_id: int, guide_id: int):
"""Get or Create WineSectionNode."""
guide_element_type_qs = GuideElementType.objects.filter(name='ColorWineSectionNode')
parent_node_qs = GuideElement.objects.filter(id=yard_node_id)
guide_qs = Guide.objects.filter(id=guide_id)
if not wine_color_name.endswith('SectionNode'):
wine_color_name = transform_into_section_name(wine_color_name)
wine_color_section, _ = GuideWineColorSection.objects.get_or_create(
name=wine_color_name,
defaults={
'name': wine_color_name
})
if parent_node_qs.exists() and guide_element_type_qs.exists() and guide_qs.exists():
root_node = parent_node_qs.first()
return self.get_or_create(guide_element_type=guide_element_type_qs.first(),
parent=root_node,
wine_color_section=wine_color_section,
guide=guide_qs.first())
return None, False
def get_or_create_wine_node(self, color_wine_section_node_id: int,
wine_id: int, review_id: int, guide_id: int):
"""Get or Create WineNode."""
guide_element_type_qs = GuideElementType.objects.filter(name='WineNode')
parent_node_qs = GuideElement.objects.filter(id=color_wine_section_node_id)
wine_qs = Product.objects.wines().filter(id=wine_id)
review_qs = Review.objects.filter(id=review_id)
guide_qs = Guide.objects.filter(id=guide_id)
if (parent_node_qs.exists() and wine_qs.exists() and
review_qs.exists() and guide_element_type_qs.exists() and guide_qs.exists()):
root_node = parent_node_qs.first()
return self.get_or_create(guide_element_type=guide_element_type_qs.first(),
parent=root_node,
product=wine_qs.first(),
guide=guide_qs.first(),
review=review_qs.first())
return None, False
class GuideElementQuerySet(models.QuerySet):
"""QuerySet for model Guide elements."""
def base_related(self):
"""Return QuerySet with base related."""
return self.select_related(
'guide_element_type',
'establishment',
'review',
'wine_region',
'product',
'city',
'wine_color_section',
'section',
'guide',
'label_photo',
)
def restaurant_nodes(self):
"""Return GuideElement with type RestaurantSectionNode."""
return self.filter(guide_element_type__name='RestaurantSectionNode')
def shop_nodes(self):
"""Return GuideElement with type ShopSectionNode."""
return self.filter(guide_element_type__name='ShopSectionNode')
def wine_nodes(self):
"""Return GuideElement with type WineNode."""
return self.filter(guide_element_type__name='WineNode')
def descendants(self):
"""Return QuerySet with descendants."""
return self.exclude(guide_element_type__name='Root')
def by_establishment_type(self, establishment_type_index_name: str):
"""Return QuerySet by establishment_type."""
return self.filter(
guide_element_type__name__iexact=transform_into_section_name(
establishment_type_index_name
)
)
class GuideElement(ProjectBaseMixin, MPTTModel):
"""Frozen state of elements of guide instance."""
guide_element_type = models.ForeignKey('GuideElementType', on_delete=models.SET_NULL,
null=True,
verbose_name=_('guide element type'))
establishment = models.ForeignKey('establishment.Establishment', on_delete=models.SET_NULL,
null=True, blank=True, default=None)
review = models.ForeignKey('review.Review', on_delete=models.SET_NULL,
null=True, blank=True, default=None)
wine_region = models.ForeignKey('location.WineRegion', on_delete=models.SET_NULL,
null=True, blank=True, default=None)
product = models.ForeignKey('product.Product', on_delete=models.SET_NULL,
null=True, blank=True, default=None)
priority = models.IntegerField(null=True, blank=True, default=None)
city = models.ForeignKey('location.City', on_delete=models.SET_NULL,
null=True, blank=True, default=None)
wine_color_section = models.ForeignKey('GuideWineColorSection', on_delete=models.SET_NULL,
null=True, blank=True, default=None)
section = models.ForeignKey('GuideElementSection', on_delete=models.SET_NULL,
null=True, blank=True, default=None)
guide = models.ForeignKey('Guide', on_delete=models.SET_NULL,
null=True, blank=True, default=None)
parent = TreeForeignKey('self', on_delete=models.CASCADE,
null=True, blank=True,
related_name='children')
label_photo = models.ForeignKey('gallery.Image', on_delete=models.SET_NULL,
null=True, blank=True, default=None,
verbose_name=_('label photo'))
old_id = models.PositiveIntegerField(blank=True, null=True, default=None,
verbose_name=_('old id'))
objects = GuideElementManager.from_queryset(GuideElementQuerySet)()
class Meta:
"""Meta class."""
verbose_name = _('guide element')
verbose_name_plural = _('guide elements')
class MPTTMeta:
order_insertion_by = ['guide_element_type']
def __str__(self):
"""Overridden dunder method."""
return self.guide_element_type.name if self.guide_element_type else self.id