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 = [] | ||||
| 
 | ||||
|         # 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]) | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Christopher Neugebauer
						Christopher Neugebauer