Merge branch 'refactor_limits_testing'
This commit is contained in:
		
						commit
						8b13bb9bc5
					
				
					 15 changed files with 543 additions and 327 deletions
				
			
		|  | @ -1,13 +1,18 @@ | |||
| import collections | ||||
| import datetime | ||||
| import discount | ||||
| import itertools | ||||
| 
 | ||||
| from django.core.exceptions import ObjectDoesNotExist | ||||
| from django.core.exceptions import ValidationError | ||||
| from django.db import transaction | ||||
| from django.db.models import Max | ||||
| from django.utils import timezone | ||||
| 
 | ||||
| from registrasion import models as rego | ||||
| from registrasion.exceptions import CartValidationError | ||||
| 
 | ||||
| from category import CategoryController | ||||
| from conditions import ConditionController | ||||
| from product import ProductController | ||||
| 
 | ||||
|  | @ -17,8 +22,8 @@ class CartController(object): | |||
|     def __init__(self, cart): | ||||
|         self.cart = cart | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def for_user(user): | ||||
|     @classmethod | ||||
|     def for_user(cls, user): | ||||
|         ''' Returns the user's current cart, or creates a new cart | ||||
|         if there isn't one ready yet. ''' | ||||
| 
 | ||||
|  | @ -31,7 +36,7 @@ class CartController(object): | |||
|                 reservation_duration=datetime.timedelta(), | ||||
|                  ) | ||||
|             existing.save() | ||||
|         return CartController(existing) | ||||
|         return cls(existing) | ||||
| 
 | ||||
|     def extend_reservation(self): | ||||
|         ''' Updates the cart's time last updated value, which is used to | ||||
|  | @ -57,70 +62,115 @@ class CartController(object): | |||
| 
 | ||||
|     def end_batch(self): | ||||
|         ''' Performs operations that occur occur at the end of a batch of | ||||
|         product changes/voucher applications etc. ''' | ||||
|         product changes/voucher applications etc. | ||||
|         THIS SHOULD BE PRIVATE | ||||
|         ''' | ||||
| 
 | ||||
|         self.recalculate_discounts() | ||||
| 
 | ||||
|         self.extend_reservation() | ||||
|         self.cart.revision += 1 | ||||
|         self.cart.save() | ||||
| 
 | ||||
|     def set_quantity(self, product, quantity, batched=False): | ||||
|         ''' Sets the _quantity_ of the given _product_ in the cart to the given | ||||
|         _quantity_. ''' | ||||
|     @transaction.atomic | ||||
|     def set_quantities(self, product_quantities): | ||||
|         ''' Sets the quantities on each of the products on each of the | ||||
|         products specified. Raises an exception (ValidationError) if a limit | ||||
|         is violated. `product_quantities` is an iterable of (product, quantity) | ||||
|         pairs. ''' | ||||
| 
 | ||||
|         if quantity < 0: | ||||
|             raise ValidationError("Cannot have fewer than 0 items in cart.") | ||||
|         items_in_cart = rego.ProductItem.objects.filter(cart=self.cart) | ||||
|         product_quantities = list(product_quantities) | ||||
| 
 | ||||
|         try: | ||||
|             product_item = rego.ProductItem.objects.get( | ||||
|                 cart=self.cart, | ||||
|                 product=product) | ||||
|             old_quantity = product_item.quantity | ||||
|         # n.b need to add have the existing items first so that the new | ||||
|         # items override the old ones. | ||||
|         all_product_quantities = dict(itertools.chain( | ||||
|             ((i.product, i.quantity) for i in items_in_cart.all()), | ||||
|             product_quantities, | ||||
|         )).items() | ||||
| 
 | ||||
|             if quantity == 0: | ||||
|                 product_item.delete() | ||||
|                 return | ||||
|         except ObjectDoesNotExist: | ||||
|             if quantity == 0: | ||||
|                 return | ||||
|         # Validate that the limits we're adding are OK | ||||
|         self._test_limits(all_product_quantities) | ||||
| 
 | ||||
|             product_item = rego.ProductItem.objects.create( | ||||
|                 cart=self.cart, | ||||
|                 product=product, | ||||
|                 quantity=0, | ||||
|         for product, quantity in product_quantities: | ||||
|             try: | ||||
|                 product_item = rego.ProductItem.objects.get( | ||||
|                     cart=self.cart, | ||||
|                     product=product, | ||||
|                 ) | ||||
|                 product_item.quantity = quantity | ||||
|                 product_item.save() | ||||
|             except ObjectDoesNotExist: | ||||
|                 rego.ProductItem.objects.create( | ||||
|                     cart=self.cart, | ||||
|                     product=product, | ||||
|                     quantity=quantity, | ||||
|                 ) | ||||
| 
 | ||||
|         items_in_cart.filter(quantity=0).delete() | ||||
| 
 | ||||
|         self.end_batch() | ||||
| 
 | ||||
|     def _test_limits(self, product_quantities): | ||||
|         ''' Tests that the quantity changes we intend to make do not violate | ||||
|         the limits and enabling conditions imposed on the products. ''' | ||||
| 
 | ||||
|         errors = [] | ||||
| 
 | ||||
|         # Test each product limit here | ||||
|         for product, quantity in product_quantities: | ||||
|             if quantity < 0: | ||||
|                 # TODO: batch errors | ||||
|                 errors.append((product, "Value must be zero or greater.")) | ||||
| 
 | ||||
|             prod = ProductController(product) | ||||
|             limit = prod.user_quantity_remaining(self.cart.user) | ||||
| 
 | ||||
|             if quantity > limit: | ||||
|                 # TODO: batch errors | ||||
|                 errors.append(( | ||||
|                     product, | ||||
|                     "You may only have %d of product: %s" % ( | ||||
|                         limit, product, | ||||
|                     ) | ||||
|                 )) | ||||
| 
 | ||||
|         # Collect by category | ||||
|         by_cat = collections.defaultdict(list) | ||||
|         for product, quantity in product_quantities: | ||||
|             by_cat[product.category].append((product, quantity)) | ||||
| 
 | ||||
|         # Test each category limit here | ||||
|         for category in by_cat: | ||||
|             ctrl = CategoryController(category) | ||||
|             limit = ctrl.user_quantity_remaining(self.cart.user) | ||||
| 
 | ||||
|             # Get the amount so far in the cart | ||||
|             to_add = sum(i[1] for i in by_cat[category]) | ||||
| 
 | ||||
|             if to_add > limit: | ||||
|                 # TODO: batch errors | ||||
|                 errors.append(( | ||||
|                     category, | ||||
|                     "You may only have %d items in category: %s" % ( | ||||
|                         limit, category.name, | ||||
|                     ) | ||||
|                 )) | ||||
| 
 | ||||
|         # Test the enabling conditions | ||||
|         errs = ConditionController.test_enabling_conditions( | ||||
|             self.cart.user, | ||||
|             product_quantities=product_quantities, | ||||
|         ) | ||||
| 
 | ||||
|         if errs: | ||||
|             # TODO: batch errors | ||||
|             errors.append( | ||||
|                 ("enabling_conditions", "An enabling condition failed") | ||||
|             ) | ||||
| 
 | ||||
|             old_quantity = 0 | ||||
| 
 | ||||
|         # Validate the addition to the cart | ||||
|         adjustment = quantity - old_quantity | ||||
|         prod = ProductController(product) | ||||
| 
 | ||||
|         if not prod.can_add_with_enabling_conditions( | ||||
|                 self.cart.user, adjustment): | ||||
|             raise ValidationError("Not enough of that product left (ec)") | ||||
| 
 | ||||
|         if not prod.user_can_add_within_limit(self.cart.user, adjustment): | ||||
|             raise ValidationError("Not enough of that product left (user)") | ||||
| 
 | ||||
|         product_item.quantity = quantity | ||||
|         product_item.save() | ||||
| 
 | ||||
|         if not batched: | ||||
|             self.end_batch() | ||||
| 
 | ||||
|     def add_to_cart(self, product, quantity): | ||||
|         ''' Adds _quantity_ of the given _product_ to the cart. Raises | ||||
|         ValidationError if constraints are violated.''' | ||||
| 
 | ||||
|         try: | ||||
|             product_item = rego.ProductItem.objects.get( | ||||
|                 cart=self.cart, | ||||
|                 product=product) | ||||
|             old_quantity = product_item.quantity | ||||
|         except ObjectDoesNotExist: | ||||
|             old_quantity = 0 | ||||
|         self.set_quantity(product, old_quantity + quantity) | ||||
|         if errors: | ||||
|             raise CartValidationError(errors) | ||||
| 
 | ||||
|     def apply_voucher(self, voucher_code): | ||||
|         ''' Applies the voucher with the given code to this cart. ''' | ||||
|  | @ -153,22 +203,12 @@ class CartController(object): | |||
|         ''' Determines whether the status of the current cart is valid; | ||||
|         this is normally called before generating or paying an invoice ''' | ||||
| 
 | ||||
|         is_reserved = self.cart in rego.Cart.reserved_carts() | ||||
| 
 | ||||
|         # TODO: validate vouchers | ||||
| 
 | ||||
|         items = rego.ProductItem.objects.filter(cart=self.cart) | ||||
|         for item in items: | ||||
|             # NOTE: per-user limits are tested at add time | ||||
|             # and are unliklely to change | ||||
|             prod = ProductController(item.product) | ||||
| 
 | ||||
|             # If the cart is not reserved, we need to see if we can re-reserve | ||||
|             quantity = 0 if is_reserved else item.quantity | ||||
| 
 | ||||
|             if not prod.can_add_with_enabling_conditions( | ||||
|                     self.cart.user, quantity): | ||||
|                 raise ValidationError("Products are no longer available") | ||||
|         product_quantities = list((i.product, i.quantity) for i in items) | ||||
|         self._test_limits(product_quantities) | ||||
| 
 | ||||
|         # Validate the discounts | ||||
|         discount_items = rego.DiscountItem.objects.filter(cart=self.cart) | ||||
|  | @ -183,9 +223,7 @@ class CartController(object): | |||
|                 pk=discount.pk) | ||||
|             cond = ConditionController.for_condition(real_discount) | ||||
| 
 | ||||
