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…
Reference in a new issue