added counters

This commit is contained in:
Anatoly 2019-12-19 16:27:58 +03:00
parent 0851a9e6d6
commit 68b8aa427c
5 changed files with 194 additions and 28 deletions

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.7 on 2019-12-19 13:04
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('collection', '0027_auto_20191218_0753'),
]
operations = [
migrations.AddField(
model_name='guide',
name='count_objects_during_init',
field=models.PositiveIntegerField(default=0, help_text='* after rebuild guide, refresh count of related guide elements', verbose_name='count of related guide elements during initialization'),
),
]

View File

@ -8,8 +8,8 @@ from django.utils.translation import gettext_lazy as _
from mptt.models import MPTTModel, TreeForeignKey from mptt.models import MPTTModel, TreeForeignKey
from location.models import Country, Region, WineRegion, WineSubRegion from location.models import Country, Region, WineRegion, WineSubRegion
from translation.models import Language
from review.models import Review from review.models import Review
from translation.models import Language
from utils.models import IntermediateGalleryModelMixin, GalleryModelMixin from utils.models import IntermediateGalleryModelMixin, GalleryModelMixin
from utils.models import ( from utils.models import (
ProjectBaseMixin, TJSONField, TranslatedFieldsMixin, ProjectBaseMixin, TJSONField, TranslatedFieldsMixin,
@ -178,6 +178,68 @@ class GuideQuerySet(models.QuerySet):
"""Return QuerySet filtered by country code.""" """Return QuerySet filtered by country code."""
return self.filter(country_json__id__contains=country_id) 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): class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin):
"""Guide model.""" """Guide model."""
@ -191,7 +253,6 @@ class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin):
(WAITING, 'waiting'), (WAITING, 'waiting'),
(REMOVING, 'removing'), (REMOVING, 'removing'),
(BUILDING, 'building'), (BUILDING, 'building'),
) )
start = models.DateTimeField(null=True, start = models.DateTimeField(null=True,
@ -210,6 +271,10 @@ class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin):
verbose_name=_('site settings')) verbose_name=_('site settings'))
state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES, state = models.PositiveSmallIntegerField(default=WAITING, choices=STATE_CHOICES,
verbose_name=_('state')) verbose_name=_('state'))
count_objects_during_init = 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) old_id = models.IntegerField(blank=True, null=True)
objects = GuideQuerySet.as_manager() objects = GuideQuerySet.as_manager()
@ -223,16 +288,45 @@ class Guide(ProjectBaseMixin, CollectionNameMixin, CollectionDateMixin):
"""String method.""" """String method."""
return f'{self.name}' return f'{self.name}'
@property # todo: for test use, use annotation instead
def entities(self): # @property
"""Return entities and its count.""" # def restaurant_counter_prop(self):
# todo: to work # counter = 0
return { # root_node = GuideElement.objects.get_root_node(self)
'Current': 0, # if root_node:
'Initial': 0, # descendants = GuideElement.objects.get_root_node(self).get_descendants()
'Restaurants': 0, # if descendants:
'Shops': 0, # 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
class AdvertorialQuerySet(models.QuerySet): class AdvertorialQuerySet(models.QuerySet):
@ -439,9 +533,31 @@ class GuideElementSection(ProjectBaseMixin):
verbose_name_plural = _('guide element sections') 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()
class GuideElementQuerySet(models.QuerySet): class GuideElementQuerySet(models.QuerySet):
"""QuerySet for model Guide elements.""" """QuerySet for model Guide elements."""
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')
class GuideElement(ProjectBaseMixin, MPTTModel): class GuideElement(ProjectBaseMixin, MPTTModel):
"""Frozen state of elements of guide instance.""" """Frozen state of elements of guide instance."""
@ -475,7 +591,7 @@ class GuideElement(ProjectBaseMixin, MPTTModel):
old_id = models.PositiveIntegerField(blank=True, null=True, default=None, old_id = models.PositiveIntegerField(blank=True, null=True, default=None,
verbose_name=_('old id')) verbose_name=_('old id'))
objects = GuideElementQuerySet.as_manager() objects = GuideElementManager.from_queryset(GuideElementQuerySet)()
class Meta: class Meta:
"""Meta class.""" """Meta class."""

View File

@ -88,9 +88,13 @@ class GuideBaseSerializer(serializers.ModelSerializer):
source='guide_type') source='guide_type')
site_detail = SiteShortSerializer(read_only=True, site_detail = SiteShortSerializer(read_only=True,
source='site') source='site')
entities = serializers.DictField(read_only=True)
guide_filters = GuideFilterBaseSerialzer(read_only=True, guide_filters = GuideFilterBaseSerialzer(read_only=True,
source='guidefilter') source='guidefilter')
# counters
restaurant_counter = serializers.IntegerField(read_only=True)
shop_counter = serializers.IntegerField(read_only=True)
wine_counter = serializers.IntegerField(read_only=True)
present_objects_counter = serializers.IntegerField(read_only=True)
class Meta: class Meta:
model = models.Guide model = models.Guide
@ -107,8 +111,12 @@ class GuideBaseSerializer(serializers.ModelSerializer):
'site_detail', 'site_detail',
'state', 'state',
'state_display', 'state_display',
'entities',
'guide_filters', 'guide_filters',
'restaurant_counter',
'shop_counter',
'wine_counter',
'present_objects_counter',
'count_objects_during_init',
] ]
extra_kwargs = { extra_kwargs = {
'guide_type': {'write_only': True}, 'guide_type': {'write_only': True},
@ -116,6 +124,7 @@ class GuideBaseSerializer(serializers.ModelSerializer):
'state': {'write_only': True}, 'state': {'write_only': True},
'start': {'required': True}, 'start': {'required': True},
'slug': {'required': True}, 'slug': {'required': True},
'count_objects_during_init': {'read_only': True}
} }