|             quantity = 0 if is_reserved else discount_item.quantity | ||||
| 
 | ||||
|             if not cond.is_met(self.cart.user, quantity): | ||||
|             if not cond.is_met(self.cart.user): | ||||
|                 raise ValidationError("Discounts are no longer available") | ||||
| 
 | ||||
|     def recalculate_discounts(self): | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| from .product import ProductController | ||||
| 
 | ||||
| from registrasion import models as rego | ||||
| 
 | ||||
| from django.db.models import Sum | ||||
| 
 | ||||
| 
 | ||||
| class AllProducts(object): | ||||
|     pass | ||||
|  | @ -9,12 +9,18 @@ class AllProducts(object): | |||
| 
 | ||||
| class CategoryController(object): | ||||
| 
 | ||||
|     def __init__(self, category): | ||||
|         self.category = category | ||||
| 
 | ||||
|     @classmethod | ||||
|     def available_categories(cls, user, products=AllProducts): | ||||
|         ''' Returns the categories available to the user. Specify `products` if | ||||
|         you want to restrict to just the categories that hold the specified | ||||
|         products, otherwise it'll do all. ''' | ||||
| 
 | ||||
|         # STOPGAP -- this needs to be elsewhere tbqh | ||||
|         from product import ProductController | ||||
| 
 | ||||
|         if products is AllProducts: | ||||
|             products = rego.Product.objects.all() | ||||
| 
 | ||||
|  | @ -24,3 +30,27 @@ class CategoryController(object): | |||
|         ) | ||||
| 
 | ||||
|         return set(i.category for i in available) | ||||
| 
 | ||||
|     def user_quantity_remaining(self, user): | ||||
|         ''' Returns the number of items from this category that the user may | ||||
|         add in the current cart. ''' | ||||
| 
 | ||||
|         cat_limit = self.category.limit_per_user | ||||
| 
 | ||||
|         if cat_limit is None: | ||||
|             # We don't need to waste the following queries | ||||
|             return 99999999 | ||||
| 
 | ||||
|         carts = rego.Cart.objects.filter( | ||||
|             user=user, | ||||
|             active=False, | ||||
|             released=False, | ||||
|         ) | ||||
| 
 | ||||
|         items = rego.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,10 +1,23 @@ | |||
| from django.db.models import Q | ||||
| import itertools | ||||
| 
 | ||||
| from collections import defaultdict | ||||
| from collections import namedtuple | ||||
| 
 | ||||
| from django.db.models import Sum | ||||
| from django.utils import timezone | ||||
| 
 | ||||
| from registrasion import models as rego | ||||
| 
 | ||||
| 
 | ||||
| ConditionAndRemainder = namedtuple( | ||||
|     "ConditionAndRemainder", | ||||
|     ( | ||||
|         "condition", | ||||
|         "remainder", | ||||
|     ), | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
| class ConditionController(object): | ||||
|     ''' Base class for testing conditions that activate EnablingCondition | ||||
|     or Discount objects. ''' | ||||
|  | @ -19,9 +32,9 @@ class ConditionController(object): | |||
|             rego.IncludedProductDiscount: ProductConditionController, | ||||
|             rego.ProductEnablingCondition: ProductConditionController, | ||||
|             rego.TimeOrStockLimitDiscount: | ||||
|                 TimeOrStockLimitConditionController, | ||||
|                 TimeOrStockLimitDiscountController, | ||||
|             rego.TimeOrStockLimitEnablingCondition: | ||||
|                 TimeOrStockLimitConditionController, | ||||
|                 TimeOrStockLimitEnablingConditionController, | ||||
|             rego.VoucherDiscount: VoucherConditionController, | ||||
|             rego.VoucherEnablingCondition: VoucherConditionController, | ||||
|         } | ||||
|  | @ -31,8 +44,102 @@ class ConditionController(object): | |||
|         except KeyError: | ||||
|             return ConditionController() | ||||
| 
 | ||||
|     def is_met(self, user, quantity): | ||||
|         return True | ||||
|     @classmethod | ||||
|     def test_enabling_conditions( | ||||
|             cls, user, products=None, product_quantities=None): | ||||
|         ''' Evaluates all of the enabling conditions on the given products. | ||||
| 
 | ||||
|         If `product_quantities` is supplied, the condition is only met if it | ||||
|         will permit the sum of the product quantities for all of the products | ||||
|         it covers. Otherwise, it will be met if at least one item can be | ||||
|         accepted. | ||||
| 
 | ||||
|         If all enabling conditions pass, an empty list is returned, otherwise | ||||
|         a list is returned containing all of the products that are *not | ||||
|         enabled*. ''' | ||||
| 
 | ||||
|         if products is not None and product_quantities is not None: | ||||
|             raise ValueError("Please specify only products or " | ||||
|                              "product_quantities") | ||||
|         elif products is None: | ||||
|             products = set(i[0] for i in product_quantities) | ||||
|             quantities = dict((product, quantity) | ||||
|                               for product, quantity in product_quantities) | ||||
|         elif product_quantities is None: | ||||
|             products = set(products) | ||||
|             quantities = {} | ||||
| 
 | ||||
|         # Get the conditions covered by the products themselves | ||||
|         all_conditions = [ | ||||
|             product.enablingconditionbase_set.select_subclasses() | | ||||
|             product.category.enablingconditionbase_set.select_subclasses() | ||||
|             for product in products | ||||
|         ] | ||||
|         all_conditions = set(itertools.chain(*all_conditions)) | ||||
| 
 | ||||
|         # All mandatory conditions on a product need to be met | ||||
|         mandatory = defaultdict(lambda: True) | ||||
|         # At least one non-mandatory condition on a product must be met | ||||
|         # if there are no mandatory conditions | ||||
|         non_mandatory = defaultdict(lambda: False) | ||||
| 
 | ||||
|         for condition in all_conditions: | ||||
|             cond = cls.for_condition(condition) | ||||
|             remainder = cond.user_quantity_remaining(user) | ||||
| 
 | ||||
|             # Get all products covered by this condition, and the products | ||||
|             # from the categories covered by this condition | ||||
|             cond_products = condition.products.all() | ||||
|             from_category = rego.Product.objects.filter( | ||||
|                 category__in=condition.categories.all(), | ||||
|             ).all() | ||||
|             all_products = set(itertools.chain(cond_products, from_category)) | ||||
| 
 | ||||
|             # Remove the products that we aren't asking about | ||||
|             all_products = all_products & products | ||||
| 
 | ||||
|             if quantities: | ||||
|                 consumed = sum(quantities[i] for i in all_products) | ||||
|             else: | ||||
|                 consumed = 1 | ||||
|             met = consumed <= remainder | ||||
| 
 | ||||
|             for product in all_products: | ||||
|                 if condition.mandatory: | ||||
|                     mandatory[product] &= met | ||||
|                 else: | ||||
|                     non_mandatory[product] |= met | ||||
| 
 | ||||
|         valid = defaultdict(lambda: True) | ||||
|         for product in itertools.chain(mandatory, non_mandatory): | ||||
|             if product in mandatory: | ||||
|                 # If there's a mandatory condition, all must be met | ||||
|                 valid[product] = mandatory[product] | ||||
|             else: | ||||
|                 # Otherwise, we need just one non-mandatory condition met | ||||
|                 valid[product] = non_mandatory[product] | ||||
| 
 | ||||
|         error_fields = [product for product in valid if not valid[product]] | ||||
|         return error_fields | ||||
| 
 | ||||
|     def user_quantity_remaining(self, user): | ||||
|         ''' Returns the number of items covered by this enabling condition the | ||||
|         user can add to the current cart. This default implementation returns | ||||
|         a big number if is_met() is true, otherwise 0. | ||||
| 
 | ||||
|         Either this method, or is_met() must be overridden in subclasses. | ||||
|         ''' | ||||
| 
 | ||||
|         return 99999999 if self.is_met(user) else 0 | ||||
| 
 | ||||
|     def is_met(self, user): | ||||
|         ''' Returns True if this enabling condition is met, otherwise returns | ||||
|         False. | ||||
| 
 | ||||
|         Either this method, or user_quantity_remaining() must be overridden | ||||
|         in subclasses. | ||||
|         ''' | ||||
|         return self.user_quantity_remaining(user) > 0 | ||||
| 
 | ||||
| 
 | ||||
| class CategoryConditionController(ConditionController): | ||||
|  | @ -40,7 +147,7 @@ class CategoryConditionController(ConditionController): | |||
|     def __init__(self, condition): | ||||
|         self.condition = condition | ||||
| 
 | ||||
|     def is_met(self, user, quantity): | ||||
|     def is_met(self, user): | ||||
|         ''' returns True if the user has a product from a category that invokes | ||||
|         this condition in one of their carts ''' | ||||
| 
 | ||||
|  | @ -48,11 +155,11 @@ class CategoryConditionController(ConditionController): | |||
|         enabling_products = rego.Product.objects.filter( | ||||
|             category=self.condition.enabling_category, | ||||
|         ) | ||||
|         products = rego.ProductItem.objects.filter( | ||||
|             cart=carts, | ||||
|         products_count = rego.ProductItem.objects.filter( | ||||
|             cart__in=carts, | ||||
|             product__in=enabling_products, | ||||
|         ) | ||||
|         return len(products) > 0 | ||||
|         ).count() | ||||
|         return products_count > 0 | ||||
| 
 | ||||
| 
 | ||||
| class ProductConditionController(ConditionController): | ||||
|  | @ -62,41 +169,36 @@ class ProductConditionController(ConditionController): | |||
|     def __init__(self, condition): | ||||
|         self.condition = condition | ||||
| 
 | ||||
|     def is_met(self, user, quantity): | ||||
|     def is_met(self, user): | ||||
|         ''' returns True if the user has a product that invokes this | ||||
|         condition in one of their carts ''' | ||||
| 
 | ||||
