Merge branch 'refactor_limits_testing'

This commit is contained in:
Christopher Neugebauer 2016-04-03 15:27:05 +10:00
commit 8b13bb9bc5
15 changed files with 543 additions and 327 deletions

View file

@ -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):

View file

@ -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

View file

@ -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

View file

@ -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,

View file

@ -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

View file

@ -0,0 +1,4 @@
from django.core.exceptions import ValidationError
class CartValidationError(ValidationError):
pass

View 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)

View file

@ -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)

View file

@ -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()))

View file

@ -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()

View file

@ -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,

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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):