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 datetime
|
||||||
import discount
|
import discount
|
||||||
|
import itertools
|
||||||
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.db import transaction
|
||||||
from django.db.models import Max
|
from django.db.models import Max
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from registrasion import models as rego
|
from registrasion import models as rego
|
||||||
|
from registrasion.exceptions import CartValidationError
|
||||||
|
|
||||||
|
from category import CategoryController
|
||||||
from conditions import ConditionController
|
from conditions import ConditionController
|
||||||
from product import ProductController
|
from product import ProductController
|
||||||
|
|
||||||
|
@ -17,8 +22,8 @@ class CartController(object):
|
||||||
def __init__(self, cart):
|
def __init__(self, cart):
|
||||||
self.cart = cart
|
self.cart = cart
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def for_user(user):
|
def for_user(cls, user):
|
||||||
''' Returns the user's current cart, or creates a new cart
|
''' Returns the user's current cart, or creates a new cart
|
||||||
if there isn't one ready yet. '''
|
if there isn't one ready yet. '''
|
||||||
|
|
||||||
|
@ -31,7 +36,7 @@ class CartController(object):
|
||||||
reservation_duration=datetime.timedelta(),
|
reservation_duration=datetime.timedelta(),
|
||||||
)
|
)
|
||||||
existing.save()
|
existing.save()
|
||||||
return CartController(existing)
|
return cls(existing)
|
||||||
|
|
||||||
def extend_reservation(self):
|
def extend_reservation(self):
|
||||||
''' Updates the cart's time last updated value, which is used to
|
''' Updates the cart's time last updated value, which is used to
|
||||||
|
@ -57,70 +62,115 @@ class CartController(object):
|
||||||
|
|
||||||
def end_batch(self):
|
def end_batch(self):
|
||||||
''' Performs operations that occur occur at the end of a batch of
|
''' 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.recalculate_discounts()
|
||||||
|
|
||||||
self.extend_reservation()
|
self.extend_reservation()
|
||||||
self.cart.revision += 1
|
self.cart.revision += 1
|
||||||
self.cart.save()
|
self.cart.save()
|
||||||
|
|
||||||
def set_quantity(self, product, quantity, batched=False):
|
@transaction.atomic
|
||||||
''' Sets the _quantity_ of the given _product_ in the cart to the given
|
def set_quantities(self, product_quantities):
|
||||||
_quantity_. '''
|
''' 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:
|
items_in_cart = rego.ProductItem.objects.filter(cart=self.cart)
|
||||||
raise ValidationError("Cannot have fewer than 0 items in cart.")
|
product_quantities = list(product_quantities)
|
||||||
|
|
||||||
try:
|
# n.b need to add have the existing items first so that the new
|
||||||
product_item = rego.ProductItem.objects.get(
|
# items override the old ones.
|
||||||
cart=self.cart,
|
all_product_quantities = dict(itertools.chain(
|
||||||
product=product)
|
((i.product, i.quantity) for i in items_in_cart.all()),
|
||||||
old_quantity = product_item.quantity
|
product_quantities,
|
||||||
|
)).items()
|
||||||
|
|
||||||
if quantity == 0:
|
# Validate that the limits we're adding are OK
|
||||||
product_item.delete()
|
self._test_limits(all_product_quantities)
|
||||||
return
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
if quantity == 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
product_item = rego.ProductItem.objects.create(
|
for product, quantity in product_quantities:
|
||||||
cart=self.cart,
|
try:
|
||||||
product=product,
|
product_item = rego.ProductItem.objects.get(
|
||||||
quantity=0,
|
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
|
if errors:
|
||||||
|
raise CartValidationError(errors)
|
||||||
# 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)
|
|
||||||
|
|
||||||
def apply_voucher(self, voucher_code):
|
def apply_voucher(self, voucher_code):
|
||||||
''' Applies the voucher with the given code to this cart. '''
|
''' 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;
|
''' Determines whether the status of the current cart is valid;
|
||||||
this is normally called before generating or paying an invoice '''
|
this is normally called before generating or paying an invoice '''
|
||||||
|
|
||||||
is_reserved = self.cart in rego.Cart.reserved_carts()
|
|
||||||
|
|
||||||
# TODO: validate vouchers
|
# TODO: validate vouchers
|
||||||
|
|
||||||
items = rego.ProductItem.objects.filter(cart=self.cart)
|
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
|
product_quantities = list((i.product, i.quantity) for i in items)
|
||||||
quantity = 0 if is_reserved else item.quantity
|
self._test_limits(product_quantities)
|
||||||
|
|
||||||
if not prod.can_add_with_enabling_conditions(
|
|
||||||
self.cart.user, quantity):
|
|
||||||
raise ValidationError("Products are no longer available")
|
|
||||||
|
|
||||||
# Validate the discounts
|
# Validate the discounts
|
||||||
discount_items = rego.DiscountItem.objects.filter(cart=self.cart)
|
discount_items = rego.DiscountItem.objects.filter(cart=self.cart)
|
||||||
|
@ -183,9 +223,7 @@ class CartController(object):
|
||||||
pk=discount.pk)
|
pk=discount.pk)
|
||||||
cond = ConditionController.for_condition(real_discount)
|
cond = ConditionController.for_condition(real_discount)
|
||||||
|
|
||||||
quantity = 0 if is_reserved else discount_item.quantity
|
if not cond.is_met(self.cart.user):
|
||||||
|
|
||||||
if not cond.is_met(self.cart.user, quantity):
|
|
||||||
raise ValidationError("Discounts are no longer available")
|
raise ValidationError("Discounts are no longer available")
|
||||||
|
|
||||||
def recalculate_discounts(self):
|
def recalculate_discounts(self):
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from .product import ProductController
|
|
||||||
|
|
||||||
from registrasion import models as rego
|
from registrasion import models as rego
|
||||||
|
|
||||||
|
from django.db.models import Sum
|
||||||
|
|
||||||
|
|
||||||
class AllProducts(object):
|
class AllProducts(object):
|
||||||
pass
|
pass
|
||||||
|
@ -9,12 +9,18 @@ class AllProducts(object):
|
||||||
|
|
||||||
class CategoryController(object):
|
class CategoryController(object):
|
||||||
|
|
||||||
|
def __init__(self, category):
|
||||||
|
self.category = category
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def available_categories(cls, user, products=AllProducts):
|
def available_categories(cls, user, products=AllProducts):
|
||||||
''' Returns the categories available to the user. Specify `products` if
|
''' Returns the categories available to the user. Specify `products` if
|
||||||
you want to restrict to just the categories that hold the specified
|
you want to restrict to just the categories that hold the specified
|
||||||
products, otherwise it'll do all. '''
|
products, otherwise it'll do all. '''
|
||||||
|
|
||||||
|
# STOPGAP -- this needs to be elsewhere tbqh
|
||||||
|
from product import ProductController
|
||||||
|
|
||||||
if products is AllProducts:
|
if products is AllProducts:
|
||||||
products = rego.Product.objects.all()
|
products = rego.Product.objects.all()
|
||||||
|
|
||||||
|
@ -24,3 +30,27 @@ class CategoryController(object):
|
||||||
)
|
)
|
||||||
|
|
||||||
return set(i.category for i in available)
|
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.db.models import Sum
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from registrasion import models as rego
|
from registrasion import models as rego
|
||||||
|
|
||||||
|
|
||||||
|
ConditionAndRemainder = namedtuple(
|
||||||
|
"ConditionAndRemainder",
|
||||||
|
(
|
||||||
|
"condition",
|
||||||
|
"remainder",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ConditionController(object):
|
class ConditionController(object):
|
||||||
''' Base class for testing conditions that activate EnablingCondition
|
''' Base class for testing conditions that activate EnablingCondition
|
||||||
or Discount objects. '''
|
or Discount objects. '''
|
||||||
|
@ -19,9 +32,9 @@ class ConditionController(object):
|
||||||
rego.IncludedProductDiscount: ProductConditionController,
|
rego.IncludedProductDiscount: ProductConditionController,
|
||||||
rego.ProductEnablingCondition: ProductConditionController,
|
rego.ProductEnablingCondition: ProductConditionController,
|
||||||
rego.TimeOrStockLimitDiscount:
|
rego.TimeOrStockLimitDiscount:
|
||||||
TimeOrStockLimitConditionController,
|
TimeOrStockLimitDiscountController,
|
||||||
rego.TimeOrStockLimitEnablingCondition:
|
rego.TimeOrStockLimitEnablingCondition:
|
||||||
TimeOrStockLimitConditionController,
|
TimeOrStockLimitEnablingConditionController,
|
||||||
rego.VoucherDiscount: VoucherConditionController,
|
rego.VoucherDiscount: VoucherConditionController,
|
||||||
rego.VoucherEnablingCondition: VoucherConditionController,
|
rego.VoucherEnablingCondition: VoucherConditionController,
|
||||||
}
|
}
|
||||||
|
@ -31,8 +44,102 @@ class ConditionController(object):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return ConditionController()
|
return ConditionController()
|
||||||
|
|
||||||
def is_met(self, user, quantity):
|
@classmethod
|
||||||
return True
|
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):
|
class CategoryConditionController(ConditionController):
|
||||||
|
@ -40,7 +147,7 @@ class CategoryConditionController(ConditionController):
|
||||||
def __init__(self, condition):
|
def __init__(self, condition):
|
||||||
self.condition = 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
|
''' returns True if the user has a product from a category that invokes
|
||||||
this condition in one of their carts '''
|
this condition in one of their carts '''
|
||||||
|
|
||||||
|
@ -48,11 +155,11 @@ class CategoryConditionController(ConditionController):
|
||||||
enabling_products = rego.Product.objects.filter(
|
enabling_products = rego.Product.objects.filter(
|
||||||
category=self.condition.enabling_category,
|
category=self.condition.enabling_category,
|
||||||
)
|
)
|
||||||
products = rego.ProductItem.objects.filter(
|
products_count = rego.ProductItem.objects.filter(
|
||||||
cart=carts,
|
cart__in=carts,
|
||||||
product__in=enabling_products,
|
product__in=enabling_products,
|
||||||
)
|
).count()
|
||||||
return len(products) > 0
|
return products_count > 0
|
||||||
|
|
||||||
|
|
||||||
class ProductConditionController(ConditionController):
|
class ProductConditionController(ConditionController):
|
||||||
|
@ -62,41 +169,36 @@ class ProductConditionController(ConditionController):
|
||||||
def __init__(self, condition):
|
def __init__(self, condition):
|
||||||
self.condition = 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
|
''' returns True if the user has a product that invokes this
|
||||||
condition in one of their carts '''
|
condition in one of their carts '''
|
||||||
|
|
||||||
carts = rego.Cart.objects.filter(user=user, released=False)
|
carts = rego.Cart.objects.filter(user=user, released=False)
|
||||||
products = rego.ProductItem.objects.filter(
|
products_count = rego.ProductItem.objects.filter(
|
||||||
cart=carts,
|
cart__in=carts,
|
||||||
product__in=self.condition.enabling_products.all(),
|
product__in=self.condition.enabling_products.all(),
|
||||||
)
|
).count()
|
||||||
return len(products) > 0
|
return products_count > 0
|
||||||
|
|
||||||
|
|
||||||
class TimeOrStockLimitConditionController(ConditionController):
|
class TimeOrStockLimitConditionController(ConditionController):
|
||||||
''' Condition tests for TimeOrStockLimit EnablingCondition and
|
''' Common condition tests for TimeOrStockLimit EnablingCondition and
|
||||||
Discount.'''
|
Discount.'''
|
||||||
|
|
||||||
def __init__(self, ceiling):
|
def __init__(self, ceiling):
|
||||||
self.ceiling = ceiling
|
self.ceiling = ceiling
|
||||||
|
|
||||||
def is_met(self, user, quantity):
|
def user_quantity_remaining(self, user):
|
||||||
''' returns True if adding _quantity_ of _product_ will not vioilate
|
''' returns 0 if the date range is violated, otherwise, it will return
|
||||||
this ceiling. '''
|
the quantity remaining under the stock limit. '''
|
||||||
|
|
||||||
# Test date range
|
# Test date range
|
||||||
if not self.test_date_range():
|
if not self._test_date_range():
|
||||||
return False
|
return 0
|
||||||
|
|
||||||
# Test limits
|
return self._get_remaining_stock(user)
|
||||||
if not self.test_limits(quantity):
|
|
||||||
return False
|
|
||||||
|
|
||||||
# All limits have been met
|
def _test_date_range(self):
|
||||||
return True
|
|
||||||
|
|
||||||
def test_date_range(self):
|
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
|
|
||||||
if self.ceiling.start_time is not None:
|
if self.ceiling.start_time is not None:
|
||||||
|
@ -109,42 +211,49 @@ class TimeOrStockLimitConditionController(ConditionController):
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _products(self):
|
def _get_remaining_stock(self, user):
|
||||||
''' Abstracts away the product list, becuase enabling conditions
|
''' Returns the stock that remains under this ceiling, excluding the
|
||||||
list products differently to discounts. '''
|
user's current cart. '''
|
||||||
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 test_limits(self, quantity):
|
|
||||||
if self.ceiling.limit is None:
|
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()
|
reserved_carts = rego.Cart.reserved_carts()
|
||||||
product_items = rego.ProductItem.objects.filter(
|
reserved_carts = reserved_carts.exclude(
|
||||||
product__in=self._products().all(),
|
user=user,
|
||||||
|
active=True,
|
||||||
)
|
)
|
||||||
product_items = product_items.filter(cart=reserved_carts)
|
|
||||||
|
|
||||||
agg = product_items.aggregate(Sum("quantity"))
|
items = self._items()
|
||||||
count = agg["quantity__sum"]
|
items = items.filter(cart__in=reserved_carts)
|
||||||
if count is None:
|
count = items.aggregate(Sum("quantity"))["quantity__sum"] or 0
|
||||||
count = 0
|
|
||||||
|
|
||||||
if count + quantity > self.ceiling.limit:
|
return self.ceiling.limit - count
|
||||||
return False
|
|
||||||
|
|
||||||
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):
|
class VoucherConditionController(ConditionController):
|
||||||
|
@ -153,10 +262,10 @@ class VoucherConditionController(ConditionController):
|
||||||
def __init__(self, condition):
|
def __init__(self, condition):
|
||||||
self.condition = 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. '''
|
''' returns True if the user has the given voucher attached. '''
|
||||||
carts = rego.Cart.objects.filter(
|
carts_count = rego.Cart.objects.filter(
|
||||||
user=user,
|
user=user,
|
||||||
vouchers=self.condition.voucher,
|
vouchers=self.condition.voucher,
|
||||||
)
|
).count()
|
||||||
return len(carts) > 0
|
return carts_count > 0
|
||||||
|
|
|
@ -75,7 +75,7 @@ def available_discounts(user, categories, products):
|
||||||
pass
|
pass
|
||||||
elif real_discount not in failed_discounts:
|
elif real_discount not in failed_discounts:
|
||||||
# This clause is still available
|
# 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
|
# This clause is valid for this user
|
||||||
discounts.append(DiscountAndQuantity(
|
discounts.append(DiscountAndQuantity(
|
||||||
discount=real_discount,
|
discount=real_discount,
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
from django.db.models import Q
|
|
||||||
from django.db.models import Sum
|
from django.db.models import Sum
|
||||||
from registrasion import models as rego
|
from registrasion import models as rego
|
||||||
|
|
||||||
|
from category import CategoryController
|
||||||
from conditions import ConditionController
|
from conditions import ConditionController
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,80 +29,45 @@ class ProductController(object):
|
||||||
if products is not None:
|
if products is not None:
|
||||||
all_products = itertools.chain(all_products, products)
|
all_products = itertools.chain(all_products, products)
|
||||||
|
|
||||||
out = [
|
passed_limits = set(
|
||||||
product
|
product
|
||||||
for product in all_products
|
for product in all_products
|
||||||
if cls(product).user_can_add_within_limit(user, 1, past_carts=True)
|
if CategoryController(product.category).user_quantity_remaining(
|
||||||
if cls(product).can_add_with_enabling_conditions(user, 0)
|
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)
|
out.sort(key=lambda product: product.order)
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def user_can_add_within_limit(self, user, quantity, past_carts=False):
|
def user_quantity_remaining(self, user):
|
||||||
''' Return true if the user is able to add _quantity_ to their count of
|
''' Returns the quantity of this product that the user add in the
|
||||||
this Product without exceeding _limit_per_user_.'''
|
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(
|
carts = rego.Cart.objects.filter(
|
||||||
user=user,
|
user=user,
|
||||||
|
active=False,
|
||||||
released=False,
|
released=False,
|
||||||
)
|
)
|
||||||
if past_carts:
|
|
||||||
carts = carts.filter(active=False)
|
|
||||||
|
|
||||||
items = rego.ProductItem.objects.filter(
|
items = rego.ProductItem.objects.filter(
|
||||||
cart__in=carts,
|
cart__in=carts,
|
||||||
|
product=self.product,
|
||||||
)
|
)
|
||||||
|
|
||||||
prod_items = items.filter(product=self.product)
|
prod_count = items.aggregate(Sum("quantity"))["quantity__sum"] or 0
|
||||||
cat_items = items.filter(product__category=self.product.category)
|
|
||||||
|
|
||||||
prod_count = prod_items.aggregate(Sum("quantity"))["quantity__sum"]
|
return prod_limit - prod_count
|
||||||
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
|
|
||||||
|
|
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 django.test import TestCase
|
||||||
|
|
||||||
from registrasion import models as rego
|
from registrasion import models as rego
|
||||||
from registrasion.controllers.cart import CartController
|
|
||||||
from registrasion.controllers.product import ProductController
|
from registrasion.controllers.product import ProductController
|
||||||
|
|
||||||
|
from cart_controller_helper import TestingCartController
|
||||||
from patch_datetime import SetTimeMixin
|
from patch_datetime import SetTimeMixin
|
||||||
|
|
||||||
UTC = pytz.timezone('UTC')
|
UTC = pytz.timezone('UTC')
|
||||||
|
@ -72,6 +72,15 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase):
|
||||||
cls.PROD_4.price = Decimal("5.00")
|
cls.PROD_4.price = Decimal("5.00")
|
||||||
cls.PROD_4.save()
|
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
|
@classmethod
|
||||||
def make_ceiling(cls, name, limit=None, start_time=None, end_time=None):
|
def make_ceiling(cls, name, limit=None, start_time=None, end_time=None):
|
||||||
limit_ceiling = rego.TimeOrStockLimitEnablingCondition.objects.create(
|
limit_ceiling = rego.TimeOrStockLimitEnablingCondition.objects.create(
|
||||||
|
@ -101,7 +110,8 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def make_discount_ceiling(
|
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(
|
limit_ceiling = rego.TimeOrStockLimitDiscount.objects.create(
|
||||||
description=name,
|
description=name,
|
||||||
start_time=start_time,
|
start_time=start_time,
|
||||||
|
@ -112,29 +122,39 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase):
|
||||||
rego.DiscountForProduct.objects.create(
|
rego.DiscountForProduct.objects.create(
|
||||||
discount=limit_ceiling,
|
discount=limit_ceiling,
|
||||||
product=cls.PROD_1,
|
product=cls.PROD_1,
|
||||||
percentage=100,
|
percentage=percentage,
|
||||||
quantity=10,
|
quantity=10,
|
||||||
).save()
|
).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):
|
class BasicCartTests(RegistrationCartTestCase):
|
||||||
|
|
||||||
def test_get_cart(self):
|
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.active = False
|
||||||
current_cart.cart.save()
|
current_cart.cart.save()
|
||||||
|
|
||||||
old_cart = current_cart
|
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)
|
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)
|
self.assertEqual(current_cart.cart, current_cart2.cart)
|
||||||
|
|
||||||
def test_add_to_cart_collapses_product_items(self):
|
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
|
# Add a product twice
|
||||||
current_cart.add_to_cart(self.PROD_1, 1)
|
current_cart.add_to_cart(self.PROD_1, 1)
|
||||||
|
@ -149,7 +169,7 @@ class BasicCartTests(RegistrationCartTestCase):
|
||||||
self.assertEquals(2, item.quantity)
|
self.assertEquals(2, item.quantity)
|
||||||
|
|
||||||
def test_set_quantity(self):
|
def test_set_quantity(self):
|
||||||
current_cart = CartController.for_user(self.USER_1)
|
current_cart = TestingCartController.for_user(self.USER_1)
|
||||||
|
|
||||||
def get_item():
|
def get_item():
|
||||||
return rego.ProductItem.objects.get(
|
return rego.ProductItem.objects.get(
|
||||||
|
@ -181,7 +201,7 @@ class BasicCartTests(RegistrationCartTestCase):
|
||||||
self.assertEqual(2, get_item().quantity)
|
self.assertEqual(2, get_item().quantity)
|
||||||
|
|
||||||
def test_add_to_cart_product_per_user_limit(self):
|
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.
|
# User should be able to add 1 of PROD_1 to the current cart.
|
||||||
current_cart.add_to_cart(self.PROD_1, 1)
|
current_cart.add_to_cart(self.PROD_1, 1)
|
||||||
|
@ -197,14 +217,14 @@ class BasicCartTests(RegistrationCartTestCase):
|
||||||
current_cart.cart.active = False
|
current_cart.cart.active = False
|
||||||
current_cart.cart.save()
|
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,
|
# User should not be able to add 10 of PROD_1 to the current cart now,
|
||||||
# even though it's a new cart.
|
# even though it's a new cart.
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
current_cart.add_to_cart(self.PROD_1, 10)
|
current_cart.add_to_cart(self.PROD_1, 10)
|
||||||
|
|
||||||
# Second user should not be affected by first user's limits
|
# 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)
|
second_user_cart.add_to_cart(self.PROD_1, 10)
|
||||||
|
|
||||||
def set_limits(self):
|
def set_limits(self):
|
||||||
|
@ -221,7 +241,7 @@ class BasicCartTests(RegistrationCartTestCase):
|
||||||
def test_per_user_product_limit_ignored_if_blank(self):
|
def test_per_user_product_limit_ignored_if_blank(self):
|
||||||
self.set_limits()
|
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
|
# There is no product limit on PROD_2, and there is no cat limit
|
||||||
current_cart.add_to_cart(self.PROD_2, 1)
|
current_cart.add_to_cart(self.PROD_2, 1)
|
||||||
# There is no product limit on PROD_3, but there is a cat limit
|
# 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):
|
def test_per_user_category_limit_ignored_if_blank(self):
|
||||||
self.set_limits()
|
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
|
# There is no product limit on PROD_2, and there is no cat limit
|
||||||
current_cart.add_to_cart(self.PROD_2, 1)
|
current_cart.add_to_cart(self.PROD_2, 1)
|
||||||
# There is no cat limit on PROD_1, but there is a prod limit
|
# 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):
|
def test_per_user_category_limit_only(self):
|
||||||
self.set_limits()
|
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.
|
# Cannot add to cart if category limit is filled by one product.
|
||||||
current_cart.set_quantity(self.PROD_3, 10)
|
current_cart.set_quantity(self.PROD_3, 10)
|
||||||
|
@ -255,7 +275,7 @@ class BasicCartTests(RegistrationCartTestCase):
|
||||||
current_cart.cart.active = False
|
current_cart.cart.active = False
|
||||||
current_cart.cart.save()
|
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
|
# The category limit should extend across carts
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
current_cart.add_to_cart(self.PROD_3, 10)
|
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):
|
def test_per_user_category_and_product_limits(self):
|
||||||
self.set_limits()
|
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:
|
# Hit both the product and category edges:
|
||||||
current_cart.set_quantity(self.PROD_3, 4)
|
current_cart.set_quantity(self.PROD_3, 4)
|
||||||
|
@ -281,7 +301,7 @@ class BasicCartTests(RegistrationCartTestCase):
|
||||||
current_cart.cart.active = False
|
current_cart.cart.active = False
|
||||||
current_cart.cart.save()
|
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)
|
current_cart.set_quantity(self.PROD_3, 4)
|
||||||
|
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
|
@ -299,7 +319,7 @@ class BasicCartTests(RegistrationCartTestCase):
|
||||||
products=[self.PROD_2, self.PROD_3, self.PROD_4],
|
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()
|
prods = get_prods()
|
||||||
self.assertTrue(item in prods)
|
self.assertTrue(item in prods)
|
||||||
current_cart.add_to_cart(item, quantity)
|
current_cart.add_to_cart(item, quantity)
|
||||||
|
@ -308,7 +328,7 @@ class BasicCartTests(RegistrationCartTestCase):
|
||||||
current_cart.cart.active = False
|
current_cart.cart.active = False
|
||||||
current_cart.cart.save()
|
current_cart.cart.save()
|
||||||
|
|
||||||
current_cart = CartController.for_user(self.USER_1)
|
current_cart = TestingCartController.for_user(self.USER_1)
|
||||||
|
|
||||||
prods = get_prods()
|
prods = get_prods()
|
||||||
self.assertTrue(item not in prods)
|
self.assertTrue(item not in prods)
|
||||||
|
|
|
@ -3,10 +3,11 @@ import pytz
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
from registrasion.controllers.cart import CartController
|
from cart_controller_helper import TestingCartController
|
||||||
|
|
||||||
from test_cart import RegistrationCartTestCase
|
from test_cart import RegistrationCartTestCase
|
||||||
|
|
||||||
|
from registrasion import models as rego
|
||||||
|
|
||||||
UTC = pytz.timezone('UTC')
|
UTC = pytz.timezone('UTC')
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,7 +23,7 @@ class CeilingsTestCases(RegistrationCartTestCase):
|
||||||
|
|
||||||
def __add_to_cart_test(self):
|
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
|
# User should not be able to add 10 of PROD_1 to the current cart
|
||||||
# because it is affected by limit_ceiling
|
# because it is affected by limit_ceiling
|
||||||
|
@ -46,7 +47,7 @@ class CeilingsTestCases(RegistrationCartTestCase):
|
||||||
start_time=datetime.datetime(2015, 01, 01, tzinfo=UTC),
|
start_time=datetime.datetime(2015, 01, 01, tzinfo=UTC),
|
||||||
end_time=datetime.datetime(2015, 02, 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
|
# User should not be able to add whilst we're before start_time
|
||||||
self.set_time(datetime.datetime(2014, 01, 01, tzinfo=UTC))
|
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))
|
self.set_time(datetime.datetime(2015, 01, 01, tzinfo=UTC))
|
||||||
|
|
||||||
first_cart = CartController.for_user(self.USER_1)
|
first_cart = TestingCartController.for_user(self.USER_1)
|
||||||
second_cart = CartController.for_user(self.USER_2)
|
second_cart = TestingCartController.for_user(self.USER_2)
|
||||||
|
|
||||||
first_cart.add_to_cart(self.PROD_1, 1)
|
first_cart.add_to_cart(self.PROD_1, 1)
|
||||||
|
|
||||||
|
@ -111,8 +112,8 @@ class CeilingsTestCases(RegistrationCartTestCase):
|
||||||
def __validation_test(self):
|
def __validation_test(self):
|
||||||
self.set_time(datetime.datetime(2015, 01, 01, tzinfo=UTC))
|
self.set_time(datetime.datetime(2015, 01, 01, tzinfo=UTC))
|
||||||
|
|
||||||
first_cart = CartController.for_user(self.USER_1)
|
first_cart = TestingCartController.for_user(self.USER_1)
|
||||||
second_cart = CartController.for_user(self.USER_2)
|
second_cart = TestingCartController.for_user(self.USER_2)
|
||||||
|
|
||||||
# Adding a valid product should validate.
|
# Adding a valid product should validate.
|
||||||
first_cart.add_to_cart(self.PROD_1, 1)
|
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):
|
def test_items_released_from_ceiling_by_refund(self):
|
||||||
self.make_ceiling("Limit ceiling", limit=1)
|
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.add_to_cart(self.PROD_1, 1)
|
||||||
|
|
||||||
first_cart.cart.active = False
|
first_cart.cart.active = False
|
||||||
first_cart.cart.save()
|
first_cart.cart.save()
|
||||||
|
|
||||||
second_cart = CartController.for_user(self.USER_2)
|
second_cart = TestingCartController.for_user(self.USER_2)
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
second_cart.add_to_cart(self.PROD_1, 1)
|
second_cart.add_to_cart(self.PROD_1, 1)
|
||||||
|
|
||||||
|
@ -150,3 +151,36 @@ class CeilingsTestCases(RegistrationCartTestCase):
|
||||||
first_cart.cart.save()
|
first_cart.cart.save()
|
||||||
|
|
||||||
second_cart.add_to_cart(self.PROD_1, 1)
|
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 import models as rego
|
||||||
from registrasion.controllers import discount
|
from registrasion.controllers import discount
|
||||||
from registrasion.controllers.cart import CartController
|
from cart_controller_helper import TestingCartController
|
||||||
from registrasion.controllers.invoice import InvoiceController
|
from registrasion.controllers.invoice import InvoiceController
|
||||||
|
|
||||||
from test_cart import RegistrationCartTestCase
|
from test_cart import RegistrationCartTestCase
|
||||||
|
@ -84,7 +84,7 @@ class DiscountTestCase(RegistrationCartTestCase):
|
||||||
def test_discount_is_applied(self):
|
def test_discount_is_applied(self):
|
||||||
self.add_discount_prod_1_includes_prod_2()
|
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_1, 1)
|
||||||
cart.add_to_cart(self.PROD_2, 1)
|
cart.add_to_cart(self.PROD_2, 1)
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ class DiscountTestCase(RegistrationCartTestCase):
|
||||||
def test_discount_is_applied_for_category(self):
|
def test_discount_is_applied_for_category(self):
|
||||||
self.add_discount_prod_1_includes_cat_2()
|
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_1, 1)
|
||||||
cart.add_to_cart(self.PROD_3, 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):
|
def test_discount_does_not_apply_if_not_met(self):
|
||||||
self.add_discount_prod_1_includes_prod_2()
|
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_2, 1)
|
||||||
|
|
||||||
# No discount should be applied as the condition is not met
|
# 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):
|
def test_discount_applied_out_of_order(self):
|
||||||
self.add_discount_prod_1_includes_prod_2()
|
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_2, 1)
|
||||||
cart.add_to_cart(self.PROD_1, 1)
|
cart.add_to_cart(self.PROD_1, 1)
|
||||||
|
|
||||||
|
@ -123,7 +123,7 @@ class DiscountTestCase(RegistrationCartTestCase):
|
||||||
def test_discounts_collapse(self):
|
def test_discounts_collapse(self):
|
||||||
self.add_discount_prod_1_includes_prod_2()
|
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_1, 1)
|
||||||
cart.add_to_cart(self.PROD_2, 1)
|
cart.add_to_cart(self.PROD_2, 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):
|
def test_discounts_respect_quantity(self):
|
||||||
self.add_discount_prod_1_includes_prod_2()
|
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_1, 1)
|
||||||
cart.add_to_cart(self.PROD_2, 3)
|
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_full = self.add_discount_prod_1_includes_prod_2()
|
||||||
discount_half = self.add_discount_prod_1_includes_prod_2(Decimal(50))
|
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_1, 1)
|
||||||
cart.add_to_cart(self.PROD_2, 3)
|
cart.add_to_cart(self.PROD_2, 3)
|
||||||
|
|
||||||
|
@ -166,13 +166,13 @@ class DiscountTestCase(RegistrationCartTestCase):
|
||||||
self.add_discount_prod_1_includes_prod_2()
|
self.add_discount_prod_1_includes_prod_2()
|
||||||
|
|
||||||
# Enable the discount during the first cart.
|
# 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.add_to_cart(self.PROD_1, 1)
|
||||||
cart.cart.active = False
|
cart.cart.active = False
|
||||||
cart.cart.save()
|
cart.cart.save()
|
||||||
|
|
||||||
# Use the discount in the second cart
|
# 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)
|
cart.add_to_cart(self.PROD_2, 1)
|
||||||
|
|
||||||
# The discount should be applied.
|
# The discount should be applied.
|
||||||
|
@ -182,7 +182,7 @@ class DiscountTestCase(RegistrationCartTestCase):
|
||||||
|
|
||||||
# The discount should respect the total quantity across all
|
# The discount should respect the total quantity across all
|
||||||
# of the user's carts.
|
# 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)
|
cart.add_to_cart(self.PROD_2, 2)
|
||||||
|
|
||||||
# Having one item in the second cart leaves one more item where
|
# 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):
|
def test_discount_applies_only_once_enabled(self):
|
||||||
# Enable the discount during the first cart.
|
# 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.add_to_cart(self.PROD_1, 1)
|
||||||
# This would exhaust discount if present
|
# This would exhaust discount if present
|
||||||
cart.add_to_cart(self.PROD_2, 2)
|
cart.add_to_cart(self.PROD_2, 2)
|
||||||
|
@ -201,7 +201,7 @@ class DiscountTestCase(RegistrationCartTestCase):
|
||||||
cart.cart.save()
|
cart.cart.save()
|
||||||
|
|
||||||
self.add_discount_prod_1_includes_prod_2()
|
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)
|
cart.add_to_cart(self.PROD_2, 2)
|
||||||
|
|
||||||
discount_items = list(cart.cart.discountitem_set.all())
|
discount_items = list(cart.cart.discountitem_set.all())
|
||||||
|
@ -209,7 +209,7 @@ class DiscountTestCase(RegistrationCartTestCase):
|
||||||
|
|
||||||
def test_category_discount_applies_once_per_category(self):
|
def test_category_discount_applies_once_per_category(self):
|
||||||
self.add_discount_prod_1_includes_cat_2(quantity=1)
|
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)
|
cart.add_to_cart(self.PROD_1, 1)
|
||||||
|
|
||||||
# Add two items from category 2
|
# Add two items from category 2
|
||||||
|
@ -223,7 +223,7 @@ class DiscountTestCase(RegistrationCartTestCase):
|
||||||
|
|
||||||
def test_category_discount_applies_to_highest_value(self):
|
def test_category_discount_applies_to_highest_value(self):
|
||||||
self.add_discount_prod_1_includes_cat_2(quantity=1)
|
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)
|
cart.add_to_cart(self.PROD_1, 1)
|
||||||
|
|
||||||
# Add two items from category 2, add the less expensive one first
|
# 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
|
# Both users should be able to apply the same discount
|
||||||
# in the same way
|
# in the same way
|
||||||
for user in (self.USER_1, self.USER_2):
|
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_1, 1) # Enable the discount
|
||||||
cart.add_to_cart(self.PROD_3, 1)
|
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):
|
def test_category_discount_appears_once_if_met_twice(self):
|
||||||
self.add_discount_prod_1_includes_cat_2(quantity=1)
|
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
|
cart.add_to_cart(self.PROD_1, 1) # Enable the discount
|
||||||
|
|
||||||
discounts = discount.available_discounts(
|
discounts = discount.available_discounts(
|
||||||
|
@ -283,7 +283,7 @@ class DiscountTestCase(RegistrationCartTestCase):
|
||||||
def test_category_discount_appears_with_category(self):
|
def test_category_discount_appears_with_category(self):
|
||||||
self.add_discount_prod_1_includes_cat_2(quantity=1)
|
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
|
cart.add_to_cart(self.PROD_1, 1) # Enable the discount
|
||||||
|
|
||||||
discounts = discount.available_discounts(self.USER_1, [self.CAT_2], [])
|
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):
|
def test_category_discount_appears_with_product(self):
|
||||||
self.add_discount_prod_1_includes_cat_2(quantity=1)
|
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
|
cart.add_to_cart(self.PROD_1, 1) # Enable the discount
|
||||||
|
|
||||||
discounts = discount.available_discounts(
|
discounts = discount.available_discounts(
|
||||||
|
@ -305,7 +305,7 @@ class DiscountTestCase(RegistrationCartTestCase):
|
||||||
def test_category_discount_appears_once_with_two_valid_product(self):
|
def test_category_discount_appears_once_with_two_valid_product(self):
|
||||||
self.add_discount_prod_1_includes_cat_2(quantity=1)
|
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
|
cart.add_to_cart(self.PROD_1, 1) # Enable the discount
|
||||||
|
|
||||||
discounts = discount.available_discounts(
|
discounts = discount.available_discounts(
|
||||||
|
@ -318,7 +318,7 @@ class DiscountTestCase(RegistrationCartTestCase):
|
||||||
def test_product_discount_appears_with_product(self):
|
def test_product_discount_appears_with_product(self):
|
||||||
self.add_discount_prod_1_includes_prod_2(quantity=1)
|
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
|
cart.add_to_cart(self.PROD_1, 1) # Enable the discount
|
||||||
|
|
||||||
discounts = discount.available_discounts(
|
discounts = discount.available_discounts(
|
||||||
|
@ -331,7 +331,7 @@ class DiscountTestCase(RegistrationCartTestCase):
|
||||||
def test_product_discount_does_not_appear_with_category(self):
|
def test_product_discount_does_not_appear_with_category(self):
|
||||||
self.add_discount_prod_1_includes_prod_2(quantity=1)
|
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
|
cart.add_to_cart(self.PROD_1, 1) # Enable the discount
|
||||||
|
|
||||||
discounts = discount.available_discounts(self.USER_1, [self.CAT_1], [])
|
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):
|
def test_discount_quantity_is_correct_before_first_purchase(self):
|
||||||
self.add_discount_prod_1_includes_cat_2(quantity=2)
|
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_1, 1) # Enable the discount
|
||||||
cart.add_to_cart(self.PROD_3, 1) # Exhaust the quantity
|
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):
|
def test_discount_quantity_is_correct_after_first_purchase(self):
|
||||||
self.test_discount_quantity_is_correct_before_first_purchase()
|
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
|
cart.add_to_cart(self.PROD_3, 1) # Exhaust the quantity
|
||||||
|
|
||||||
discounts = discount.available_discounts(self.USER_1, [self.CAT_2], [])
|
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):
|
def test_product_discount_enabled_twice_appears_twice(self):
|
||||||
self.add_discount_prod_1_includes_prod_3_and_prod_4(quantity=2)
|
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
|
cart.add_to_cart(self.PROD_1, 1) # Enable the discount
|
||||||
discounts = discount.available_discounts(
|
discounts = discount.available_discounts(
|
||||||
self.USER_1,
|
self.USER_1,
|
||||||
|
@ -380,7 +380,7 @@ class DiscountTestCase(RegistrationCartTestCase):
|
||||||
|
|
||||||
def test_discounts_are_released_by_refunds(self):
|
def test_discounts_are_released_by_refunds(self):
|
||||||
self.add_discount_prod_1_includes_prod_2(quantity=2)
|
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
|
cart.add_to_cart(self.PROD_1, 1) # Enable the discount
|
||||||
discounts = discount.available_discounts(
|
discounts = discount.available_discounts(
|
||||||
self.USER_1,
|
self.USER_1,
|
||||||
|
@ -392,7 +392,7 @@ class DiscountTestCase(RegistrationCartTestCase):
|
||||||
cart.cart.active = False # Keep discount enabled
|
cart.cart.active = False # Keep discount enabled
|
||||||
cart.cart.save()
|
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.add_to_cart(self.PROD_2, 2) # The discount will be exhausted
|
||||||
cart.cart.active = False
|
cart.cart.active = False
|
||||||
cart.cart.save()
|
cart.cart.save()
|
||||||
|
|
|
@ -4,7 +4,7 @@ from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
from registrasion import models as rego
|
from registrasion import models as rego
|
||||||
from registrasion.controllers.category import CategoryController
|
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 registrasion.controllers.product import ProductController
|
||||||
|
|
||||||
from test_cart import RegistrationCartTestCase
|
from test_cart import RegistrationCartTestCase
|
||||||
|
@ -56,7 +56,7 @@ class EnablingConditionTestCases(RegistrationCartTestCase):
|
||||||
self.add_product_enabling_condition()
|
self.add_product_enabling_condition()
|
||||||
|
|
||||||
# Cannot buy PROD_1 without buying PROD_2
|
# 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):
|
with self.assertRaises(ValidationError):
|
||||||
current_cart.add_to_cart(self.PROD_1, 1)
|
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):
|
def test_product_enabled_by_product_in_previous_cart(self):
|
||||||
self.add_product_enabling_condition()
|
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.add_to_cart(self.PROD_2, 1)
|
||||||
current_cart.cart.active = False
|
current_cart.cart.active = False
|
||||||
current_cart.cart.save()
|
current_cart.cart.save()
|
||||||
|
|
||||||
# Create new cart and try to add PROD_1
|
# 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)
|
current_cart.add_to_cart(self.PROD_1, 1)
|
||||||
|
|
||||||
def test_product_enabling_condition_enables_category(self):
|
def test_product_enabling_condition_enables_category(self):
|
||||||
self.add_product_enabling_condition_on_category()
|
self.add_product_enabling_condition_on_category()
|
||||||
|
|
||||||
# Cannot buy PROD_1 without buying item from CAT_2
|
# 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):
|
with self.assertRaises(ValidationError):
|
||||||
current_cart.add_to_cart(self.PROD_1, 1)
|
current_cart.add_to_cart(self.PROD_1, 1)
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ class EnablingConditionTestCases(RegistrationCartTestCase):
|
||||||
self.add_category_enabling_condition()
|
self.add_category_enabling_condition()
|
||||||
|
|
||||||
# Cannot buy PROD_1 without buying PROD_2
|
# 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):
|
with self.assertRaises(ValidationError):
|
||||||
current_cart.add_to_cart(self.PROD_1, 1)
|
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):
|
def test_product_enabled_by_category_in_previous_cart(self):
|
||||||
self.add_category_enabling_condition()
|
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.add_to_cart(self.PROD_3, 1)
|
||||||
current_cart.cart.active = False
|
current_cart.cart.active = False
|
||||||
current_cart.cart.save()
|
current_cart.cart.save()
|
||||||
|
|
||||||
# Create new cart and try to add PROD_1
|
# 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)
|
current_cart.add_to_cart(self.PROD_1, 1)
|
||||||
|
|
||||||
def test_multiple_non_mandatory_conditions(self):
|
def test_multiple_non_mandatory_conditions(self):
|
||||||
|
@ -115,7 +115,7 @@ class EnablingConditionTestCases(RegistrationCartTestCase):
|
||||||
self.add_category_enabling_condition()
|
self.add_category_enabling_condition()
|
||||||
|
|
||||||
# User 1 is testing the product 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
|
# Cannot add PROD_1 until a condition is met
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
cart_1.add_to_cart(self.PROD_1, 1)
|
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)
|
cart_1.add_to_cart(self.PROD_1, 1)
|
||||||
|
|
||||||
# User 2 is testing the category enabling condition
|
# 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
|
# Cannot add PROD_1 until a condition is met
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
cart_2.add_to_cart(self.PROD_1, 1)
|
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_product_enabling_condition(mandatory=True)
|
||||||
self.add_category_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
|
# Cannot add PROD_1 until both conditions are met
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
cart_1.add_to_cart(self.PROD_1, 1)
|
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_product_enabling_condition(mandatory=False)
|
||||||
self.add_category_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
|
# Cannot add PROD_1 until both conditions are met
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
cart_1.add_to_cart(self.PROD_1, 1)
|
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):
|
def test_available_products_on_category_works_when_condition_is_met(self):
|
||||||
self.add_product_enabling_condition(mandatory=False)
|
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)
|
cart_1.add_to_cart(self.PROD_2, 1)
|
||||||
|
|
||||||
prods = ProductController.available_products(
|
prods = ProductController.available_products(
|
||||||
|
@ -224,7 +224,7 @@ class EnablingConditionTestCases(RegistrationCartTestCase):
|
||||||
def test_available_products_on_products_works_when_condition_is_met(self):
|
def test_available_products_on_products_works_when_condition_is_met(self):
|
||||||
self.add_product_enabling_condition(mandatory=False)
|
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)
|
cart_1.add_to_cart(self.PROD_2, 1)
|
||||||
|
|
||||||
prods = ProductController.available_products(
|
prods = ProductController.available_products(
|
||||||
|
@ -238,13 +238,13 @@ class EnablingConditionTestCases(RegistrationCartTestCase):
|
||||||
def test_category_enabling_condition_fails_if_cart_refunded(self):
|
def test_category_enabling_condition_fails_if_cart_refunded(self):
|
||||||
self.add_category_enabling_condition(mandatory=False)
|
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.add_to_cart(self.PROD_3, 1)
|
||||||
|
|
||||||
cart.cart.active = False
|
cart.cart.active = False
|
||||||
cart.cart.save()
|
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.add_to_cart(self.PROD_1, 1)
|
||||||
cart_2.set_quantity(self.PROD_1, 0)
|
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):
|
def test_product_enabling_condition_fails_if_cart_refunded(self):
|
||||||
self.add_product_enabling_condition(mandatory=False)
|
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.add_to_cart(self.PROD_2, 1)
|
||||||
|
|
||||||
cart.cart.active = False
|
cart.cart.active = False
|
||||||
cart.cart.save()
|
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.add_to_cart(self.PROD_1, 1)
|
||||||
cart_2.set_quantity(self.PROD_1, 0)
|
cart_2.set_quantity(self.PROD_1, 0)
|
||||||
|
|
||||||
|
@ -276,7 +276,7 @@ class EnablingConditionTestCases(RegistrationCartTestCase):
|
||||||
def test_available_categories(self):
|
def test_available_categories(self):
|
||||||
self.add_product_enabling_condition_on_category(mandatory=False)
|
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(
|
cats = CategoryController.available_categories(
|
||||||
self.USER_1,
|
self.USER_1,
|
||||||
|
|
|
@ -5,7 +5,7 @@ from decimal import Decimal
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
from registrasion import models as rego
|
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 registrasion.controllers.invoice import InvoiceController
|
||||||
|
|
||||||
from test_cart import RegistrationCartTestCase
|
from test_cart import RegistrationCartTestCase
|
||||||
|
@ -16,7 +16,7 @@ UTC = pytz.timezone('UTC')
|
||||||
class InvoiceTestCase(RegistrationCartTestCase):
|
class InvoiceTestCase(RegistrationCartTestCase):
|
||||||
|
|
||||||
def test_create_invoice(self):
|
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
|
# Should be able to create an invoice after the product is added
|
||||||
current_cart.add_to_cart(self.PROD_1, 1)
|
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):
|
def test_create_invoice_fails_if_cart_invalid(self):
|
||||||
self.make_ceiling("Limit ceiling", limit=1)
|
self.make_ceiling("Limit ceiling", limit=1)
|
||||||
self.set_time(datetime.datetime(2015, 01, 01, tzinfo=UTC))
|
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)
|
current_cart.add_to_cart(self.PROD_1, 1)
|
||||||
|
|
||||||
self.add_timedelta(self.RESERVATION * 2)
|
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)
|
cart_2.add_to_cart(self.PROD_1, 1)
|
||||||
|
|
||||||
# Now try to invoice the first user
|
# Now try to invoice the first user
|
||||||
|
@ -61,7 +61,7 @@ class InvoiceTestCase(RegistrationCartTestCase):
|
||||||
InvoiceController.for_cart(current_cart.cart)
|
InvoiceController.for_cart(current_cart.cart)
|
||||||
|
|
||||||
def test_paying_invoice_makes_new_cart(self):
|
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)
|
current_cart.add_to_cart(self.PROD_1, 1)
|
||||||
|
|
||||||
invoice = InvoiceController.for_cart(current_cart.cart)
|
invoice = InvoiceController.for_cart(current_cart.cart)
|
||||||
|
@ -74,7 +74,7 @@ class InvoiceTestCase(RegistrationCartTestCase):
|
||||||
self.assertFalse(invoice.invoice.cart.active)
|
self.assertFalse(invoice.invoice.cart.active)
|
||||||
|
|
||||||
# Asking for a cart should generate a new one
|
# 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)
|
self.assertNotEqual(current_cart.cart, new_cart.cart)
|
||||||
|
|
||||||
def test_invoice_includes_discounts(self):
|
def test_invoice_includes_discounts(self):
|
||||||
|
@ -96,7 +96,7 @@ class InvoiceTestCase(RegistrationCartTestCase):
|
||||||
quantity=1
|
quantity=1
|
||||||
).save()
|
).save()
|
||||||
|
|
||||||
current_cart = CartController.for_user(self.USER_1)
|
current_cart = TestingCartController.for_user(self.USER_1)
|
||||||
current_cart.apply_voucher(voucher.code)
|
current_cart.apply_voucher(voucher.code)
|
||||||
|
|
||||||
# Should be able to create an invoice after the product is added
|
# Should be able to create an invoice after the product is added
|
||||||
|
@ -112,7 +112,7 @@ class InvoiceTestCase(RegistrationCartTestCase):
|
||||||
invoice_1.invoice.value)
|
invoice_1.invoice.value)
|
||||||
|
|
||||||
def test_invoice_voids_self_if_cart_is_invalid(self):
|
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
|
# Should be able to create an invoice after the product is added
|
||||||
current_cart.add_to_cart(self.PROD_1, 1)
|
current_cart.add_to_cart(self.PROD_1, 1)
|
||||||
|
@ -134,7 +134,7 @@ class InvoiceTestCase(RegistrationCartTestCase):
|
||||||
self.assertFalse(invoice_2_new.invoice.void)
|
self.assertFalse(invoice_2_new.invoice.void)
|
||||||
|
|
||||||
def test_voiding_invoice_creates_new_invoice(self):
|
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
|
# Should be able to create an invoice after the product is added
|
||||||
current_cart.add_to_cart(self.PROD_1, 1)
|
current_cart.add_to_cart(self.PROD_1, 1)
|
||||||
|
@ -147,7 +147,7 @@ class InvoiceTestCase(RegistrationCartTestCase):
|
||||||
self.assertNotEqual(invoice_1.invoice, invoice_2.invoice)
|
self.assertNotEqual(invoice_1.invoice, invoice_2.invoice)
|
||||||
|
|
||||||
def test_cannot_pay_void_invoice(self):
|
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
|
# Should be able to create an invoice after the product is added
|
||||||
current_cart.add_to_cart(self.PROD_1, 1)
|
current_cart.add_to_cart(self.PROD_1, 1)
|
||||||
|
@ -159,7 +159,7 @@ class InvoiceTestCase(RegistrationCartTestCase):
|
||||||
invoice_1.pay("Reference", invoice_1.invoice.value)
|
invoice_1.pay("Reference", invoice_1.invoice.value)
|
||||||
|
|
||||||
def test_cannot_void_paid_invoice(self):
|
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
|
# Should be able to create an invoice after the product is added
|
||||||
current_cart.add_to_cart(self.PROD_1, 1)
|
current_cart.add_to_cart(self.PROD_1, 1)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
from registrasion.controllers.cart import CartController
|
from cart_controller_helper import TestingCartController
|
||||||
from registrasion.controllers.invoice import InvoiceController
|
from registrasion.controllers.invoice import InvoiceController
|
||||||
|
|
||||||
from test_cart import RegistrationCartTestCase
|
from test_cart import RegistrationCartTestCase
|
||||||
|
@ -11,7 +11,7 @@ UTC = pytz.timezone('UTC')
|
||||||
class RefundTestCase(RegistrationCartTestCase):
|
class RefundTestCase(RegistrationCartTestCase):
|
||||||
|
|
||||||
def test_refund_marks_void_and_unpaid_and_cart_released(self):
|
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
|
# Should be able to create an invoice after the product is added
|
||||||
current_cart.add_to_cart(self.PROD_1, 1)
|
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 django.db import IntegrityError
|
||||||
|
|
||||||
from registrasion import models as rego
|
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 registrasion.controllers.invoice import InvoiceController
|
||||||
|
|
||||||
from test_cart import RegistrationCartTestCase
|
from test_cart import RegistrationCartTestCase
|
||||||
|
@ -16,27 +16,17 @@ UTC = pytz.timezone('UTC')
|
||||||
|
|
||||||
class VoucherTestCases(RegistrationCartTestCase):
|
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):
|
def test_apply_voucher(self):
|
||||||
voucher = self.new_voucher()
|
voucher = self.new_voucher()
|
||||||
|
|
||||||
self.set_time(datetime.datetime(2015, 01, 01, tzinfo=UTC))
|
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)
|
cart_1.apply_voucher(voucher.code)
|
||||||
self.assertIn(voucher, cart_1.cart.vouchers.all())
|
self.assertIn(voucher, cart_1.cart.vouchers.all())
|
||||||
|
|
||||||
# Second user should not be able to apply this voucher (it's exhausted)
|
# 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):
|
with self.assertRaises(ValidationError):
|
||||||
cart_2.apply_voucher(voucher.code)
|
cart_2.apply_voucher(voucher.code)
|
||||||
|
|
||||||
|
@ -66,7 +56,7 @@ class VoucherTestCases(RegistrationCartTestCase):
|
||||||
enabling_condition.save()
|
enabling_condition.save()
|
||||||
|
|
||||||
# Adding the product without a voucher will not work
|
# 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):
|
with self.assertRaises(ValidationError):
|
||||||
current_cart.add_to_cart(self.PROD_1, 1)
|
current_cart.add_to_cart(self.PROD_1, 1)
|
||||||
|
|
||||||
|
@ -90,7 +80,7 @@ class VoucherTestCases(RegistrationCartTestCase):
|
||||||
).save()
|
).save()
|
||||||
|
|
||||||
# Having PROD_1 in place should add a discount
|
# 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.apply_voucher(voucher.code)
|
||||||
current_cart.add_to_cart(self.PROD_1, 1)
|
current_cart.add_to_cart(self.PROD_1, 1)
|
||||||
self.assertEqual(1, len(current_cart.cart.discountitem_set.all()))
|
self.assertEqual(1, len(current_cart.cart.discountitem_set.all()))
|
||||||
|
@ -106,19 +96,19 @@ class VoucherTestCases(RegistrationCartTestCase):
|
||||||
|
|
||||||
def test_vouchers_case_insensitive(self):
|
def test_vouchers_case_insensitive(self):
|
||||||
voucher = self.new_voucher(code="VOUCHeR")
|
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())
|
current_cart.apply_voucher(voucher.code.lower())
|
||||||
|
|
||||||
def test_voucher_can_only_be_applied_once(self):
|
def test_voucher_can_only_be_applied_once(self):
|
||||||
voucher = self.new_voucher(limit=2)
|
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)
|
current_cart.apply_voucher(voucher.code)
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
current_cart.apply_voucher(voucher.code)
|
current_cart.apply_voucher(voucher.code)
|
||||||
|
|
||||||
def test_voucher_can_only_be_applied_once_across_multiple_carts(self):
|
def test_voucher_can_only_be_applied_once_across_multiple_carts(self):
|
||||||
voucher = self.new_voucher(limit=2)
|
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)
|
current_cart.apply_voucher(voucher.code)
|
||||||
|
|
||||||
inv = InvoiceController.for_cart(current_cart.cart)
|
inv = InvoiceController.for_cart(current_cart.cart)
|
||||||
|
@ -131,13 +121,13 @@ class VoucherTestCases(RegistrationCartTestCase):
|
||||||
|
|
||||||
def test_refund_releases_used_vouchers(self):
|
def test_refund_releases_used_vouchers(self):
|
||||||
voucher = self.new_voucher(limit=2)
|
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)
|
current_cart.apply_voucher(voucher.code)
|
||||||
|
|
||||||
inv = InvoiceController.for_cart(current_cart.cart)
|
inv = InvoiceController.for_cart(current_cart.cart)
|
||||||
inv.pay("Hello!", inv.invoice.value)
|
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):
|
with self.assertRaises(ValidationError):
|
||||||
current_cart.apply_voucher(voucher.code)
|
current_cart.apply_voucher(voucher.code)
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ from registrasion.controllers import discount
|
||||||
from registrasion.controllers.cart import CartController
|
from registrasion.controllers.cart import CartController
|
||||||
from registrasion.controllers.invoice import InvoiceController
|
from registrasion.controllers.invoice import InvoiceController
|
||||||
from registrasion.controllers.product import ProductController
|
from registrasion.controllers.product import ProductController
|
||||||
|
from registrasion.exceptions import CartValidationError
|
||||||
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
|
@ -14,7 +15,6 @@ from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import transaction
|
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
@ -121,10 +121,6 @@ def guided_registration(request, page_id=0):
|
||||||
category=category,
|
category=category,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not products:
|
|
||||||
# This product category does not exist for this user
|
|
||||||
continue
|
|
||||||
|
|
||||||
prefix = "category_" + str(category.id)
|
prefix = "category_" + str(category.id)
|
||||||
p = handle_products(request, category, products, prefix)
|
p = handle_products(request, category, products, prefix)
|
||||||
products_form, discounts, products_handled = p
|
products_form, discounts, products_handled = p
|
||||||
|
@ -135,7 +131,9 @@ def guided_registration(request, page_id=0):
|
||||||
discounts=discounts,
|
discounts=discounts,
|
||||||
form=products_form,
|
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 request.method == "POST" and not products_form.errors:
|
||||||
if category.id > attendee.highest_complete_category:
|
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():
|
if request.method == "POST" and products_form.is_valid():
|
||||||
try:
|
if products_form.has_changed():
|
||||||
if products_form.has_changed():
|
set_quantities_from_products_form(products_form, current_cart)
|
||||||
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 category is required, the user must have at least one
|
# If category is required, the user must have at least one
|
||||||
# in an active+valid cart
|
# in an active+valid cart
|
||||||
|
@ -326,20 +320,26 @@ def handle_products(request, category, products, prefix):
|
||||||
return products_form, discounts, handled
|
return products_form, discounts, handled
|
||||||
|
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def set_quantities_from_products_form(products_form, current_cart):
|
def set_quantities_from_products_form(products_form, current_cart):
|
||||||
# TODO: issue #8 is a problem here.
|
|
||||||
quantities = list(products_form.product_quantities())
|
quantities = list(products_form.product_quantities())
|
||||||
quantities.sort(key=lambda item: item[1])
|
product_quantities = [
|
||||||
for product_id, quantity, field_name in quantities:
|
(rego.Product.objects.get(pk=i[0]), i[1]) for i in quantities
|
||||||
product = rego.Product.objects.get(pk=product_id)
|
]
|
||||||
try:
|
field_names = dict(
|
||||||
current_cart.set_quantity(product, quantity, batched=True)
|
(i[0][0], i[1][2]) for i in zip(product_quantities, quantities)
|
||||||
except ValidationError as ve:
|
)
|
||||||
products_form.add_error(field_name, ve)
|
|
||||||
if products_form.errors:
|
try:
|
||||||
raise ValidationError("Cannot add that stuff")
|
current_cart.set_quantities(product_quantities)
|
||||||
current_cart.end_batch()
|
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):
|
def handle_voucher(request, prefix):
|
||||||
|
|
Loading…
Add table
Reference in a new issue