Skip to content
This repository has been archived by the owner on Feb 23, 2020. It is now read-only.

Commit

Permalink
#611,697,710 Section and Category min_price, optimize Category.get_se…
Browse files Browse the repository at this point in the history
…ries (#716)

* #697  Implement min price for high level categories

* #710  Clarify Category.min_price arch

* #710  Implement Section.get_min_price

* #611  Optimize get_series queries
  • Loading branch information
duker33 committed Jun 29, 2019
1 parent e9b2908 commit c249ec0
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 29 deletions.
53 changes: 28 additions & 25 deletions stroyprombeton/models.py
Expand Up @@ -76,29 +76,19 @@ def get_series(self) -> models.QuerySet:
.select_related('product')
.select_related('product__category')
.filter(
product__category__in=(
# @todo #597:30m Check mptt's `get_descendants` default optimization.
# Check if the method already contains
# relevant prefetch/select_related for parent, children fields.
Category.objects
.select_related('parent')
.prefetch_related('children')
.filter(id=self.id)
.get_descendants(include_self=True)
.active()
)
product__in=Product.objects.filter_descendants(self)
).values('id')
)
).distinct()
.order_by('name')
)

def recursive_products(self) -> 'ProductQuerySet':
return Product.objects.filter_descendants(self)

def get_min_price(self) -> float:
return (
Option.objects
.filter(product__in=self.products.active())
.min_price()
)
"""Helper for templates."""
return self.recursive_products().options().min_price()


class SeriesQuerySet(models.QuerySet):
Expand Down Expand Up @@ -180,7 +170,8 @@ def _get_slug(self) -> str:
)

def get_min_price(self) -> float:
return Option.objects.filter(series=self).min_price()
"""Helper for templates."""
return self.options.min_price()

def save(self, *args, **kwargs):
if not self.slug:
Expand Down Expand Up @@ -249,14 +240,9 @@ def get_absolute_url(self):
"""Url path to the related page."""
return reverse('section', args=(self.page.slug,))

# @todo #669:60m Implement `Section.get_min_price` method.
# And look at the get_min_price arch in a whole.
# Maybe only ProductsQS and OptionsQS should have min_price,
# but Section and Series should not.
# Category, Series and Product page templates at the production DB
# contains series min price usage.
def get_min_price(self) -> float:
raise NotImplemented()
"""Helper for templates."""
return self.products.options().min_price()


class OptionQuerySet(models.QuerySet):
Expand All @@ -268,6 +254,7 @@ def bind_fields(self):
"""Prefetch or select typical related fields to reduce sql queries count."""
return (
self.select_related('product')
.select_related('series')
.prefetch_related('tags')
)

Expand Down Expand Up @@ -382,11 +369,27 @@ class ProductQuerySet(catalog.models.ProductQuerySet):
def get_series(self):
pass

def options(self) -> OptionQuerySet:
return Option.objects.filter(product__in=self).distinct()


class ProductManager(models.Manager.from_queryset(ProductQuerySet)):
"""Get all products of given category by Category's id or instance."""

def filter_descendants(self, category: Category) -> ProductQuerySet:
return self.get_queryset().filter_descendants(category)

def active(self):
return self.get_queryset().active()

def tagged(self, tags: typing.Iterable['Tag']):
return self.get_queryset().tagged(tags)


# not inherited from `catalog.models.AbstractProduct`, because
# AbstractProduct's set of fields is shared between Product and Option models.
class Product(catalog.models.AbstractProduct, pages.models.PageMixin):
objects = catalog.models.ProductManager()
objects = ProductManager()

name = models.CharField(max_length=255, db_index=True, verbose_name=_('name'))
category = models.ForeignKey(
Expand Down
20 changes: 16 additions & 4 deletions stroyprombeton/tests/tests_models.py
@@ -1,5 +1,3 @@
import unittest

from django.test import TestCase, tag

from pages import models as pages_models
Expand All @@ -15,19 +13,33 @@ def test_series(self):
option = stb_models.Option.objects.first()
least = option.product.category
root = least.get_root()
self.assertNumQueries(2, lambda: len(root.get_series()))
self.assertIn(option.series, least.get_series())
self.assertIn(option.series, root.get_series())

# @todo #692:30m Implement min_price for non-leaf category.
@unittest.expectedFailure
def test_min_price(self):
"""Non-leaf category should find min price recursively."""
option = stb_models.Option.objects.filter(price__gt=0).first()
category = option.product.category.parent
self.assertNumQueries(1, category.get_min_price)
self.assertGreater(category.get_min_price(), 0)
self.assertLessEqual(category.get_min_price(), option.price)


@tag('fast')
class Section(TestCase):

fixtures = ['dump.json']

def test_min_price(self):
"""Non-leaf category should find min price recursively."""
option = stb_models.Option.objects.filter(price__gt=0).first()
section = option.product.section
self.assertNumQueries(1, section.get_min_price)
self.assertGreater(section.get_min_price(), 0)
self.assertLessEqual(section.get_min_price(), option.price)


@tag('fast')
class Option_(TestCase):

Expand Down

3 comments on commit c249ec0

@0pdd
Copy link
Collaborator

@0pdd 0pdd commented on c249ec0 Jun 29, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Puzzle 597-ce108e86 disappeared from stroyprombeton/models.py, that's why I closed #611. Please, remember that the puzzle was not necessarily removed in this particular commit. Maybe it happened earlier, but we discovered this fact only now.

@0pdd
Copy link
Collaborator

@0pdd 0pdd commented on c249ec0 Jun 29, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Puzzle 692-fd8e891c disappeared from stroyprombeton/tests/tests_models.py, that's why I closed #697. Please, remember that the puzzle was not necessarily removed in this particular commit. Maybe it happened earlier, but we discovered this fact only now.

@0pdd
Copy link
Collaborator

@0pdd 0pdd commented on c249ec0 Jun 29, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Puzzle 669-b068266e disappeared from stroyprombeton/models.py, that's why I closed #710. Please, remember that the puzzle was not necessarily removed in this particular commit. Maybe it happened earlier, but we discovered this fact only now.

Please sign in to comment.