Puts attach_remainders on ProductController and CategoryController, eliminating the need to query each product and category separately.

This commit is contained in:
Christopher Neugebauer 2016-04-28 18:57:55 +10:00
parent 3b5b958b78
commit a79ad3520e
3 changed files with 113 additions and 53 deletions

View file

@ -203,13 +203,17 @@ class CartController(object):
errors = []
# Pre-annotate products
products = [p for (p, q) in product_quantities]
r = ProductController.attach_user_remainders(self.cart.user, products)
with_remainders = dict((p, p) for p in r)
# Test each product limit here
for product, quantity in product_quantities:
if quantity < 0:
errors.append((product, "Value must be zero or greater."))
prod = ProductController(product)
limit = prod.user_quantity_remaining(self.cart.user)
limit = with_remainders[product].remainder
if quantity > limit:
errors.append((
@ -224,10 +228,15 @@ class CartController(object):
for product, quantity in product_quantities:
by_cat[product.category].append((product, quantity))
# Pre-annotate categories
r = CategoryController.attach_user_remainders(self.cart.user, by_cat)
with_remainders = dict((cat, cat) for cat in r)
# Test each category limit here
for category in by_cat:
ctrl = CategoryController(category)
limit = ctrl.user_quantity_remaining(self.cart.user)
#ctrl = CategoryController(category)
#limit = ctrl.user_quantity_remaining(self.cart.user)
limit = with_remainders[category].remainder
# Get the amount so far in the cart
to_add = sum(i[1] for i in by_cat[category])

View file

@ -1,7 +1,11 @@
from registrasion.models import commerce
from registrasion.models import inventory
from django.db.models import Case
from django.db.models import F, Q
from django.db.models import Sum
from django.db.models import When
from django.db.models import Value
class AllProducts(object):
@ -34,25 +38,48 @@ class CategoryController(object):
return set(i.category for i in available)
@classmethod
def attach_user_remainders(cls, user, categories):
'''
Return:
queryset(inventory.Product): A queryset containing items from
``categories``, with an extra attribute -- remainder = the amount
of items from this category that is remaining.
'''
ids = [category.id for category in categories]
categories = inventory.Category.objects.filter(id__in=ids)
cart_filter = (
Q(product__productitem__cart__user=user) &
Q(product__productitem__cart__status=commerce.Cart.STATUS_PAID)
)
quantity = When(
cart_filter,
then='product__productitem__quantity'
)
quantity_or_zero = Case(
quantity,
default=Value(0),
)
remainder = Case(
When(limit_per_user=None, then=Value(99999999)),
default=F('limit_per_user') - Sum(quantity_or_zero),
)
categories = categories.annotate(remainder=remainder)
return categories
def user_quantity_remaining(self, user):
''' Returns the number of items from this category that the user may
add in the current cart. '''
''' Returns the quantity of this product that the user add in the
current cart. '''
cat_limit = self.category.limit_per_user
with_remainders = self.attach_user_remainders(user, [self.category])
if cat_limit is None:
# We don't need to waste the following queries
return 99999999
carts = commerce.Cart.objects.filter(
user=user,
status=commerce.Cart.STATUS_PAID,
)
items = commerce.ProductItem.objects.filter(
cart__in=carts,
product__category=self.category,
)
cat_count = items.aggregate(Sum("quantity"))["quantity__sum"] or 0
return cat_limit - cat_count
return with_remainders[0].remainder

View file

@ -1,6 +1,11 @@
import itertools
from django.db.models import Case
from django.db.models import F, Q
from django.db.models import Sum
from django.db.models import When
from django.db.models import Value
from registrasion.models import commerce
from registrasion.models import inventory
@ -16,9 +21,7 @@ class ProductController(object):
@classmethod
def available_products(cls, user, category=None, products=None):
''' Returns a list of all of the products that are available per
flag conditions from the given categories.
TODO: refactor so that all conditions are tested here and
can_add_with_flags calls this method. '''
flag conditions from the given categories. '''
if category is None and products is None:
raise ValueError("You must provide products or a category")
@ -31,19 +34,18 @@ class ProductController(object):
if products is not None:
all_products = set(itertools.chain(all_products, products))
cat_quants = dict(
(
category,
CategoryController(category).user_quantity_remaining(user),
)
for category in set(product.category for product in all_products)
)
categories = set(product.category for product in all_products)
r = CategoryController.attach_user_remainders(user, categories)
cat_quants = dict((c,c) for c in r)
r = ProductController.attach_user_remainders(user, all_products)
prod_quants = dict((p,p) for p in r)
passed_limits = set(
product
for product in all_products
if cat_quants[product.category] > 0
if cls(product).user_quantity_remaining(user) > 0
if cat_quants[product.category].remainder > 0
if prod_quants[product].remainder > 0
)
failed_and_messages = FlagController.test_flags(
@ -56,26 +58,48 @@ class ProductController(object):
return out
@classmethod
def attach_user_remainders(cls, user, products):
'''
Return:
queryset(inventory.Product): A queryset containing items from
``product``, with an extra attribute -- remainder = the amount of
this item that is remaining.
'''
ids = [product.id for product in products]
products = inventory.Product.objects.filter(id__in=ids)
cart_filter = (
Q(productitem__cart__user=user) &
Q(productitem__cart__status=commerce.Cart.STATUS_PAID)
)
quantity = When(
cart_filter,
then='productitem__quantity'
)
quantity_or_zero = Case(
quantity,
default=Value(0),
)
remainder = Case(
When(limit_per_user=None, then=Value(99999999)),
default=F('limit_per_user') - Sum(quantity_or_zero),
)
products = products.annotate(remainder=remainder)
return products
def user_quantity_remaining(self, user):
''' Returns the quantity of this product that the user add in the
current cart. '''
prod_limit = self.product.limit_per_user
with_remainders = self.attach_user_remainders(user, [self.product])
if prod_limit is None:
# Don't need to run the remaining queries
return 999999 # We can do better
carts = commerce.Cart.objects.filter(
user=user,
status=commerce.Cart.STATUS_PAID,
)
items = commerce.ProductItem.objects.filter(
cart__in=carts,
product=self.product,
)
prod_count = items.aggregate(Sum("quantity"))["quantity__sum"] or 0
return prod_limit - prod_count
return with_remainders[0].remainder