|         carts = rego.Cart.objects.filter(user=user, released=False) | ||||
|         products = rego.ProductItem.objects.filter( | ||||
|             cart=carts, | ||||
|         products_count = rego.ProductItem.objects.filter( | ||||
|             cart__in=carts, | ||||
|             product__in=self.condition.enabling_products.all(), | ||||
|         ) | ||||
|         return len(products) > 0 | ||||
|         ).count() | ||||
|         return products_count > 0 | ||||
| 
 | ||||
| 
 | ||||
| class TimeOrStockLimitConditionController(ConditionController): | ||||
|     ''' Condition tests for TimeOrStockLimit EnablingCondition and | ||||
|     ''' Common condition tests for TimeOrStockLimit EnablingCondition and | ||||
|     Discount.''' | ||||
| 
 | ||||
|     def __init__(self, ceiling): | ||||
|         self.ceiling = ceiling | ||||
| 
 | ||||
|     def is_met(self, user, quantity): | ||||
|         ''' returns True if adding _quantity_ of _product_ will not vioilate | ||||
|         this ceiling. ''' | ||||
|     def user_quantity_remaining(self, user): | ||||
|         ''' returns 0 if the date range is violated, otherwise, it will return | ||||
|         the quantity remaining under the stock limit. ''' | ||||
| 
 | ||||
|         # Test date range | ||||
|         if not self.test_date_range(): | ||||
|             return False | ||||
|         if not self._test_date_range(): | ||||
|             return 0 | ||||
| 
 | ||||
|         # Test limits | ||||
|         if not self.test_limits(quantity): | ||||
|             return False | ||||
|         return self._get_remaining_stock(user) | ||||
| 
 | ||||
|         # All limits have been met | ||||
|         return True | ||||
| 
 | ||||
|     def test_date_range(self): | ||||
|     def _test_date_range(self): | ||||
|         now = timezone.now() | ||||
| 
 | ||||
|         if self.ceiling.start_time is not None: | ||||
|  | @ -109,42 +211,49 @@ class TimeOrStockLimitConditionController(ConditionController): | |||
| 
 | ||||
|         return True | ||||
| 
 | ||||
|     def _products(self): | ||||
|         ''' Abstracts away the product list, becuase enabling conditions | ||||
|         list products differently to discounts. ''' | ||||
|         if isinstance(self.ceiling, rego.TimeOrStockLimitEnablingCondition): | ||||
|             category_products = rego.Product.objects.filter( | ||||
|                 category=self.ceiling.categories.all(), | ||||
|             ) | ||||
|             return self.ceiling.products.all() | category_products | ||||
|         else: | ||||
|             categories = rego.Category.objects.filter( | ||||
|                 discountforcategory__discount=self.ceiling, | ||||
|             ) | ||||
|             return rego.Product.objects.filter( | ||||
|                 Q(discountforproduct__discount=self.ceiling) | | ||||
|                 Q(category=categories.all()) | ||||
|             ) | ||||
|     def _get_remaining_stock(self, user): | ||||
|         ''' Returns the stock that remains under this ceiling, excluding the | ||||
|         user's current cart. ''' | ||||
| 
 | ||||
|     def test_limits(self, quantity): | ||||
|         if self.ceiling.limit is None: | ||||
|             return True | ||||
|             return 99999999 | ||||
| 
 | ||||
