diff --git a/registrasion/controllers/product.py b/registrasion/controllers/product.py index d91a2541534414761f0553287d52801c7b11c036..88beb8f8378cb353f348916b74d29b93d6e03c19 100644 --- a/registrasion/controllers/product.py +++ b/registrasion/controllers/product.py @@ -1,6 +1,7 @@ import itertools from django.db.models import Q +from django.db.models import Sum from registrasion import models as rego from conditions import ConditionController @@ -42,17 +43,25 @@ class ProductController(object): carts = rego.Cart.objects.filter(user=user) items = rego.ProductItem.objects.filter( - product=self.product, - cart=carts) + cart=carts, + ) - count = 0 - for item in items: - count += item.quantity + prod_items = items.filter(product=self.product) + cat_items = items.filter(product__category=self.product.category) - if quantity + count > self.product.limit_per_user: - return False - else: + prod_count = prod_items.aggregate(Sum("quantity"))["quantity__sum"] + cat_count = cat_items.aggregate(Sum("quantity"))["quantity__sum"] + + 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 diff --git a/registrasion/migrations/0007_auto_20160326_2105.py b/registrasion/migrations/0007_auto_20160326_2105.py new file mode 100644 index 0000000000000000000000000000000000000000..dbcf2ac73c861c64347dfc5a7431bce64e0c24b1 --- /dev/null +++ b/registrasion/migrations/0007_auto_20160326_2105.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('registrasion', '0006_category_required'), + ] + + operations = [ + migrations.AddField( + model_name='category', + name='limit_per_user', + field=models.PositiveIntegerField(null=True, verbose_name='Limit per user', blank=True), + ), + migrations.AlterField( + model_name='product', + name='limit_per_user', + field=models.PositiveIntegerField(null=True, verbose_name='Limit per user', blank=True), + ), + ] diff --git a/registrasion/models.py b/registrasion/models.py index 75949e243c6e5baf09a189e5541af7df642353fc..c95e174050ad5930182c6bc189da97d4589a9155 100644 --- a/registrasion/models.py +++ b/registrasion/models.py @@ -132,6 +132,10 @@ class Category(models.Model): name = models.CharField(max_length=65, verbose_name=_("Name")) description = models.CharField(max_length=255, verbose_name=_("Description")) + limit_per_user = models.PositiveIntegerField( + null=True, + blank=True, + verbose_name=_("Limit per user")) required = models.BooleanField(blank=True) order = models.PositiveIntegerField(verbose_name=("Display order")) render_type = models.IntegerField(choices=CATEGORY_RENDER_TYPES, @@ -153,6 +157,7 @@ class Product(models.Model): decimal_places=2, verbose_name=_("Price")) limit_per_user = models.PositiveIntegerField( + null=True, blank=True, verbose_name=_("Limit per user")) reservation_duration = models.DurationField( diff --git a/registrasion/tests/test_cart.py b/registrasion/tests/test_cart.py index b4ff3919f4f3fa6722aef7c8dae916ab6562a8e7..5989f6e2f0c8889ef82dcb47ef58d7d1a7a03073 100644 --- a/registrasion/tests/test_cart.py +++ b/registrasion/tests/test_cart.py @@ -35,7 +35,7 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase): cls.RESERVATION = datetime.timedelta(hours=1) cls.categories = [] - for i in xrange(3): + for i in xrange(2): cat = rego.Category.objects.create( name="Category " + str(i + 1), description="This is a test category", @@ -48,13 +48,12 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase): cls.CAT_1 = cls.categories[0] cls.CAT_2 = cls.categories[1] - cls.CAT_3 = cls.categories[2] cls.products = [] - for i in xrange(6): + for i in xrange(4): prod = rego.Product.objects.create( name="Product 1", - description="This is a test product." + description="This is a test product.", category=cls.categories[i / 2], # 2 products per category price=Decimal("10.00"), reservation_duration=cls.RESERVATION, @@ -68,8 +67,6 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase): cls.PROD_2 = cls.products[1] cls.PROD_3 = cls.products[2] cls.PROD_4 = cls.products[3] - cls.PROD_5 = cls.products[4] - cls.PROD_6 = cls.products[5] cls.PROD_4.price = Decimal("5.00") cls.PROD_4.save() @@ -208,3 +205,86 @@ class BasicCartTests(RegistrationCartTestCase): # Second user should not be affected by first user's limits second_user_cart = CartController.for_user(self.USER_2) second_user_cart.add_to_cart(self.PROD_1, 10) + + def set_limits(self): + self.CAT_2.limit_per_user = 10 + self.PROD_2.limit_per_user = None + self.PROD_3.limit_per_user = None + self.PROD_4.limit_per_user = 6 + + self.CAT_2.save() + self.PROD_2.save() + self.PROD_3.save() + self.PROD_4.save() + + def test_per_user_product_limit_ignored_if_blank(self): + self.set_limits() + + current_cart = CartController.for_user(self.USER_1) + # There is no product limit on PROD_2, and there is no cat limit + current_cart.add_to_cart(self.PROD_2, 1) + # There is no product limit on PROD_3, but there is a cat limit + current_cart.add_to_cart(self.PROD_3, 1) + + def test_per_user_category_limit_ignored_if_blank(self): + self.set_limits() + current_cart = CartController.for_user(self.USER_1) + # There is no product limit on PROD_2, and there is no cat limit + current_cart.add_to_cart(self.PROD_2, 1) + # There is no cat limit on PROD_1, but there is a prod limit + current_cart.add_to_cart(self.PROD_1, 1) + + def test_per_user_category_limit_only(self): + self.set_limits() + + current_cart = CartController.for_user(self.USER_1) + + # Cannot add to cart if category limit is filled by one product. + current_cart.set_quantity(self.PROD_3, 10) + with self.assertRaises(ValidationError): + current_cart.set_quantity(self.PROD_4, 1) + + # Can add to cart if category limit is not filled by one product + current_cart.set_quantity(self.PROD_3, 5) + current_cart.set_quantity(self.PROD_4, 5) + # Cannot add to cart if category limit is filled by two products + with self.assertRaises(ValidationError): + current_cart.add_to_cart(self.PROD_3, 1) + + current_cart.cart.active = False + current_cart.cart.save() + + current_cart = CartController.for_user(self.USER_1) + # The category limit should extend across carts + with self.assertRaises(ValidationError): + current_cart.add_to_cart(self.PROD_3, 10) + + def test_per_user_category_and_product_limits(self): + self.set_limits() + + current_cart = CartController.for_user(self.USER_1) + + # Hit both the product and category edges: + current_cart.set_quantity(self.PROD_3, 4) + current_cart.set_quantity(self.PROD_4, 6) + with self.assertRaises(ValidationError): + # There's unlimited PROD_3, but limited in the category + current_cart.add_to_cart(self.PROD_3, 1) + + current_cart.set_quantity(self.PROD_3, 0) + with self.assertRaises(ValidationError): + # There's only 6 allowed of PROD_4 + current_cart.add_to_cart(self.PROD_4, 1) + + # The limits should extend across carts... + current_cart.cart.active = False + current_cart.cart.save() + + current_cart = CartController.for_user(self.USER_1) + current_cart.set_quantity(self.PROD_3, 4) + + with self.assertRaises(ValidationError): + current_cart.set_quantity(self.PROD_3, 5) + + with self.assertRaises(ValidationError): + current_cart.set_quantity(self.PROD_4, 1)