Optimises queries through simplifying repeated queries and select_related use

This commit is contained in:
Christopher Neugebauer 2016-04-06 18:28:33 +10:00
parent 5debbb2ac8
commit 53413388e0
7 changed files with 95 additions and 38 deletions

View file

@ -80,6 +80,11 @@ class CartController(object):
pairs. ''' pairs. '''
items_in_cart = rego.ProductItem.objects.filter(cart=self.cart) items_in_cart = rego.ProductItem.objects.filter(cart=self.cart)
items_in_cart = items_in_cart.select_related(
"product",
"product__category",
)
product_quantities = list(product_quantities) product_quantities = list(product_quantities)
# n.b need to add have the existing items first so that the new # n.b need to add have the existing items first so that the new
@ -283,6 +288,7 @@ class CartController(object):
# Fix products and discounts # Fix products and discounts
items = rego.ProductItem.objects.filter(cart=self.cart) items = rego.ProductItem.objects.filter(cart=self.cart)
items = items.select_related("product")
products = set(i.product for i in items) products = set(i.product for i in items)
available = set(ProductController.available_products( available = set(ProductController.available_products(
self.cart.user, self.cart.user,
@ -302,7 +308,9 @@ class CartController(object):
# Delete the existing entries. # Delete the existing entries.
rego.DiscountItem.objects.filter(cart=self.cart).delete() rego.DiscountItem.objects.filter(cart=self.cart).delete()
product_items = self.cart.productitem_set.all() product_items = self.cart.productitem_set.all().select_related(
"product", "product__category",
)
products = [i.product for i in product_items] products = [i.product for i in product_items]
discounts = discount.available_discounts(self.cart.user, [], products) discounts = discount.available_discounts(self.cart.user, [], products)
@ -310,6 +318,7 @@ class CartController(object):
# The highest-value discounts will apply to the highest-value # The highest-value discounts will apply to the highest-value
# products first. # products first.
product_items = self.cart.productitem_set.all() product_items = self.cart.productitem_set.all()
product_items = product_items.select_related("product")
product_items = product_items.order_by('product__price') product_items = product_items.order_by('product__price')
product_items = reversed(product_items) product_items = reversed(product_items)
for item in product_items: for item in product_items:

View file

@ -22,7 +22,7 @@ class CategoryController(object):
from product import ProductController from product import ProductController
if products is AllProducts: if products is AllProducts:
products = rego.Product.objects.all() products = rego.Product.objects.all().select_related("category")
available = ProductController.available_products( available = ProductController.available_products(
user, user,

View file

@ -1,4 +1,5 @@
import itertools import itertools
import operator
from collections import defaultdict from collections import defaultdict
from collections import namedtuple from collections import namedtuple
@ -90,12 +91,22 @@ class ConditionController(object):
quantities = {} quantities = {}
# Get the conditions covered by the products themselves # Get the conditions covered by the products themselves
all_conditions = [
product.enablingconditionbase_set.select_subclasses() | prods = (
product.category.enablingconditionbase_set.select_subclasses() product.enablingconditionbase_set.select_subclasses()
for product in products for product in products
] )
all_conditions = set(itertools.chain(*all_conditions)) # Get the conditions covered by their categories
cats = (
category.enablingconditionbase_set.select_subclasses()
for category in set(product.category for product in products)
)
if products:
# Simplify the query.
all_conditions = reduce(operator.or_, itertools.chain(prods, cats))
else:
all_conditions = []
# All mandatory conditions on a product need to be met # All mandatory conditions on a product need to be met
mandatory = defaultdict(lambda: True) mandatory = defaultdict(lambda: True)
@ -115,10 +126,14 @@ class ConditionController(object):
from_category = rego.Product.objects.filter( from_category = rego.Product.objects.filter(
category__in=condition.categories.all(), category__in=condition.categories.all(),
).all() ).all()
all_products = set(itertools.chain(cond_products, from_category)) all_products = cond_products | from_category
all_products = all_products.select_related("category")
# Remove the products that we aren't asking about # Remove the products that we aren't asking about
all_products = all_products & products all_products = [
product
for product in all_products
if product in products
]
if quantities: if quantities:
consumed = sum(quantities[i] for i in all_products) consumed = sum(quantities[i] for i in all_products)

View file

@ -13,7 +13,7 @@ class DiscountAndQuantity(object):
self.quantity = quantity self.quantity = quantity
def __repr__(self): def __repr__(self):
print "(discount=%s, clause=%s, quantity=%d)" % ( return "(discount=%s, clause=%s, quantity=%d)" % (
self.discount, self.clause, self.quantity, self.discount, self.clause, self.quantity,
) )
@ -37,11 +37,20 @@ def available_discounts(user, categories, products):
) )
# (Not relevant: discounts that match products in provided categories) # (Not relevant: discounts that match products in provided categories)
product_discounts = product_discounts.select_related(
"product",
"product__category",
)
all_category_discounts = category_discounts | product_category_discounts
all_category_discounts = all_category_discounts.select_related(
"category",
)
# The set of all potential discounts # The set of all potential discounts
potential_discounts = set(itertools.chain( potential_discounts = set(itertools.chain(
product_discounts, product_discounts,
category_discounts, all_category_discounts,
product_category_discounts,
)) ))
discounts = [] discounts = []
@ -50,6 +59,7 @@ def available_discounts(user, categories, products):
accepted_discounts = set() accepted_discounts = set()
failed_discounts = set() failed_discounts = set()
for discount in potential_discounts: for discount in potential_discounts:
real_discount = rego.DiscountBase.objects.get_subclass( real_discount = rego.DiscountBase.objects.get_subclass(
pk=discount.discount.pk, pk=discount.discount.pk,
@ -63,7 +73,7 @@ def available_discounts(user, categories, products):
cart__user=user, cart__user=user,
cart__active=False, # Only past carts count cart__active=False, # Only past carts count
cart__released=False, # You can reuse refunded discounts cart__released=False, # You can reuse refunded discounts
discount=discount.discount, discount=real_discount,
) )
agg = past_uses.aggregate(Sum("quantity")) agg = past_uses.aggregate(Sum("quantity"))
past_use_count = agg["quantity__sum"] past_use_count = agg["quantity__sum"]

View file

@ -23,18 +23,25 @@ class ProductController(object):
if category is not None: if category is not None:
all_products = rego.Product.objects.filter(category=category) all_products = rego.Product.objects.filter(category=category)
all_products = all_products.select_related("category")
else: else:
all_products = [] all_products = []
if products is not None: if products is not None:
all_products = itertools.chain(all_products, products) 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)
)
passed_limits = set( passed_limits = set(
product product
for product in all_products for product in all_products
if CategoryController(product.category).user_quantity_remaining( if cat_quants[product.category] > 0
user
) > 0
if cls(product).user_quantity_remaining(user) > 0 if cls(product).user_quantity_remaining(user) > 0
) )

View file

@ -30,7 +30,7 @@ def items_pending(context):
all_items = rego.ProductItem.objects.filter( all_items = rego.ProductItem.objects.filter(
cart__user=context.request.user, cart__user=context.request.user,
cart__active=True, cart__active=True,
) ).select_related("product", "product__category")
return all_items return all_items
@ -42,17 +42,18 @@ def items_purchased(context, category=None):
all_items = rego.ProductItem.objects.filter( all_items = rego.ProductItem.objects.filter(
cart__user=context.request.user, cart__user=context.request.user,
cart__active=False, cart__active=False,
) cart__released=False,
).select_related("product", "product__category")
if category: if category:
all_items = all_items.filter(product__category=category) all_items = all_items.filter(product__category=category)
products = set(item.product for item in all_items) pq = all_items.values("product").annotate(quantity=Sum("quantity")).all()
products = rego.Product.objects.all()
out = [] out = []
for product in products: for item in pq:
pp = all_items.filter(product=product) prod = products.get(pk=item["product"])
quantity = pp.aggregate(Sum("quantity"))["quantity__sum"] out.append(ProductAndQuantity(prod, item["quantity"]))
out.append(ProductAndQuantity(product, quantity))
return out return out

View file

@ -115,11 +115,20 @@ def guided_registration(request, page_id=0):
current_step = 3 current_step = 3
title = "Additional items" title = "Additional items"
for category in cats: all_products = rego.Product.objects.filter(
products = ProductController.available_products( category__in=cats,
).select_related("category")
available_products = set(ProductController.available_products(
request.user, request.user,
category=category, products=all_products,
) ))
for category in cats:
products = [
i for i in available_products
if i.category == category
]
prefix = "category_" + str(category.id) prefix = "category_" + str(category.id)
p = handle_products(request, category, products, prefix) p = handle_products(request, category, products, prefix)
@ -280,15 +289,17 @@ def handle_products(request, category, products, prefix):
items = rego.ProductItem.objects.filter( items = rego.ProductItem.objects.filter(
product__in=products, product__in=products,
cart=current_cart.cart, cart=current_cart.cart,
) ).select_related("product")
quantities = [] quantities = []
for product in products: seen = set()
# Only add items that are enabled.
try: for item in items:
quantity = items.get(product=product).quantity quantities.append((item.product, item.quantity))
except ObjectDoesNotExist: seen.add(item.product)
quantity = 0
quantities.append((product, quantity)) zeros = set(products) - seen
for product in zeros:
quantities.append((product, 0))
products_form = ProductsForm( products_form = ProductsForm(
request.POST or None, request.POST or None,
@ -323,8 +334,12 @@ def handle_products(request, category, products, prefix):
def set_quantities_from_products_form(products_form, current_cart): def set_quantities_from_products_form(products_form, current_cart):
quantities = list(products_form.product_quantities()) quantities = list(products_form.product_quantities())
pks = [i[0] for i in quantities]
products = rego.Product.objects.filter(id__in=pks).select_related("category")
product_quantities = [ product_quantities = [
(rego.Product.objects.get(pk=i[0]), i[1]) for i in quantities (products.get(pk=i[0]), i[1]) for i in quantities
] ]
field_names = dict( field_names = dict(
(i[0][0], i[1][2]) for i in zip(product_quantities, quantities) (i[0][0], i[1][2]) for i in zip(product_quantities, quantities)