|         # We care about all reserved carts, but not the user's current cart | ||||
|         reserved_carts = rego.Cart.reserved_carts() | ||||
|         product_items = rego.ProductItem.objects.filter( | ||||
|             product__in=self._products().all(), | ||||
|         reserved_carts = reserved_carts.exclude( | ||||
|             user=user, | ||||
|             active=True, | ||||
|         ) | ||||
|         product_items = product_items.filter(cart=reserved_carts) | ||||
| 
 | ||||
|         agg = product_items.aggregate(Sum("quantity")) | ||||
|         count = agg["quantity__sum"] | ||||
|         if count is None: | ||||
|             count = 0 | ||||
|         items = self._items() | ||||
|         items = items.filter(cart__in=reserved_carts) | ||||
|         count = items.aggregate(Sum("quantity"))["quantity__sum"] or 0 | ||||
| 
 | ||||
|         if count + quantity > self.ceiling.limit: | ||||
|             return False | ||||
|         return self.ceiling.limit - count | ||||
| 
 | ||||
|         return True | ||||
| 
 | ||||
| class TimeOrStockLimitEnablingConditionController( | ||||
|         TimeOrStockLimitConditionController): | ||||
| 
 | ||||
|     def _items(self): | ||||
|         category_products = rego.Product.objects.filter( | ||||
|             category__in=self.ceiling.categories.all(), | ||||
|         ) | ||||
|         products = self.ceiling.products.all() | category_products | ||||
| 
 | ||||
|         product_items = rego.ProductItem.objects.filter( | ||||
|             product__in=products.all(), | ||||
|         ) | ||||
|         return product_items | ||||
| 
 | ||||
| 
 | ||||
| class TimeOrStockLimitDiscountController(TimeOrStockLimitConditionController): | ||||
| 
 | ||||
|     def _items(self): | ||||
|         discount_items = rego.DiscountItem.objects.filter( | ||||
|             discount=self.ceiling, | ||||
|         ) | ||||
|         return discount_items | ||||
| 
 | ||||
| 
 | ||||
| class VoucherConditionController(ConditionController): | ||||
|  | @ -153,10 +262,10 @@ class VoucherConditionController(ConditionController): | |||
|     def __init__(self, condition): | ||||
|         self.condition = condition | ||||
| 
 | ||||
|     def is_met(self, user, quantity): | ||||
|     def is_met(self, user): | ||||
|         ''' returns True if the user has the given voucher attached. ''' | ||||
|         carts = rego.Cart.objects.filter( | ||||
|         carts_count = rego.Cart.objects.filter( | ||||
|             user=user, | ||||
|             vouchers=self.condition.voucher, | ||||
|         ) | ||||
|         return len(carts) > 0 | ||||
|         ).count() | ||||
|         return carts_count > 0 | ||||
|  |  | |||
|  | @ -75,7 +75,7 @@ def available_discounts(user, categories, products): | |||
|             pass | ||||
|         elif real_discount not in failed_discounts: | ||||
|             # This clause is still available | ||||
|             if real_discount in accepted_discounts or cond.is_met(user, 0): | ||||
|             if real_discount in accepted_discounts or cond.is_met(user): | ||||
|                 # This clause is valid for this user | ||||
|                 discounts.append(DiscountAndQuantity( | ||||
|                     discount=real_discount, | ||||
|  |  | |||
|  | @ -1,9 +1,9 @@ | |||
| import itertools | ||||
| 
 | ||||
| from django.db.models import Q | ||||
| from django.db.models import Sum | ||||
| from registrasion import models as rego | ||||
| 
 | ||||
| from category import CategoryController | ||||
| from conditions import ConditionController | ||||
| 
 | ||||
| 
 | ||||
|  | @ -29,80 +29,45 @@ class ProductController(object): | |||
|         if products is not None: | ||||
|             all_products = itertools.chain(all_products, products) | ||||
| 
 | ||||
|         out = [ | ||||
|         passed_limits = set( | ||||
|             product | ||||
|             for product in all_products | ||||
|             if cls(product).user_can_add_within_limit(user, 1, past_carts=True) | ||||
|             if cls(product).can_add_with_enabling_conditions(user, 0) | ||||
|         ] | ||||
|             if CategoryController(product.category).user_quantity_remaining( | ||||
|                 user | ||||
|             ) > 0 | ||||
|             if cls(product).user_quantity_remaining(user) > 0 | ||||
|         ) | ||||
| 
 | ||||
|         failed_conditions = set(ConditionController.test_enabling_conditions( | ||||
|             user, products=passed_limits | ||||
|         )) | ||||
| 
 | ||||
|         out = list(passed_limits - failed_conditions) | ||||
|         out.sort(key=lambda product: product.order) | ||||
| 
 | ||||
|         return out | ||||
| 
 | ||||
|     def user_can_add_within_limit(self, user, quantity, past_carts=False): | ||||
|         ''' Return true if the user is able to add _quantity_ to their count of | ||||
|         this Product without exceeding _limit_per_user_.''' | ||||
|     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 | ||||
| 
 | ||||
|         if prod_limit is None: | ||||
|             # Don't need to run the remaining queries | ||||
|             return 999999  # We can do better | ||||
| 
 | ||||
|         carts = rego.Cart.objects.filter( | ||||
|             user=user, | ||||
|             active=False, | ||||
|             released=False, | ||||
|         ) | ||||
|         if past_carts: | ||||
|             carts = carts.filter(active=False) | ||||
| 
 | ||||
|         items = rego.ProductItem.objects.filter( | ||||
|             cart__in=carts, | ||||
|             product=self.product, | ||||
|         ) | ||||
| 
 | ||||
|         prod_items = items.filter(product=self.product) | ||||
|         cat_items = items.filter(product__category=self.product.category) | ||||
|         prod_count = items.aggregate(Sum("quantity"))["quantity__sum"] or 0 | ||||
| 
 | ||||
|         prod_count = prod_items.aggregate(Sum("quantity"))["quantity__sum"] | ||||
|         cat_count = cat_items.aggregate(Sum("quantity"))["quantity__sum"] | ||||
| 
 | ||||
|         if prod_count is None: | ||||
|             prod_count = 0 | ||||
|         if cat_count is None: | ||||
|             cat_count = 0 | ||||
| 
 | ||||
|         prod_limit = self.product.limit_per_user | ||||
|         prod_met = prod_limit is None or quantity + prod_count <= prod_limit | ||||
| 
 | ||||
|         cat_limit = self.product.category.limit_per_user | ||||
|         cat_met = cat_limit is None or quantity + cat_count <= cat_limit | ||||
| 
 | ||||
|         if prod_met and cat_met: | ||||
|             return True | ||||
|         else: | ||||
|             return False | ||||
| 
 | ||||
|     def can_add_with_enabling_conditions(self, user, quantity): | ||||
|         ''' Returns true if the user is able to add _quantity_ to their count | ||||
|         of this Product without exceeding the ceilings the product is attached | ||||
|         to. ''' | ||||
| 
 | ||||
|         conditions = rego.EnablingConditionBase.objects.filter( | ||||
|             Q(products=self.product) | Q(categories=self.product.category) | ||||
|         ).select_subclasses() | ||||
| 
 | ||||
|         mandatory_violated = False | ||||
|         non_mandatory_met = False | ||||
| 
 | ||||
|         for condition in conditions: | ||||
|             cond = ConditionController.for_condition(condition) | ||||
|             met = cond.is_met(user, quantity) | ||||
| 
 | ||||
|             if condition.mandatory and not met: | ||||
|                 mandatory_violated = True | ||||
|                 break | ||||
|             if met: | ||||
|                 non_mandatory_met = True | ||||
| 
 | ||||
|         if mandatory_violated: | ||||
|             # All mandatory conditions must be met | ||||
|             return False | ||||
| 
 | ||||
|         if len(conditions) > 0 and not non_mandatory_met: | ||||
|             # If there's any non-mandatory conditions, one must be met | ||||
|             return False | ||||
| 
 | ||||
|         return True | ||||
|         return prod_limit - prod_count | ||||
|  |  | |||
							
								
								
									
										4
									
								
								registrasion/exceptions.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								registrasion/exceptions.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,4 @@ | |||
| from django.core.exceptions import ValidationError | ||||
| 
 | ||||
| class CartValidationError(ValidationError): | ||||
|     pass | ||||
							
								
								
									
										26
									
								
								registrasion/tests/cart_controller_helper.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								registrasion/tests/cart_controller_helper.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,26 @@ | |||
| from registrasion.controllers.cart import CartController | ||||
| from registrasion import models as rego | ||||
| 
 | ||||
| from django.core.exceptions import ObjectDoesNotExist | ||||
| 
 | ||||
| 
 | ||||
| class TestingCartController(CartController): | ||||
| 
 | ||||
|     def set_quantity(self, product, quantity, batched=False): | ||||
|         ''' Sets the _quantity_ of the given _product_ in the cart to the given | ||||
|         _quantity_. ''' | ||||
| 
 | ||||
|         self.set_quantities(((product, quantity),)) | ||||
| 
 | ||||
|     def add_to_cart(self, product, quantity): | ||||
|         ''' Adds _quantity_ of the given _product_ to the cart. Raises | ||||
|         ValidationError if constraints are violated.''' | ||||
| 
 | ||||
|         try: | ||||
|             product_item = rego.ProductItem.objects.get( | ||||
|                 cart=self.cart, | ||||
|                 product=product) | ||||
|             old_quantity = product_item.quantity | ||||
|         except ObjectDoesNotExist: | ||||
|             old_quantity = 0 | ||||
|         self.set_quantity(product, old_quantity + quantity) | ||||
|  | @ -8,9 +8,9 @@ from django.core.exceptions import ValidationError | |||
| from django.test import TestCase | ||||
| 
 | ||||
| from registrasion import models as rego | ||||
| from registrasion.controllers.cart import CartController | ||||
| from registrasion.controllers.product import ProductController | ||||
| 
 | ||||
| from cart_controller_helper import TestingCartController | ||||
| from patch_datetime import SetTimeMixin | ||||
| 
 | ||||
| UTC = pytz.timezone('UTC') | ||||
|  | @ -72,6 +72,15 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase): | |||
|         cls.PROD_4.price = Decimal("5.00") | ||||
|         cls.PROD_4.save() | ||||
| 
 | ||||
|         # Burn through some carts -- this made some past EC tests fail | ||||
|         current_cart = TestingCartController.for_user(cls.USER_1) | ||||
|         current_cart.cart.active = False | ||||
|         current_cart.cart.save() | ||||
| 
 | ||||
|         current_cart = TestingCartController.for_user(cls.USER_2) | ||||
|         current_cart.cart.active = False | ||||
|         current_cart.cart.save() | ||||
| 
 | ||||
|     @classmethod | ||||
|     def make_ceiling(cls, name, limit=None, start_time=None, end_time=None): | ||||
|         limit_ceiling = rego.TimeOrStockLimitEnablingCondition.objects.create( | ||||
|  | @ -101,7 +110,8 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase): | |||
| 
 | ||||
|     @classmethod | ||||
|     def make_discount_ceiling( | ||||
|             cls, name, limit=None, start_time=None, end_time=None): | ||||
|             cls, name, limit=None, start_time=None, end_time=None, | ||||
|             percentage=100): | ||||
|         limit_ceiling = rego.TimeOrStockLimitDiscount.objects.create( | ||||
|             description=name, | ||||
|             start_time=start_time, | ||||
|  | @ -112,29 +122,39 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase): | |||
|         rego.DiscountForProduct.objects.create( | ||||
|             discount=limit_ceiling, | ||||
|             product=cls.PROD_1, | ||||
|             percentage=100, | ||||
|             percentage=percentage, | ||||
|             quantity=10, | ||||
|         ).save() | ||||
| 
 | ||||
|     @classmethod | ||||
|     def new_voucher(self, code="VOUCHER", limit=1): | ||||
|         voucher = rego.Voucher.objects.create( | ||||
|             recipient="Voucher recipient", | ||||
|             code=code, | ||||
|             limit=limit, | ||||
|         ) | ||||
|         voucher.save() | ||||
|         return voucher | ||||
| 
 | ||||
| 
 | ||||
| class BasicCartTests(RegistrationCartTestCase): | ||||
| 
 | ||||
|     def test_get_cart(self): | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
| 
 | ||||
|         current_cart.cart.active = False | ||||
|         current_cart.cart.save() | ||||
| 
 | ||||
|         old_cart = current_cart | ||||
| 
 | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
|         self.assertNotEqual(old_cart.cart, current_cart.cart) | ||||
| 
 | ||||
|         current_cart2 = CartController.for_user(self.USER_1) | ||||
|         current_cart2 = TestingCartController.for_user(self.USER_1) | ||||
|         self.assertEqual(current_cart.cart, current_cart2.cart) | ||||
| 
 | ||||
|     def test_add_to_cart_collapses_product_items(self): | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
| 
 | ||||
|         # Add a product twice | ||||
|         current_cart.add_to_cart(self.PROD_1, 1) | ||||
|  | @ -149,7 +169,7 @@ class BasicCartTests(RegistrationCartTestCase): | |||
|         self.assertEquals(2, item.quantity) | ||||
| 
 | ||||
|     def test_set_quantity(self): | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
| 
 | ||||
|         def get_item(): | ||||
|             return rego.ProductItem.objects.get( | ||||
|  | @ -181,7 +201,7 @@ class BasicCartTests(RegistrationCartTestCase): | |||
|         self.assertEqual(2, get_item().quantity) | ||||
| 
 | ||||
|     def test_add_to_cart_product_per_user_limit(self): | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
| 
 | ||||
|         # User should be able to add 1 of PROD_1 to the current cart. | ||||
|         current_cart.add_to_cart(self.PROD_1, 1) | ||||
|  | @ -197,14 +217,14 @@ class BasicCartTests(RegistrationCartTestCase): | |||
|         current_cart.cart.active = False | ||||
|         current_cart.cart.save() | ||||
| 
 | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
|         # User should not be able to add 10 of PROD_1 to the current cart now, | ||||
|         # even though it's a new cart. | ||||
|         with self.assertRaises(ValidationError): | ||||
|             current_cart.add_to_cart(self.PROD_1, 10) | ||||
| 
 | ||||
|         # Second user should not be affected by first user's limits | ||||
|         second_user_cart = CartController.for_user(self.USER_2) | ||||
|         second_user_cart = TestingCartController.for_user(self.USER_2) | ||||
|         second_user_cart.add_to_cart(self.PROD_1, 10) | ||||
| 
 | ||||
|     def set_limits(self): | ||||
|  | @ -221,7 +241,7 @@ class BasicCartTests(RegistrationCartTestCase): | |||
|     def test_per_user_product_limit_ignored_if_blank(self): | ||||
|         self.set_limits() | ||||
| 
 | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
|         # There is no product limit on PROD_2, and there is no cat limit | ||||
|         current_cart.add_to_cart(self.PROD_2, 1) | ||||
|         # There is no product limit on PROD_3, but there is a cat limit | ||||
|  | @ -229,7 +249,7 @@ class BasicCartTests(RegistrationCartTestCase): | |||
| 
 | ||||
|     def test_per_user_category_limit_ignored_if_blank(self): | ||||
|         self.set_limits() | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
|         # There is no product limit on PROD_2, and there is no cat limit | ||||
|         current_cart.add_to_cart(self.PROD_2, 1) | ||||
|         # There is no cat limit on PROD_1, but there is a prod limit | ||||
|  | @ -238,7 +258,7 @@ class BasicCartTests(RegistrationCartTestCase): | |||
|     def test_per_user_category_limit_only(self): | ||||
|         self.set_limits() | ||||
| 
 | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
| 
 | ||||
|         # Cannot add to cart if category limit is filled by one product. | ||||
|         current_cart.set_quantity(self.PROD_3, 10) | ||||
|  | @ -255,7 +275,7 @@ class BasicCartTests(RegistrationCartTestCase): | |||
|         current_cart.cart.active = False | ||||
|         current_cart.cart.save() | ||||
| 
 | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
|         # The category limit should extend across carts | ||||
|         with self.assertRaises(ValidationError): | ||||
|             current_cart.add_to_cart(self.PROD_3, 10) | ||||
|  | @ -263,7 +283,7 @@ class BasicCartTests(RegistrationCartTestCase): | |||
|     def test_per_user_category_and_product_limits(self): | ||||
|         self.set_limits() | ||||
| 
 | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
| 
 | ||||
|         # Hit both the product and category edges: | ||||
|         current_cart.set_quantity(self.PROD_3, 4) | ||||
|  | @ -281,7 +301,7 @@ class BasicCartTests(RegistrationCartTestCase): | |||
|         current_cart.cart.active = False | ||||
|         current_cart.cart.save() | ||||
| 
 | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
|         current_cart.set_quantity(self.PROD_3, 4) | ||||
| 
 | ||||
|         with self.assertRaises(ValidationError): | ||||
|  | @ -299,7 +319,7 @@ class BasicCartTests(RegistrationCartTestCase): | |||
|                 products=[self.PROD_2, self.PROD_3, self.PROD_4], | ||||
|             ) | ||||
| 
 | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
|         prods = get_prods() | ||||
|         self.assertTrue(item in prods) | ||||
|         current_cart.add_to_cart(item, quantity) | ||||
|  | @ -308,7 +328,7 @@ class BasicCartTests(RegistrationCartTestCase): | |||
|         current_cart.cart.active = False | ||||
|         current_cart.cart.save() | ||||
| 
 | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
| 
 | ||||
|         prods = get_prods() | ||||
|         self.assertTrue(item not in prods) | ||||
|  |  | |||
|  | @ -3,10 +3,11 @@ import pytz | |||
| 
 | ||||
| from django.core.exceptions import ValidationError | ||||
| 
 | ||||
| from registrasion.controllers.cart import CartController | ||||
| 
 | ||||
| from cart_controller_helper import TestingCartController | ||||
| from test_cart import RegistrationCartTestCase | ||||
| 
 | ||||
| from registrasion import models as rego | ||||
| 
 | ||||
| UTC = pytz.timezone('UTC') | ||||
| 
 | ||||
| 
 | ||||
|  | @ -22,7 +23,7 @@ class CeilingsTestCases(RegistrationCartTestCase): | |||
| 
 | ||||
|     def __add_to_cart_test(self): | ||||
| 
 | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
| 
 | ||||
|         # User should not be able to add 10 of PROD_1 to the current cart | ||||
|         # because it is affected by limit_ceiling | ||||
|  | @ -46,7 +47,7 @@ class CeilingsTestCases(RegistrationCartTestCase): | |||
|             start_time=datetime.datetime(2015, 01, 01, tzinfo=UTC), | ||||
|             end_time=datetime.datetime(2015, 02, 01, tzinfo=UTC)) | ||||
| 
 | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
