Puts attach_remainders on ProductController and CategoryController, eliminating the need to query each product and category separately.
This commit is contained in:
parent
3b5b958b78
commit
a79ad3520e
3 changed files with 113 additions and 53 deletions
|
@ -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])
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
Loading…
Reference in a new issue