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 = [] 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 # Test each product limit here
for product, quantity in product_quantities: for product, quantity in product_quantities:
if quantity < 0: if quantity < 0:
errors.append((product, "Value must be zero or greater.")) errors.append((product, "Value must be zero or greater."))
prod = ProductController(product) limit = with_remainders[product].remainder
limit = prod.user_quantity_remaining(self.cart.user)
if quantity > limit: if quantity > limit:
errors.append(( errors.append((
@ -224,10 +228,15 @@ class CartController(object):
for product, quantity in product_quantities: for product, quantity in product_quantities:
by_cat[product.category].append((product, quantity)) 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 # Test each category limit here
for category in by_cat: for category in by_cat:
ctrl = CategoryController(category) #ctrl = CategoryController(category)
limit = ctrl.user_quantity_remaining(self.cart.user) #limit = ctrl.user_quantity_remaining(self.cart.user)
limit = with_remainders[category].remainder
# Get the amount so far in the cart # Get the amount so far in the cart
to_add = sum(i[1] for i in by_cat[category]) 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 commerce
from registrasion.models import inventory 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 Sum
from django.db.models import When
from django.db.models import Value
class AllProducts(object): class AllProducts(object):
@ -34,25 +38,48 @@ class CategoryController(object):
return set(i.category for i in available) 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): def user_quantity_remaining(self, user):
''' Returns the number of items from this category that the user may ''' Returns the quantity of this product that the user add in the
add in the current cart. ''' current cart. '''
cat_limit = self.category.limit_per_user with_remainders = self.attach_user_remainders(user, [self.category])
if cat_limit is None: return with_remainders[0].remainder
# 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

View file

@ -1,6 +1,11 @@
import itertools 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 Sum
from django.db.models import When
from django.db.models import Value
from registrasion.models import commerce from registrasion.models import commerce
from registrasion.models import inventory from registrasion.models import inventory
@ -16,9 +21,7 @@ class ProductController(object):
@classmethod @classmethod
def available_products(cls, user, category=None, products=None): def available_products(cls, user, category=None, products=None):
''' Returns a list of all of the products that are available per ''' Returns a list of all of the products that are available per
flag conditions from the given categories. flag conditions from the given categories. '''
TODO: refactor so that all conditions are tested here and
can_add_with_flags calls this method. '''
if category is None and products is None: if category is None and products is None:
raise ValueError("You must provide products or a category") raise ValueError("You must provide products or a category")
@ -31,19 +34,18 @@ class ProductController(object):
if products is not None: if products is not None:
all_products = set(itertools.chain(all_products, products)) all_products = set(itertools.chain(all_products, products))
cat_quants = dict( categories = set(product.category for product in all_products)
( r = CategoryController.attach_user_remainders(user, categories)
category, cat_quants = dict((c,c) for c in r)
CategoryController(category).user_quantity_remaining(user),
) r = ProductController.attach_user_remainders(user, all_products)
for category in set(product.category for product in all_products) prod_quants = dict((p,p) for p in r)
)
passed_limits = set( passed_limits = set(
product product
for product in all_products for product in all_products
if cat_quants[product.category] > 0 if cat_quants[product.category].remainder > 0
if cls(product).user_quantity_remaining(user) > 0 if prod_quants[product].remainder > 0
) )
failed_and_messages = FlagController.test_flags( failed_and_messages = FlagController.test_flags(
@ -56,26 +58,48 @@ class ProductController(object):
return out 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): def user_quantity_remaining(self, user):
''' Returns the quantity of this product that the user add in the ''' Returns the quantity of this product that the user add in the
current cart. ''' current cart. '''
prod_limit = self.product.limit_per_user with_remainders = self.attach_user_remainders(user, [self.product])
if prod_limit is None: return with_remainders[0].remainder
# 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