| 
 | ||||
|         # User should not be able to add whilst we're before start_time | ||||
|         self.set_time(datetime.datetime(2014, 01, 01, tzinfo=UTC)) | ||||
|  | @ -74,8 +75,8 @@ class CeilingsTestCases(RegistrationCartTestCase): | |||
| 
 | ||||
|         self.set_time(datetime.datetime(2015, 01, 01, tzinfo=UTC)) | ||||
| 
 | ||||
|         first_cart = CartController.for_user(self.USER_1) | ||||
|         second_cart = CartController.for_user(self.USER_2) | ||||
|         first_cart = TestingCartController.for_user(self.USER_1) | ||||
|         second_cart = TestingCartController.for_user(self.USER_2) | ||||
| 
 | ||||
|         first_cart.add_to_cart(self.PROD_1, 1) | ||||
| 
 | ||||
|  | @ -111,8 +112,8 @@ class CeilingsTestCases(RegistrationCartTestCase): | |||
|     def __validation_test(self): | ||||
|         self.set_time(datetime.datetime(2015, 01, 01, tzinfo=UTC)) | ||||
| 
 | ||||
|         first_cart = CartController.for_user(self.USER_1) | ||||
|         second_cart = CartController.for_user(self.USER_2) | ||||
|         first_cart = TestingCartController.for_user(self.USER_1) | ||||
|         second_cart = TestingCartController.for_user(self.USER_2) | ||||
| 
 | ||||
|         # Adding a valid product should validate. | ||||
|         first_cart.add_to_cart(self.PROD_1, 1) | ||||
|  | @ -136,13 +137,13 @@ class CeilingsTestCases(RegistrationCartTestCase): | |||
|     def test_items_released_from_ceiling_by_refund(self): | ||||
|         self.make_ceiling("Limit ceiling", limit=1) | ||||
| 
 | ||||
|         first_cart = CartController.for_user(self.USER_1) | ||||
|         first_cart = TestingCartController.for_user(self.USER_1) | ||||
|         first_cart.add_to_cart(self.PROD_1, 1) | ||||
| 
 | ||||
|         first_cart.cart.active = False | ||||
|         first_cart.cart.save() | ||||
| 
 | ||||
|         second_cart = CartController.for_user(self.USER_2) | ||||
|         second_cart = TestingCartController.for_user(self.USER_2) | ||||
|         with self.assertRaises(ValidationError): | ||||
|             second_cart.add_to_cart(self.PROD_1, 1) | ||||
| 
 | ||||
|  | @ -150,3 +151,36 @@ class CeilingsTestCases(RegistrationCartTestCase): | |||
|         first_cart.cart.save() | ||||
| 
 | ||||
|         second_cart.add_to_cart(self.PROD_1, 1) | ||||
| 
 | ||||
|     def test_discount_ceiling_only_counts_items_covered_by_ceiling(self): | ||||
|         self.make_discount_ceiling("Limit ceiling", limit=1, percentage=50) | ||||
|         voucher = self.new_voucher(code="VOUCHER") | ||||
| 
 | ||||
|         discount = rego.VoucherDiscount.objects.create( | ||||
|             description="VOUCHER RECIPIENT", | ||||
|             voucher=voucher, | ||||
|         ) | ||||
|         discount.save() | ||||
|         rego.DiscountForProduct.objects.create( | ||||
|             discount=discount, | ||||
|             product=self.PROD_1, | ||||
|             percentage=100, | ||||
|             quantity=1 | ||||
|         ).save() | ||||
| 
 | ||||
|         # Buy two of PROD_1, in separate carts: | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         # the 100% discount from the voucher should apply to the first item | ||||
|         # and not the ceiling discount. | ||||
|         cart.apply_voucher("VOUCHER") | ||||
|         cart.add_to_cart(self.PROD_1, 1) | ||||
|         self.assertEqual(1, len(cart.cart.discountitem_set.all())) | ||||
| 
 | ||||
|         cart.cart.active = False | ||||
|         cart.cart.save() | ||||
| 
 | ||||
|         # The second cart has no voucher attached, so should apply the | ||||
|         # ceiling discount | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_1, 1) | ||||
|         self.assertEqual(1, len(cart.cart.discountitem_set.all())) | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ from decimal import Decimal | |||
| 
 | ||||
| from registrasion import models as rego | ||||
| from registrasion.controllers import discount | ||||
| from registrasion.controllers.cart import CartController | ||||
| from cart_controller_helper import TestingCartController | ||||
| from registrasion.controllers.invoice import InvoiceController | ||||
| 
 | ||||
| from test_cart import RegistrationCartTestCase | ||||
|  | @ -84,7 +84,7 @@ class DiscountTestCase(RegistrationCartTestCase): | |||
|     def test_discount_is_applied(self): | ||||
|         self.add_discount_prod_1_includes_prod_2() | ||||
| 
 | ||||
|         cart = CartController.for_user(self.USER_1) | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_1, 1) | ||||
|         cart.add_to_cart(self.PROD_2, 1) | ||||
| 
 | ||||
|  | @ -94,7 +94,7 @@ class DiscountTestCase(RegistrationCartTestCase): | |||
|     def test_discount_is_applied_for_category(self): | ||||
|         self.add_discount_prod_1_includes_cat_2() | ||||
| 
 | ||||
|         cart = CartController.for_user(self.USER_1) | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_1, 1) | ||||
|         cart.add_to_cart(self.PROD_3, 1) | ||||
| 
 | ||||
|  | @ -104,7 +104,7 @@ class DiscountTestCase(RegistrationCartTestCase): | |||
|     def test_discount_does_not_apply_if_not_met(self): | ||||
|         self.add_discount_prod_1_includes_prod_2() | ||||
| 
 | ||||
|         cart = CartController.for_user(self.USER_1) | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_2, 1) | ||||
| 
 | ||||
|         # No discount should be applied as the condition is not met | ||||
|  | @ -113,7 +113,7 @@ class DiscountTestCase(RegistrationCartTestCase): | |||
|     def test_discount_applied_out_of_order(self): | ||||
|         self.add_discount_prod_1_includes_prod_2() | ||||
| 
 | ||||
|         cart = CartController.for_user(self.USER_1) | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_2, 1) | ||||
|         cart.add_to_cart(self.PROD_1, 1) | ||||
| 
 | ||||
|  | @ -123,7 +123,7 @@ class DiscountTestCase(RegistrationCartTestCase): | |||
|     def test_discounts_collapse(self): | ||||
|         self.add_discount_prod_1_includes_prod_2() | ||||
| 
 | ||||
|         cart = CartController.for_user(self.USER_1) | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_1, 1) | ||||
|         cart.add_to_cart(self.PROD_2, 1) | ||||
|         cart.add_to_cart(self.PROD_2, 1) | ||||
|  | @ -134,7 +134,7 @@ class DiscountTestCase(RegistrationCartTestCase): | |||
|     def test_discounts_respect_quantity(self): | ||||
|         self.add_discount_prod_1_includes_prod_2() | ||||
| 
 | ||||
|         cart = CartController.for_user(self.USER_1) | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_1, 1) | ||||
|         cart.add_to_cart(self.PROD_2, 3) | ||||
| 
 | ||||
|  | @ -147,7 +147,7 @@ class DiscountTestCase(RegistrationCartTestCase): | |||
|         discount_full = self.add_discount_prod_1_includes_prod_2() | ||||
|         discount_half = self.add_discount_prod_1_includes_prod_2(Decimal(50)) | ||||
| 
 | ||||
|         cart = CartController.for_user(self.USER_1) | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_1, 1) | ||||
|         cart.add_to_cart(self.PROD_2, 3) | ||||
| 
 | ||||
|  | @ -166,13 +166,13 @@ class DiscountTestCase(RegistrationCartTestCase): | |||
|         self.add_discount_prod_1_includes_prod_2() | ||||
| 
 | ||||
|         # Enable the discount during the first cart. | ||||
|         cart = CartController.for_user(self.USER_1) | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_1, 1) | ||||
|         cart.cart.active = False | ||||
|         cart.cart.save() | ||||
| 
 | ||||
|         # Use the discount in the second cart | ||||
|         cart = CartController.for_user(self.USER_1) | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_2, 1) | ||||
| 
 | ||||