View File

@ -1,5 +1,7 @@
import operator
from pprint import pprint from pprint import pprint
from django.db.models import Subquery
from tqdm import tqdm from tqdm import tqdm
from collection.models import GuideElementSection, GuideElementSectionCategory, \ from collection.models import GuideElementSection, GuideElementSectionCategory, \
@ -13,7 +15,6 @@ from review.models import Review
from transfer.models import Guides, GuideFilters, GuideSections, GuideElements, \ from transfer.models import Guides, GuideFilters, GuideSections, GuideElements, \
GuideAds, LabelPhotos GuideAds, LabelPhotos
from transfer.serializers.guide import GuideSerializer, GuideFilterSerializer from transfer.serializers.guide import GuideSerializer, GuideFilterSerializer
from django.db.models import Subquery
def transfer_guide(): def transfer_guide():
@ -163,7 +164,7 @@ def transfer_guide_elements_bulk():
return qs.first() return qs.first()
objects_to_update = [] objects_to_update = []
base_queryset = GuideElements.objects.all() base_queryset = GuideElements.objects.filter(guide_id=407)
for old_id, type, establishment_id, review_id, wine_region_id, \ for old_id, type, establishment_id, review_id, wine_region_id, \
wine_id, color, order_number, city_id, section_id, guide_id \ wine_id, color, order_number, city_id, section_id, guide_id \
@ -203,19 +204,19 @@ def transfer_guide_elements_bulk():
# create parents # create parents
GuideElement.objects.bulk_create(objects_to_update) GuideElement.objects.bulk_create(objects_to_update)
pprint(f'CREATED PARENT GUIDE ELEMENTS W/ OLD_ID: {[i.old_id for i in objects_to_update]}')
print(f'COUNT OF CREATED OBJECTS: {len(objects_to_update)}') print(f'COUNT OF CREATED OBJECTS: {len(objects_to_update)}')
# attach child guide elements # attach child guide elements
queryset_values = base_queryset.filter(parent_id__isnull=False) \ created_child = 0
.order_by('-parent_id') \ queryset_values = base_queryset.exclude(parent_id__isnull=True) \
.values_list('id', 'type', 'establishment_id', .values_list('id', 'type', 'establishment_id',
'review_id', 'wine_region_id', 'wine_id', 'review_id', 'wine_region_id', 'wine_id',
'color', 'order_number', 'city_id', 'color', 'order_number', 'city_id',
'section_id', 'guide_id', 'parent_id') 'section_id', 'guide_id', 'rgt', 'lft', 'parent_id')
for old_id, type, establishment_id, review_id, wine_region_id, \ for old_id, type, establishment_id, review_id, wine_region_id, \
wine_id, color, order_number, city_id, section_id, guide_id, parent_id \ wine_id, color, order_number, city_id, section_id, guide_id, \
in tqdm(sorted(queryset_values, key=lambda value: value[len(value)-1]), lft, rgt, parent_id \
in tqdm(sorted(queryset_values, key=lambda value: operator.itemgetter(-2, -1)(value)),
desc='Check child guide elements'): desc='Check child guide elements'):
if not GuideElement.objects.filter(old_id=old_id).exists(): if not GuideElement.objects.filter(old_id=old_id).exists():
# check old guide # check old guide
@ -223,7 +224,7 @@ def transfer_guide_elements_bulk():
old_guide = Guides.objects.exclude(title__icontains='test') \ old_guide = Guides.objects.exclude(title__icontains='test') \
.filter(id=guide_id) .filter(id=guide_id)
if old_guide.exists(): if old_guide.exists():
GuideElement.objects.create( guide_element, created = GuideElement.objects.get_or_create(
old_id=old_id, old_id=old_id,
guide_element_type=get_guide_element_type(type), guide_element_type=get_guide_element_type(type),
establishment=get_establishment(establishment_id), establishment=get_establishment(establishment_id),
@ -241,13 +242,29 @@ def transfer_guide_elements_bulk():
level=1, level=1,
guide=get_guide(guide_id), guide=get_guide(guide_id),
) )
if created: created_child += 1
pprint(f'CREATED CHILD GUIDE ELEMENTS W/ OLD_ID: {[i.old_id for i in objects_to_update]}') print(f'CREATED {created_child} OBJECTS')
print(f'COUNT OF CREATED OBJECTS: {len(objects_to_update)}')
# rebuild trees # rebuild trees
GuideElement._tree_manager.rebuild() GuideElement._tree_manager.rebuild()
# record the total count of descendants objects
objects_to_update = []
for guide in tqdm(Guide.objects.all(),
desc='update count_of_initial_objects field values'):
count_of_initial_objects = GuideElement.objects.get_root_node(guide) \
.get_descendants() \
.filter(guide_element_type__name__in=['EstablishmentNode', 'WineNode']) \
.count()
guide.count_objects_during_init = count_of_initial_objects
objects_to_update.append(guide)
# update count_of_initial_objects field values
Guide.objects.bulk_update(objects_to_update, ['count_objects_during_init', ])
print(f'COUNT OF UPDATED OBJECTS: {len(objects_to_update)}')
def transfer_guide_element_advertorials(): def transfer_guide_element_advertorials():
"""Transfer Guide Advertorials model.""" """Transfer Guide Advertorials model."""

View File

@ -7,7 +7,6 @@ from rest_framework.response import Response
from collection import models, serializers from collection import models, serializers
from utils.views import BindObjectMixin from utils.views import BindObjectMixin
from django.db.models import Prefetch
class CollectionViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): class CollectionViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
@ -27,10 +26,17 @@ class CollectionViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
class GuideBaseView(generics.GenericAPIView): class GuideBaseView(generics.GenericAPIView):
"""ViewSet for Guide model.""" """ViewSet for Guide model."""
queryset = models.Guide.objects.with_base_related()
serializer_class = serializers.GuideBaseSerializer serializer_class = serializers.GuideBaseSerializer
permission_classes = (permissions.IsAuthenticated, ) permission_classes = (permissions.IsAuthenticated, )
def get_queryset(self):
"""Overridden get_queryset method."""
return models.Guide.objects.with_base_related() \
.annotate_restaurant_counter() \
.annotate_shop_counter() \
.annotate_wine_counter() \
.annotate_present_objects_counter()
class GuideFilterBaseView(generics.GenericAPIView): class GuideFilterBaseView(generics.GenericAPIView):
"""ViewSet for GuideFilter model.""" """ViewSet for GuideFilter model."""