import re 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.conf import settings from django.utils.translation import gettext_lazy as _ from mptt.models import MPTTModel, TreeForeignKey from slugify import slugify 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 collection import tasks from translation.models import Language from utils.methods import transform_into_readable_str 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) 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 = [] # get related objects for related_object in self._meta.related_objects: 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('guide_type', 'site') def by_country_id(self, country_id): """Return QuerySet filtered by country code.""" return self.filter(country_json__id__contains=country_id) def annotate_in_restaurant_section(self): """Annotate flag if GuideElement in RestaurantSectionNode.""" return self.annotate( in_restaurant_section=models.Case( models.When( guideelement__guide_element_type__name='EstablishmentNode', guideelement__parent__guide_element_type__name='RestaurantSectionNode', then=True), default=False, output_field=models.BooleanField(default=False) ) ) def annotate_in_shop_section(self): """Annotate flag if GuideElement in ShopSectionNode.""" return self.annotate( in_shop_section=models.Case( models.When( guideelement__guide_element_type__name='EstablishmentNode', guideelement__parent__guide_element_type__name='ShopSectionNode', 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', filter=models.Q(in_restaurant_section=True) & models.Q(guideelement__parent_id__isnull=False), 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', filter=models.Q(in_shop_section=True) & models.Q(guideelement__parent_id__isnull=False), 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', 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_in_restaurant_section().annotate( present_objects_counter=models.Count( 'guideelement', filter=models.Q(guideelement__guide_element_type__name__in=['EstablishmentNode', 'WineNode']) & models.Q(guideelement__parent_id__isnull=False), distinct=True)) 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}' def save(self, *args, **kwargs): if not self.pk: if not self.slug: slugify_slug = slugify( f'{self.name} {self.vintage}', word_boundary=True ) self.slug = slugify_slug super(Guide, self).save(*args, **kwargs) # 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 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: int, establishment_node_name: str): """Get or Create (Restaurant|Shop...)SectionNode.""" parent_node_qs = GuideElement.objects.filter(id=city_node) guide_element_type_qs = GuideElementType.objects.filter(name__iexact=establishment_node_name) if parent_node_qs.exists() and guide_element_type_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=parent_node.get_root().guide) return None, False def get_or_create_establishment_node(self, restaurant_section_node_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) if parent_node_qs.exists() and establishment_qs.exists() and guide_element_type_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': parent_node.get_root().guide, '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): """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) if parent_node_qs.exists() and parent_node_qs.first().guide and wine_region_qs.exists() and guide_element_type_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=root_node.guide, wine_region=wine_region_qs.first()) return None, False def get_or_create_yard_node(self, product_id: int, wine_region_node_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) if product_qs.exists() and wine_region_node_qs.exists(): wine_region_node = wine_region_node_qs.first() root_node = wine_region_node.get_root() product = product_qs.first() if product.establishment: return self.get_or_create(guide_element_type=guide_element_type_qs.first(), parent=wine_region_node, guide=root_node.guide, establishment=product.establishment) return None, False def get_or_create_color_wine_section_node(self, wine_color_name: str, yard_node_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) if not wine_color_name.endswith('SectionNode'): wine_color_name = transform_into_readable_str(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(): 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=root_node.guide) return None, False def get_or_create_wine_node(self, color_wine_section_node_id: int, wine_id: int, review_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) if parent_node_qs.exists() and wine_qs.exists() and review_qs.exists() and guide_element_type_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=root_node.guide, 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') 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