|         # The discount should be applied. | ||||
|  | @ -182,7 +182,7 @@ class DiscountTestCase(RegistrationCartTestCase): | |||
| 
 | ||||
|         # The discount should respect the total quantity across all | ||||
|         # of the user's carts. | ||||
|         cart = CartController.for_user(self.USER_1) | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_2, 2) | ||||
| 
 | ||||
|         # Having one item in the second cart leaves one more item where | ||||
|  | @ -193,7 +193,7 @@ class DiscountTestCase(RegistrationCartTestCase): | |||
| 
 | ||||
|     def test_discount_applies_only_once_enabled(self): | ||||
|         # Enable the discount during the first cart. | ||||
|         cart = CartController.for_user(self.USER_1) | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_1, 1) | ||||
|         # This would exhaust discount if present | ||||
|         cart.add_to_cart(self.PROD_2, 2) | ||||
|  | @ -201,7 +201,7 @@ class DiscountTestCase(RegistrationCartTestCase): | |||
|         cart.cart.save() | ||||
| 
 | ||||
|         self.add_discount_prod_1_includes_prod_2() | ||||
|         cart = CartController.for_user(self.USER_1) | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_2, 2) | ||||
| 
 | ||||
|         discount_items = list(cart.cart.discountitem_set.all()) | ||||
|  | @ -209,7 +209,7 @@ class DiscountTestCase(RegistrationCartTestCase): | |||
| 
 | ||||
|     def test_category_discount_applies_once_per_category(self): | ||||
|         self.add_discount_prod_1_includes_cat_2(quantity=1) | ||||
|         cart = CartController.for_user(self.USER_1) | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_1, 1) | ||||
| 
 | ||||
|         # Add two items from category 2 | ||||
|  | @ -223,7 +223,7 @@ class DiscountTestCase(RegistrationCartTestCase): | |||
| 
 | ||||
|     def test_category_discount_applies_to_highest_value(self): | ||||
|         self.add_discount_prod_1_includes_cat_2(quantity=1) | ||||
|         cart = CartController.for_user(self.USER_1) | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_1, 1) | ||||
| 
 | ||||
|         # Add two items from category 2, add the less expensive one first | ||||
|  | @ -241,7 +241,7 @@ class DiscountTestCase(RegistrationCartTestCase): | |||
|         # Both users should be able to apply the same discount | ||||
|         # in the same way | ||||
|         for user in (self.USER_1, self.USER_2): | ||||
|             cart = CartController.for_user(user) | ||||
|             cart = TestingCartController.for_user(user) | ||||
|             cart.add_to_cart(self.PROD_1, 1)  # Enable the discount | ||||
|             cart.add_to_cart(self.PROD_3, 1) | ||||
| 
 | ||||
|  | @ -270,7 +270,7 @@ class DiscountTestCase(RegistrationCartTestCase): | |||
|     def test_category_discount_appears_once_if_met_twice(self): | ||||
|         self.add_discount_prod_1_includes_cat_2(quantity=1) | ||||
| 
 | ||||
|         cart = CartController.for_user(self.USER_1) | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_1, 1)  # Enable the discount | ||||
| 
 | ||||
|         discounts = discount.available_discounts( | ||||
|  | @ -283,7 +283,7 @@ class DiscountTestCase(RegistrationCartTestCase): | |||
|     def test_category_discount_appears_with_category(self): | ||||
|         self.add_discount_prod_1_includes_cat_2(quantity=1) | ||||
| 
 | ||||
|         cart = CartController.for_user(self.USER_1) | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_1, 1)  # Enable the discount | ||||
| 
 | ||||
|         discounts = discount.available_discounts(self.USER_1, [self.CAT_2], []) | ||||
|  | @ -292,7 +292,7 @@ class DiscountTestCase(RegistrationCartTestCase): | |||
|     def test_category_discount_appears_with_product(self): | ||||
|         self.add_discount_prod_1_includes_cat_2(quantity=1) | ||||
| 
 | ||||
|         cart = CartController.for_user(self.USER_1) | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_1, 1)  # Enable the discount | ||||
| 
 | ||||
|         discounts = discount.available_discounts( | ||||
|  | @ -305,7 +305,7 @@ class DiscountTestCase(RegistrationCartTestCase): | |||
|     def test_category_discount_appears_once_with_two_valid_product(self): | ||||
|         self.add_discount_prod_1_includes_cat_2(quantity=1) | ||||
| 
 | ||||
|         cart = CartController.for_user(self.USER_1) | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_1, 1)  # Enable the discount | ||||
| 
 | ||||
|         discounts = discount.available_discounts( | ||||
|  | @ -318,7 +318,7 @@ class DiscountTestCase(RegistrationCartTestCase): | |||
|     def test_product_discount_appears_with_product(self): | ||||
|         self.add_discount_prod_1_includes_prod_2(quantity=1) | ||||
| 
 | ||||
|         cart = CartController.for_user(self.USER_1) | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_1, 1)  # Enable the discount | ||||
| 
 | ||||
|         discounts = discount.available_discounts( | ||||
|  | @ -331,7 +331,7 @@ class DiscountTestCase(RegistrationCartTestCase): | |||
|     def test_product_discount_does_not_appear_with_category(self): | ||||
|         self.add_discount_prod_1_includes_prod_2(quantity=1) | ||||
| 
 | ||||
|         cart = CartController.for_user(self.USER_1) | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_1, 1)  # Enable the discount | ||||
| 
 | ||||
|         discounts = discount.available_discounts(self.USER_1, [self.CAT_1], []) | ||||
|  | @ -340,7 +340,7 @@ class DiscountTestCase(RegistrationCartTestCase): | |||
|     def test_discount_quantity_is_correct_before_first_purchase(self): | ||||
|         self.add_discount_prod_1_includes_cat_2(quantity=2) | ||||
| 
 | ||||
|         cart = CartController.for_user(self.USER_1) | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_1, 1)  # Enable the discount | ||||
|         cart.add_to_cart(self.PROD_3, 1)  # Exhaust the quantity | ||||
| 
 | ||||
|  | @ -353,7 +353,7 @@ class DiscountTestCase(RegistrationCartTestCase): | |||
|     def test_discount_quantity_is_correct_after_first_purchase(self): | ||||
|         self.test_discount_quantity_is_correct_before_first_purchase() | ||||
| 
 | ||||
|         cart = CartController.for_user(self.USER_1) | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_3, 1)  # Exhaust the quantity | ||||
| 
 | ||||
|         discounts = discount.available_discounts(self.USER_1, [self.CAT_2], []) | ||||
|  | @ -369,7 +369,7 @@ class DiscountTestCase(RegistrationCartTestCase): | |||
| 
 | ||||
|     def test_product_discount_enabled_twice_appears_twice(self): | ||||
|         self.add_discount_prod_1_includes_prod_3_and_prod_4(quantity=2) | ||||
|         cart = CartController.for_user(self.USER_1) | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_1, 1)  # Enable the discount | ||||
|         discounts = discount.available_discounts( | ||||
|             self.USER_1, | ||||
|  | @ -380,7 +380,7 @@ class DiscountTestCase(RegistrationCartTestCase): | |||
| 
 | ||||
|     def test_discounts_are_released_by_refunds(self): | ||||
|         self.add_discount_prod_1_includes_prod_2(quantity=2) | ||||
|         cart = CartController.for_user(self.USER_1) | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_1, 1)  # Enable the discount | ||||
|         discounts = discount.available_discounts( | ||||
|             self.USER_1, | ||||
|  | @ -392,7 +392,7 @@ class DiscountTestCase(RegistrationCartTestCase): | |||
|         cart.cart.active = False  # Keep discount enabled | ||||
|         cart.cart.save() | ||||
| 
 | ||||
|         cart = CartController.for_user(self.USER_1) | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_2, 2)  # The discount will be exhausted | ||||
|         cart.cart.active = False | ||||
|         cart.cart.save() | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ from django.core.exceptions import ValidationError | |||
| 
 | ||||
| from registrasion import models as rego | ||||
| from registrasion.controllers.category import CategoryController | ||||
| from registrasion.controllers.cart import CartController | ||||
| from cart_controller_helper import TestingCartController | ||||
| from registrasion.controllers.product import ProductController | ||||
| 
 | ||||
| from test_cart import RegistrationCartTestCase | ||||
|  | @ -56,7 +56,7 @@ class EnablingConditionTestCases(RegistrationCartTestCase): | |||
|         self.add_product_enabling_condition() | ||||
| 
 | ||||
|         # Cannot buy PROD_1 without buying PROD_2 | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
|         with self.assertRaises(ValidationError): | ||||
|             current_cart.add_to_cart(self.PROD_1, 1) | ||||
| 
 | ||||
|  | @ -66,20 +66,20 @@ class EnablingConditionTestCases(RegistrationCartTestCase): | |||
|     def test_product_enabled_by_product_in_previous_cart(self): | ||||
|         self.add_product_enabling_condition() | ||||
| 
 | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
|         current_cart.add_to_cart(self.PROD_2, 1) | ||||
|         current_cart.cart.active = False | ||||
|         current_cart.cart.save() | ||||
| 
 | ||||
|         # Create new cart and try to add PROD_1 | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
|         current_cart.add_to_cart(self.PROD_1, 1) | ||||
| 
 | ||||
|     def test_product_enabling_condition_enables_category(self): | ||||
|         self.add_product_enabling_condition_on_category() | ||||
| 
 | ||||
|         # Cannot buy PROD_1 without buying item from CAT_2 | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
|         with self.assertRaises(ValidationError): | ||||
|             current_cart.add_to_cart(self.PROD_1, 1) | ||||
| 
 | ||||
|  | @ -90,7 +90,7 @@ class EnablingConditionTestCases(RegistrationCartTestCase): | |||
|         self.add_category_enabling_condition() | ||||
| 
 | ||||
|         # Cannot buy PROD_1 without buying PROD_2 | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
|         with self.assertRaises(ValidationError): | ||||
|             current_cart.add_to_cart(self.PROD_1, 1) | ||||
| 
 | ||||
|  | @ -101,13 +101,13 @@ class EnablingConditionTestCases(RegistrationCartTestCase): | |||
|     def test_product_enabled_by_category_in_previous_cart(self): | ||||
|         self.add_category_enabling_condition() | ||||
| 
 | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
|         current_cart.add_to_cart(self.PROD_3, 1) | ||||
|         current_cart.cart.active = False | ||||
|         current_cart.cart.save() | ||||
| 
 | ||||
|         # Create new cart and try to add PROD_1 | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
|         current_cart.add_to_cart(self.PROD_1, 1) | ||||
| 
 | ||||
|     def test_multiple_non_mandatory_conditions(self): | ||||
|  | @ -115,7 +115,7 @@ class EnablingConditionTestCases(RegistrationCartTestCase): | |||
|         self.add_category_enabling_condition() | ||||
| 
 | ||||
|         # User 1 is testing the product enabling condition | ||||
|         cart_1 = CartController.for_user(self.USER_1) | ||||
|         cart_1 = TestingCartController.for_user(self.USER_1) | ||||
|         # Cannot add PROD_1 until a condition is met | ||||
|         with self.assertRaises(ValidationError): | ||||
|             cart_1.add_to_cart(self.PROD_1, 1) | ||||
|  | @ -123,7 +123,7 @@ class EnablingConditionTestCases(RegistrationCartTestCase): | |||
|         cart_1.add_to_cart(self.PROD_1, 1) | ||||
| 
 | ||||
|         # User 2 is testing the category enabling condition | ||||
|         cart_2 = CartController.for_user(self.USER_2) | ||||
|         cart_2 = TestingCartController.for_user(self.USER_2) | ||||
|         # Cannot add PROD_1 until a condition is met | ||||
|         with self.assertRaises(ValidationError): | ||||
|             cart_2.add_to_cart(self.PROD_1, 1) | ||||
|  | @ -134,7 +134,7 @@ class EnablingConditionTestCases(RegistrationCartTestCase): | |||
|         self.add_product_enabling_condition(mandatory=True) | ||||
|         self.add_category_enabling_condition(mandatory=True) | ||||
| 
 | ||||
|         cart_1 = CartController.for_user(self.USER_1) | ||||
|         cart_1 = TestingCartController.for_user(self.USER_1) | ||||
|         # Cannot add PROD_1 until both conditions are met | ||||
|         with self.assertRaises(ValidationError): | ||||
|             cart_1.add_to_cart(self.PROD_1, 1) | ||||
|  | @ -148,7 +148,7 @@ class EnablingConditionTestCases(RegistrationCartTestCase): | |||
|         self.add_product_enabling_condition(mandatory=False) | ||||
|         self.add_category_enabling_condition(mandatory=True) | ||||
| 
 | ||||
|         cart_1 = CartController.for_user(self.USER_1) | ||||
|         cart_1 = TestingCartController.for_user(self.USER_1) | ||||
|         # Cannot add PROD_1 until both conditions are met | ||||
|         with self.assertRaises(ValidationError): | ||||
|             cart_1.add_to_cart(self.PROD_1, 1) | ||||
|  | @ -199,7 +199,7 @@ class EnablingConditionTestCases(RegistrationCartTestCase): | |||
|     def test_available_products_on_category_works_when_condition_is_met(self): | ||||
|         self.add_product_enabling_condition(mandatory=False) | ||||
| 
 | ||||
|         cart_1 = CartController.for_user(self.USER_1) | ||||
|         cart_1 = TestingCartController.for_user(self.USER_1) | ||||
|         cart_1.add_to_cart(self.PROD_2, 1) | ||||
| 
 | ||||
|         prods = ProductController.available_products( | ||||
|  | @ -224,7 +224,7 @@ class EnablingConditionTestCases(RegistrationCartTestCase): | |||
|     def test_available_products_on_products_works_when_condition_is_met(self): | ||||
|         self.add_product_enabling_condition(mandatory=False) | ||||
| 
 | ||||
|         cart_1 = CartController.for_user(self.USER_1) | ||||
|         cart_1 = TestingCartController.for_user(self.USER_1) | ||||
|         cart_1.add_to_cart(self.PROD_2, 1) | ||||
| 
 | ||||
|         prods = ProductController.available_products( | ||||
|  | @ -238,13 +238,13 @@ class EnablingConditionTestCases(RegistrationCartTestCase): | |||
|     def test_category_enabling_condition_fails_if_cart_refunded(self): | ||||
|         self.add_category_enabling_condition(mandatory=False) | ||||
| 
 | ||||
|         cart = CartController.for_user(self.USER_1) | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_3, 1) | ||||
| 
 | ||||
|         cart.cart.active = False | ||||
|         cart.cart.save() | ||||
| 
 | ||||
|         cart_2 = CartController.for_user(self.USER_1) | ||||
|         cart_2 = TestingCartController.for_user(self.USER_1) | ||||
|         cart_2.add_to_cart(self.PROD_1, 1) | ||||
|         cart_2.set_quantity(self.PROD_1, 0) | ||||
| 
 | ||||
|  | @ -257,13 +257,13 @@ class EnablingConditionTestCases(RegistrationCartTestCase): | |||
|     def test_product_enabling_condition_fails_if_cart_refunded(self): | ||||
|         self.add_product_enabling_condition(mandatory=False) | ||||
| 
 | ||||
|         cart = CartController.for_user(self.USER_1) | ||||
|         cart = TestingCartController.for_user(self.USER_1) | ||||
|         cart.add_to_cart(self.PROD_2, 1) | ||||
| 
 | ||||
|         cart.cart.active = False | ||||
|         cart.cart.save() | ||||
| 
 | ||||
|         cart_2 = CartController.for_user(self.USER_1) | ||||
|         cart_2 = TestingCartController.for_user(self.USER_1) | ||||
|         cart_2.add_to_cart(self.PROD_1, 1) | ||||
|         cart_2.set_quantity(self.PROD_1, 0) | ||||
| 
 | ||||
|  | @ -276,7 +276,7 @@ class EnablingConditionTestCases(RegistrationCartTestCase): | |||
|     def test_available_categories(self): | ||||
|         self.add_product_enabling_condition_on_category(mandatory=False) | ||||
| 
 | ||||
|         cart_1 = CartController.for_user(self.USER_1) | ||||
|         cart_1 = TestingCartController.for_user(self.USER_1) | ||||
| 
 | ||||
|         cats = CategoryController.available_categories( | ||||
|             self.USER_1, | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ from decimal import Decimal | |||
| from django.core.exceptions import ValidationError | ||||
| 
 | ||||
| from registrasion import models as rego | ||||
| from registrasion.controllers.cart import CartController | ||||
| from cart_controller_helper import TestingCartController | ||||
| from registrasion.controllers.invoice import InvoiceController | ||||
| 
 | ||||
| from test_cart import RegistrationCartTestCase | ||||
|  | @ -16,7 +16,7 @@ UTC = pytz.timezone('UTC') | |||
| class InvoiceTestCase(RegistrationCartTestCase): | ||||
| 
 | ||||
|     def test_create_invoice(self): | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
| 
 | ||||
|         # Should be able to create an invoice after the product is added | ||||
|         current_cart.add_to_cart(self.PROD_1, 1) | ||||
|  | @ -49,11 +49,11 @@ class InvoiceTestCase(RegistrationCartTestCase): | |||
|     def test_create_invoice_fails_if_cart_invalid(self): | ||||
|         self.make_ceiling("Limit ceiling", limit=1) | ||||
|         self.set_time(datetime.datetime(2015, 01, 01, tzinfo=UTC)) | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
|         current_cart.add_to_cart(self.PROD_1, 1) | ||||
| 
 | ||||
|         self.add_timedelta(self.RESERVATION * 2) | ||||
|         cart_2 = CartController.for_user(self.USER_2) | ||||
|         cart_2 = TestingCartController.for_user(self.USER_2) | ||||
|         cart_2.add_to_cart(self.PROD_1, 1) | ||||
| 
 | ||||
|         # Now try to invoice the first user | ||||
|  | @ -61,7 +61,7 @@ class InvoiceTestCase(RegistrationCartTestCase): | |||
|             InvoiceController.for_cart(current_cart.cart) | ||||
| 
 | ||||
|     def test_paying_invoice_makes_new_cart(self): | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
|         current_cart.add_to_cart(self.PROD_1, 1) | ||||
| 
 | ||||
|         invoice = InvoiceController.for_cart(current_cart.cart) | ||||
|  | @ -74,7 +74,7 @@ class InvoiceTestCase(RegistrationCartTestCase): | |||
|         self.assertFalse(invoice.invoice.cart.active) | ||||
| 
 | ||||
|         # Asking for a cart should generate a new one | ||||
|         new_cart = CartController.for_user(self.USER_1) | ||||
|         new_cart = TestingCartController.for_user(self.USER_1) | ||||
|         self.assertNotEqual(current_cart.cart, new_cart.cart) | ||||
| 
 | ||||
|     def test_invoice_includes_discounts(self): | ||||
|  | @ -96,7 +96,7 @@ class InvoiceTestCase(RegistrationCartTestCase): | |||
|             quantity=1 | ||||
|         ).save() | ||||
| 
 | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
|         current_cart.apply_voucher(voucher.code) | ||||
| 
 | ||||
|         # Should be able to create an invoice after the product is added | ||||
|  | @ -112,7 +112,7 @@ class InvoiceTestCase(RegistrationCartTestCase): | |||
|             invoice_1.invoice.value) | ||||
| 
 | ||||
|     def test_invoice_voids_self_if_cart_is_invalid(self): | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
| 
 | ||||
|         # Should be able to create an invoice after the product is added | ||||
|         current_cart.add_to_cart(self.PROD_1, 1) | ||||
|  | @ -134,7 +134,7 @@ class InvoiceTestCase(RegistrationCartTestCase): | |||
|         self.assertFalse(invoice_2_new.invoice.void) | ||||
| 
 | ||||
|     def test_voiding_invoice_creates_new_invoice(self): | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
| 
 | ||||
|         # Should be able to create an invoice after the product is added | ||||
|         current_cart.add_to_cart(self.PROD_1, 1) | ||||
|  | @ -147,7 +147,7 @@ class InvoiceTestCase(RegistrationCartTestCase): | |||
|         self.assertNotEqual(invoice_1.invoice, invoice_2.invoice) | ||||
| 
 | ||||
|     def test_cannot_pay_void_invoice(self): | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
| 
 | ||||
|         # Should be able to create an invoice after the product is added | ||||
|         current_cart.add_to_cart(self.PROD_1, 1) | ||||
|  | @ -159,7 +159,7 @@ class InvoiceTestCase(RegistrationCartTestCase): | |||
|             invoice_1.pay("Reference", invoice_1.invoice.value) | ||||
| 
 | ||||
|     def test_cannot_void_paid_invoice(self): | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
| 
 | ||||
|         # Should be able to create an invoice after the product is added | ||||
|         current_cart.add_to_cart(self.PROD_1, 1) | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| import pytz | ||||
| 
 | ||||
| from registrasion.controllers.cart import CartController | ||||
| from cart_controller_helper import TestingCartController | ||||
| from registrasion.controllers.invoice import InvoiceController | ||||
| 
 | ||||
| from test_cart import RegistrationCartTestCase | ||||
|  | @ -11,7 +11,7 @@ UTC = pytz.timezone('UTC') | |||
| class RefundTestCase(RegistrationCartTestCase): | ||||
| 
 | ||||
|     def test_refund_marks_void_and_unpaid_and_cart_released(self): | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
| 
 | ||||
|         # Should be able to create an invoice after the product is added | ||||
|         current_cart.add_to_cart(self.PROD_1, 1) | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ from django.core.exceptions import ValidationError | |||
| from django.db import IntegrityError | ||||
| 
 | ||||
| from registrasion import models as rego | ||||
| from registrasion.controllers.cart import CartController | ||||
| from cart_controller_helper import TestingCartController | ||||
| from registrasion.controllers.invoice import InvoiceController | ||||
| 
 | ||||
| from test_cart import RegistrationCartTestCase | ||||
|  | @ -16,27 +16,17 @@ UTC = pytz.timezone('UTC') | |||
| 
 | ||||
| class VoucherTestCases(RegistrationCartTestCase): | ||||
| 
 | ||||
|     @classmethod | ||||
|     def new_voucher(self, code="VOUCHER", limit=1): | ||||
|         voucher = rego.Voucher.objects.create( | ||||
|             recipient="Voucher recipient", | ||||
|             code=code, | ||||
|             limit=limit, | ||||
|         ) | ||||
|         voucher.save() | ||||
|         return voucher | ||||
| 
 | ||||
|     def test_apply_voucher(self): | ||||
|         voucher = self.new_voucher() | ||||
| 
 | ||||
|         self.set_time(datetime.datetime(2015, 01, 01, tzinfo=UTC)) | ||||
| 
 | ||||
|         cart_1 = CartController.for_user(self.USER_1) | ||||
|         cart_1 = TestingCartController.for_user(self.USER_1) | ||||
|         cart_1.apply_voucher(voucher.code) | ||||
|         self.assertIn(voucher, cart_1.cart.vouchers.all()) | ||||
| 
 | ||||
|         # Second user should not be able to apply this voucher (it's exhausted) | ||||
|         cart_2 = CartController.for_user(self.USER_2) | ||||
|         cart_2 = TestingCartController.for_user(self.USER_2) | ||||
|         with self.assertRaises(ValidationError): | ||||
|             cart_2.apply_voucher(voucher.code) | ||||
| 
 | ||||
|  | @ -66,7 +56,7 @@ class VoucherTestCases(RegistrationCartTestCase): | |||
|         enabling_condition.save() | ||||
| 
 | ||||
|         # Adding the product without a voucher will not work | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
|         with self.assertRaises(ValidationError): | ||||
|             current_cart.add_to_cart(self.PROD_1, 1) | ||||
| 
 | ||||
|  | @ -90,7 +80,7 @@ class VoucherTestCases(RegistrationCartTestCase): | |||
|         ).save() | ||||
| 
 | ||||
|         # Having PROD_1 in place should add a discount | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
|         current_cart.apply_voucher(voucher.code) | ||||
|         current_cart.add_to_cart(self.PROD_1, 1) | ||||
|         self.assertEqual(1, len(current_cart.cart.discountitem_set.all())) | ||||
|  | @ -106,19 +96,19 @@ class VoucherTestCases(RegistrationCartTestCase): | |||
| 
 | ||||
|     def test_vouchers_case_insensitive(self): | ||||
|         voucher = self.new_voucher(code="VOUCHeR") | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
|         current_cart.apply_voucher(voucher.code.lower()) | ||||
| 
 | ||||
|     def test_voucher_can_only_be_applied_once(self): | ||||
|         voucher = self.new_voucher(limit=2) | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
|         current_cart.apply_voucher(voucher.code) | ||||
|         with self.assertRaises(ValidationError): | ||||
|             current_cart.apply_voucher(voucher.code) | ||||
| 
 | ||||
|     def test_voucher_can_only_be_applied_once_across_multiple_carts(self): | ||||
|         voucher = self.new_voucher(limit=2) | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
|         current_cart.apply_voucher(voucher.code) | ||||
| 
 | ||||
|         inv = InvoiceController.for_cart(current_cart.cart) | ||||
|  | @ -131,13 +121,13 @@ class VoucherTestCases(RegistrationCartTestCase): | |||
| 
 | ||||
|     def test_refund_releases_used_vouchers(self): | ||||
|         voucher = self.new_voucher(limit=2) | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
|         current_cart.apply_voucher(voucher.code) | ||||
| 
 | ||||
|         inv = InvoiceController.for_cart(current_cart.cart) | ||||
|         inv.pay("Hello!", inv.invoice.value) | ||||
| 
 | ||||
|         current_cart = CartController.for_user(self.USER_1) | ||||
|         current_cart = TestingCartController.for_user(self.USER_1) | ||||
|         with self.assertRaises(ValidationError): | ||||
|             current_cart.apply_voucher(voucher.code) | ||||
| 
 | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ from registrasion.controllers import discount | |||
| from registrasion.controllers.cart import CartController | ||||
| from registrasion.controllers.invoice import InvoiceController | ||||
| from registrasion.controllers.product import ProductController | ||||
| from registrasion.exceptions import CartValidationError | ||||
| 
 | ||||
| from collections import namedtuple | ||||
| 
 | ||||
|  | @ -14,7 +15,6 @@ from django.contrib.auth.decorators import login_required | |||
| from django.contrib import messages | ||||
| from django.core.exceptions import ObjectDoesNotExist | ||||
| from django.core.exceptions import ValidationError | ||||
| from django.db import transaction | ||||
| from django.http import Http404 | ||||
| from django.shortcuts import redirect | ||||
| from django.shortcuts import render | ||||
|  | @ -121,10 +121,6 @@ def guided_registration(request, page_id=0): | |||
|                 category=category, | ||||
|             ) | ||||
| 
 | ||||
|             if not products: | ||||
|                 # This product category does not exist for this user | ||||
|                 continue | ||||
| 
 | ||||
|             prefix = "category_" + str(category.id) | ||||
|             p = handle_products(request, category, products, prefix) | ||||
|             products_form, discounts, products_handled = p | ||||
|  | @ -135,7 +131,9 @@ def guided_registration(request, page_id=0): | |||
|                 discounts=discounts, | ||||
|                 form=products_form, | ||||
|             ) | ||||
|             sections.append(section) | ||||
|             if products: | ||||
|                 # This product category does not exist for this user | ||||
|                 sections.append(section) | ||||
| 
 | ||||
|             if request.method == "POST" and not products_form.errors: | ||||
|                 if category.id > attendee.highest_complete_category: | ||||
|  | @ -299,12 +297,8 @@ def handle_products(request, category, products, prefix): | |||
|     ) | ||||
| 
 | ||||
|     if request.method == "POST" and products_form.is_valid(): | ||||
|         try: | ||||
|             if products_form.has_changed(): | ||||
|                 set_quantities_from_products_form(products_form, current_cart) | ||||
|         except ValidationError: | ||||
|             # There were errors, but they've already been added to the form. | ||||
|             pass | ||||
|         if products_form.has_changed(): | ||||
|             set_quantities_from_products_form(products_form, current_cart) | ||||
| 
 | ||||
|         # If category is required, the user must have at least one | ||||
|         # in an active+valid cart | ||||
|  | @ -326,20 +320,26 @@ def handle_products(request, category, products, prefix): | |||
|     return products_form, discounts, handled | ||||
| 
 | ||||
| 
 | ||||
| @transaction.atomic | ||||
| def set_quantities_from_products_form(products_form, current_cart): | ||||
|     # TODO: issue #8 is a problem here. | ||||
| 
 | ||||
|     quantities = list(products_form.product_quantities()) | ||||
|     quantities.sort(key=lambda item: item[1]) | ||||
|     for product_id, quantity, field_name in quantities: | ||||
|         product = rego.Product.objects.get(pk=product_id) | ||||
|         try: | ||||
|             current_cart.set_quantity(product, quantity, batched=True) | ||||
|         except ValidationError as ve: | ||||
|             products_form.add_error(field_name, ve) | ||||
|     if products_form.errors: | ||||
|         raise ValidationError("Cannot add that stuff") | ||||
|     current_cart.end_batch() | ||||
|     product_quantities = [ | ||||
|         (rego.Product.objects.get(pk=i[0]), i[1]) for i in quantities | ||||
|     ] | ||||
|     field_names = dict( | ||||
|         (i[0][0], i[1][2]) for i in zip(product_quantities, quantities) | ||||
|     ) | ||||
| 
 | ||||
|     try: | ||||
|         current_cart.set_quantities(product_quantities) | ||||
|     except CartValidationError as ve: | ||||
|         for ve_field in ve.error_list: | ||||
|             product, message = ve_field.message | ||||
|             if product in field_names: | ||||
|                 field = field_names[product] | ||||
|             else: | ||||
|                 field = None | ||||
|             products_form.add_error(field, message) | ||||
| 
 | ||||
| 
 | ||||
| def handle_voucher(request, prefix): | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Christopher Neugebauer
						Christopher Neugebauer