From 4fedc733047a52b85814feb7f3626c6cb5aa635e Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Mon, 11 Apr 2016 17:55:00 +1000 Subject: [PATCH 1/7] Renames EnablingCondition to Flag where possible --- registrasion/admin.py | 26 ++++++++-------- registrasion/controllers/conditions.py | 20 ++++++------ .../migrations/0021_auto_20160411_0748.py | 31 +++++++++++++++++++ registrasion/models.py | 15 +++++---- registrasion/tests/test_cart.py | 4 +-- registrasion/tests/test_enabling_condition.py | 8 ++--- registrasion/tests/test_voucher.py | 2 +- 7 files changed, 70 insertions(+), 36 deletions(-) create mode 100644 registrasion/migrations/0021_auto_20160411_0748.py diff --git a/registrasion/admin.py b/registrasion/admin.py index 7155e177..67f50a8d 100644 --- a/registrasion/admin.py +++ b/registrasion/admin.py @@ -102,8 +102,8 @@ class VoucherDiscountInline(nested_admin.NestedStackedInline): ] -class VoucherEnablingConditionInline(nested_admin.NestedStackedInline): - model = rego.VoucherEnablingCondition +class VoucherFlagInline(nested_admin.NestedStackedInline): + model = rego.VoucherFlag verbose_name = _("Product and category enabled by voucher") verbose_name_plural = _("Products and categories enabled by voucher") @@ -125,7 +125,7 @@ class VoucherAdmin(nested_admin.NestedAdmin): discount_effects = None try: - enabling_effects = obj.voucherenablingcondition.effects() + enabling_effects = obj.voucherflag.effects() except ObjectDoesNotExist: enabling_effects = None @@ -140,20 +140,20 @@ class VoucherAdmin(nested_admin.NestedAdmin): list_display = ("recipient", "code", "effects") inlines = [ VoucherDiscountInline, - VoucherEnablingConditionInline, + VoucherFlagInline, ] # Enabling conditions -@admin.register(rego.ProductEnablingCondition) -class ProductEnablingConditionAdmin( +@admin.register(rego.ProductFlag) +class ProductFlagAdmin( nested_admin.NestedAdmin, EffectsDisplayMixin): def enablers(self, obj): return list(obj.enabling_products.all()) - model = rego.ProductEnablingCondition + model = rego.ProductFlag fields = ("description", "enabling_products", "mandatory", "products", "categories"), @@ -161,12 +161,12 @@ class ProductEnablingConditionAdmin( # Enabling conditions -@admin.register(rego.CategoryEnablingCondition) -class CategoryEnablingConditionAdmin( +@admin.register(rego.CategoryFlag) +class CategoryFlagAdmin( nested_admin.NestedAdmin, EffectsDisplayMixin): - model = rego.CategoryEnablingCondition + model = rego.CategoryFlag fields = ("description", "enabling_category", "mandatory", "products", "categories"), @@ -175,11 +175,11 @@ class CategoryEnablingConditionAdmin( # Enabling conditions -@admin.register(rego.TimeOrStockLimitEnablingCondition) -class TimeOrStockLimitEnablingConditionAdmin( +@admin.register(rego.TimeOrStockLimitFlag) +class TimeOrStockLimitFlagAdmin( nested_admin.NestedAdmin, EffectsDisplayMixin): - model = rego.TimeOrStockLimitEnablingCondition + model = rego.TimeOrStockLimitFlag list_display = ( "description", diff --git a/registrasion/controllers/conditions.py b/registrasion/controllers/conditions.py index 03b50118..787aed05 100644 --- a/registrasion/controllers/conditions.py +++ b/registrasion/controllers/conditions.py @@ -20,7 +20,7 @@ ConditionAndRemainder = namedtuple( class ConditionController(object): - ''' Base class for testing conditions that activate EnablingCondition + ''' Base class for testing conditions that activate Flag or Discount objects. ''' def __init__(self): @@ -29,15 +29,15 @@ class ConditionController(object): @staticmethod def for_condition(condition): CONTROLLERS = { - rego.CategoryEnablingCondition: CategoryConditionController, + rego.CategoryFlag: CategoryConditionController, rego.IncludedProductDiscount: ProductConditionController, - rego.ProductEnablingCondition: ProductConditionController, + rego.ProductFlag: ProductConditionController, rego.TimeOrStockLimitDiscount: TimeOrStockLimitDiscountController, - rego.TimeOrStockLimitEnablingCondition: - TimeOrStockLimitEnablingConditionController, + rego.TimeOrStockLimitFlag: + TimeOrStockLimitFlagController, rego.VoucherDiscount: VoucherConditionController, - rego.VoucherEnablingCondition: VoucherConditionController, + rego.VoucherFlag: VoucherConditionController, } try: @@ -211,7 +211,7 @@ class CategoryConditionController(ConditionController): class ProductConditionController(ConditionController): - ''' Condition tests for ProductEnablingCondition and + ''' Condition tests for ProductFlag and IncludedProductDiscount. ''' def __init__(self, condition): @@ -230,7 +230,7 @@ class ProductConditionController(ConditionController): class TimeOrStockLimitConditionController(ConditionController): - ''' Common condition tests for TimeOrStockLimit EnablingCondition and + ''' Common condition tests for TimeOrStockLimit Flag and Discount.''' def __init__(self, ceiling): @@ -280,7 +280,7 @@ class TimeOrStockLimitConditionController(ConditionController): return self.ceiling.limit - count -class TimeOrStockLimitEnablingConditionController( +class TimeOrStockLimitFlagController( TimeOrStockLimitConditionController): def _items(self): @@ -305,7 +305,7 @@ class TimeOrStockLimitDiscountController(TimeOrStockLimitConditionController): class VoucherConditionController(ConditionController): - ''' Condition test for VoucherEnablingCondition and VoucherDiscount.''' + ''' Condition test for VoucherFlag and VoucherDiscount.''' def __init__(self, condition): self.condition = condition diff --git a/registrasion/migrations/0021_auto_20160411_0748.py b/registrasion/migrations/0021_auto_20160411_0748.py new file mode 100644 index 00000000..345cd4ad --- /dev/null +++ b/registrasion/migrations/0021_auto_20160411_0748.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.2 on 2016-04-11 07:48 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('registrasion', '0020_auto_20160411_0258'), + ] + + operations = [ + migrations.RenameModel( + old_name='CategoryEnablingCondition', + new_name='CategoryFlag', + ), + migrations.RenameModel( + old_name='ProductEnablingCondition', + new_name='ProductFlag', + ), + migrations.RenameModel( + old_name='TimeOrStockLimitEnablingCondition', + new_name='TimeOrStockLimitFlag', + ), + migrations.RenameModel( + old_name='VoucherEnablingCondition', + new_name='VoucherFlag', + ), + ] diff --git a/registrasion/models.py b/registrasion/models.py index 6e625887..a69f67dc 100644 --- a/registrasion/models.py +++ b/registrasion/models.py @@ -375,6 +375,9 @@ class EnablingConditionBase(models.Model): condition defined on a Product or Category, it will only be enabled if at least one condition is met. ''' + # TODO: rename to EnablingConditionBase once https://code.djangoproject.com/ticket/26488 + # is solved. + objects = InheritanceManager() def __str__(self): @@ -405,7 +408,7 @@ class EnablingConditionBase(models.Model): ) -class TimeOrStockLimitEnablingCondition(EnablingConditionBase): +class TimeOrStockLimitFlag(EnablingConditionBase): ''' Registration product ceilings ''' class Meta: @@ -432,7 +435,7 @@ class TimeOrStockLimitEnablingCondition(EnablingConditionBase): @python_2_unicode_compatible -class ProductEnablingCondition(EnablingConditionBase): +class ProductFlag(EnablingConditionBase): ''' The condition is met because a specific product is purchased. ''' def __str__(self): @@ -446,7 +449,7 @@ class ProductEnablingCondition(EnablingConditionBase): @python_2_unicode_compatible -class CategoryEnablingCondition(EnablingConditionBase): +class CategoryFlag(EnablingConditionBase): ''' The condition is met because a product in a particular product is purchased. ''' @@ -461,7 +464,7 @@ class CategoryEnablingCondition(EnablingConditionBase): @python_2_unicode_compatible -class VoucherEnablingCondition(EnablingConditionBase): +class VoucherFlag(EnablingConditionBase): ''' The condition is met because a Voucher is present. This is for e.g. enabling sponsor tickets. ''' @@ -472,10 +475,10 @@ class VoucherEnablingCondition(EnablingConditionBase): # @python_2_unicode_compatible -class RoleEnablingCondition(object): +class RoleFlag(object): ''' The condition is met because the active user has a particular Role. This is for e.g. enabling Team tickets. ''' - # TODO: implement RoleEnablingCondition + # TODO: implement RoleFlag pass diff --git a/registrasion/tests/test_cart.py b/registrasion/tests/test_cart.py index f8a82c21..eb7ff528 100644 --- a/registrasion/tests/test_cart.py +++ b/registrasion/tests/test_cart.py @@ -95,7 +95,7 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase): @classmethod def make_ceiling(cls, name, limit=None, start_time=None, end_time=None): - limit_ceiling = rego.TimeOrStockLimitEnablingCondition.objects.create( + limit_ceiling = rego.TimeOrStockLimitFlag.objects.create( description=name, mandatory=True, limit=limit, @@ -109,7 +109,7 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase): @classmethod def make_category_ceiling( cls, name, limit=None, start_time=None, end_time=None): - limit_ceiling = rego.TimeOrStockLimitEnablingCondition.objects.create( + limit_ceiling = rego.TimeOrStockLimitFlag.objects.create( description=name, mandatory=True, limit=limit, diff --git a/registrasion/tests/test_enabling_condition.py b/registrasion/tests/test_enabling_condition.py index d977cc5c..2782e464 100644 --- a/registrasion/tests/test_enabling_condition.py +++ b/registrasion/tests/test_enabling_condition.py @@ -12,13 +12,13 @@ from test_cart import RegistrationCartTestCase UTC = pytz.timezone('UTC') -class EnablingConditionTestCases(RegistrationCartTestCase): +class FlagTestCases(RegistrationCartTestCase): @classmethod def add_product_enabling_condition(cls, mandatory=False): ''' Adds a product enabling condition: adding PROD_1 to a cart is predicated on adding PROD_2 beforehand. ''' - enabling_condition = rego.ProductEnablingCondition.objects.create( + enabling_condition = rego.ProductFlag.objects.create( description="Product condition", mandatory=mandatory, ) @@ -31,7 +31,7 @@ class EnablingConditionTestCases(RegistrationCartTestCase): def add_product_enabling_condition_on_category(cls, mandatory=False): ''' Adds a product enabling condition that operates on a category: adding an item from CAT_1 is predicated on adding PROD_3 beforehand ''' - enabling_condition = rego.ProductEnablingCondition.objects.create( + enabling_condition = rego.ProductFlag.objects.create( description="Product condition", mandatory=mandatory, ) @@ -43,7 +43,7 @@ class EnablingConditionTestCases(RegistrationCartTestCase): def add_category_enabling_condition(cls, mandatory=False): ''' Adds a category enabling condition: adding PROD_1 to a cart is predicated on adding an item from CAT_2 beforehand.''' - enabling_condition = rego.CategoryEnablingCondition.objects.create( + enabling_condition = rego.CategoryFlag.objects.create( description="Category condition", mandatory=mandatory, enabling_category=cls.CAT_2, diff --git a/registrasion/tests/test_voucher.py b/registrasion/tests/test_voucher.py index d4614efb..de3f93ee 100644 --- a/registrasion/tests/test_voucher.py +++ b/registrasion/tests/test_voucher.py @@ -58,7 +58,7 @@ class VoucherTestCases(RegistrationCartTestCase): def test_voucher_enables_item(self): voucher = self.new_voucher() - enabling_condition = rego.VoucherEnablingCondition.objects.create( + enabling_condition = rego.VoucherFlag.objects.create( description="Voucher condition", voucher=voucher, mandatory=False, From 7b476fd5cb04ce106f291a06f30cb36cdb311267 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Mon, 11 Apr 2016 17:56:11 +1000 Subject: [PATCH 2/7] s/enabling_condition/flag --- registrasion/tests/test_enabling_condition.py | 86 +++++++++---------- registrasion/tests/test_voucher.py | 8 +- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/registrasion/tests/test_enabling_condition.py b/registrasion/tests/test_enabling_condition.py index 2782e464..36cf34f1 100644 --- a/registrasion/tests/test_enabling_condition.py +++ b/registrasion/tests/test_enabling_condition.py @@ -15,45 +15,45 @@ UTC = pytz.timezone('UTC') class FlagTestCases(RegistrationCartTestCase): @classmethod - def add_product_enabling_condition(cls, mandatory=False): + def add_product_flag(cls, mandatory=False): ''' Adds a product enabling condition: adding PROD_1 to a cart is predicated on adding PROD_2 beforehand. ''' - enabling_condition = rego.ProductFlag.objects.create( + flag = rego.ProductFlag.objects.create( description="Product condition", mandatory=mandatory, ) - enabling_condition.save() - enabling_condition.products.add(cls.PROD_1) - enabling_condition.enabling_products.add(cls.PROD_2) - enabling_condition.save() + flag.save() + flag.products.add(cls.PROD_1) + flag.enabling_products.add(cls.PROD_2) + flag.save() @classmethod - def add_product_enabling_condition_on_category(cls, mandatory=False): + def add_product_flag_on_category(cls, mandatory=False): ''' Adds a product enabling condition that operates on a category: adding an item from CAT_1 is predicated on adding PROD_3 beforehand ''' - enabling_condition = rego.ProductFlag.objects.create( + flag = rego.ProductFlag.objects.create( description="Product condition", mandatory=mandatory, ) - enabling_condition.save() - enabling_condition.categories.add(cls.CAT_1) - enabling_condition.enabling_products.add(cls.PROD_3) - enabling_condition.save() + flag.save() + flag.categories.add(cls.CAT_1) + flag.enabling_products.add(cls.PROD_3) + flag.save() - def add_category_enabling_condition(cls, mandatory=False): + def add_category_flag(cls, mandatory=False): ''' Adds a category enabling condition: adding PROD_1 to a cart is predicated on adding an item from CAT_2 beforehand.''' - enabling_condition = rego.CategoryFlag.objects.create( + flag = rego.CategoryFlag.objects.create( description="Category condition", mandatory=mandatory, enabling_category=cls.CAT_2, ) - enabling_condition.save() - enabling_condition.products.add(cls.PROD_1) - enabling_condition.save() + flag.save() + flag.products.add(cls.PROD_1) + flag.save() - def test_product_enabling_condition_enables_product(self): - self.add_product_enabling_condition() + def test_product_flag_enables_product(self): + self.add_product_flag() # Cannot buy PROD_1 without buying PROD_2 current_cart = TestingCartController.for_user(self.USER_1) @@ -64,7 +64,7 @@ class FlagTestCases(RegistrationCartTestCase): current_cart.add_to_cart(self.PROD_1, 1) def test_product_enabled_by_product_in_previous_cart(self): - self.add_product_enabling_condition() + self.add_product_flag() current_cart = TestingCartController.for_user(self.USER_1) current_cart.add_to_cart(self.PROD_2, 1) @@ -75,8 +75,8 @@ class FlagTestCases(RegistrationCartTestCase): current_cart = TestingCartController.for_user(self.USER_1) current_cart.add_to_cart(self.PROD_1, 1) - def test_product_enabling_condition_enables_category(self): - self.add_product_enabling_condition_on_category() + def test_product_flag_enables_category(self): + self.add_product_flag_on_category() # Cannot buy PROD_1 without buying item from CAT_2 current_cart = TestingCartController.for_user(self.USER_1) @@ -86,8 +86,8 @@ class FlagTestCases(RegistrationCartTestCase): current_cart.add_to_cart(self.PROD_3, 1) current_cart.add_to_cart(self.PROD_1, 1) - def test_category_enabling_condition_enables_product(self): - self.add_category_enabling_condition() + def test_category_flag_enables_product(self): + self.add_category_flag() # Cannot buy PROD_1 without buying PROD_2 current_cart = TestingCartController.for_user(self.USER_1) @@ -99,7 +99,7 @@ class FlagTestCases(RegistrationCartTestCase): current_cart.add_to_cart(self.PROD_1, 1) def test_product_enabled_by_category_in_previous_cart(self): - self.add_category_enabling_condition() + self.add_category_flag() current_cart = TestingCartController.for_user(self.USER_1) current_cart.add_to_cart(self.PROD_3, 1) @@ -111,8 +111,8 @@ class FlagTestCases(RegistrationCartTestCase): current_cart.add_to_cart(self.PROD_1, 1) def test_multiple_non_mandatory_conditions(self): - self.add_product_enabling_condition() - self.add_category_enabling_condition() + self.add_product_flag() + self.add_category_flag() # User 1 is testing the product enabling condition cart_1 = TestingCartController.for_user(self.USER_1) @@ -131,8 +131,8 @@ class FlagTestCases(RegistrationCartTestCase): cart_2.add_to_cart(self.PROD_1, 1) def test_multiple_mandatory_conditions(self): - self.add_product_enabling_condition(mandatory=True) - self.add_category_enabling_condition(mandatory=True) + self.add_product_flag(mandatory=True) + self.add_category_flag(mandatory=True) cart_1 = TestingCartController.for_user(self.USER_1) # Cannot add PROD_1 until both conditions are met @@ -145,8 +145,8 @@ class FlagTestCases(RegistrationCartTestCase): cart_1.add_to_cart(self.PROD_1, 1) def test_mandatory_conditions_are_mandatory(self): - self.add_product_enabling_condition(mandatory=False) - self.add_category_enabling_condition(mandatory=True) + self.add_product_flag(mandatory=False) + self.add_category_flag(mandatory=True) cart_1 = TestingCartController.for_user(self.USER_1) # Cannot add PROD_1 until both conditions are met @@ -186,7 +186,7 @@ class FlagTestCases(RegistrationCartTestCase): self.assertTrue(self.PROD_4 in prods) def test_available_products_on_category_works_when_condition_not_met(self): - self.add_product_enabling_condition(mandatory=False) + self.add_product_flag(mandatory=False) prods = ProductController.available_products( self.USER_1, @@ -197,7 +197,7 @@ class FlagTestCases(RegistrationCartTestCase): self.assertTrue(self.PROD_2 in prods) def test_available_products_on_category_works_when_condition_is_met(self): - self.add_product_enabling_condition(mandatory=False) + self.add_product_flag(mandatory=False) cart_1 = TestingCartController.for_user(self.USER_1) cart_1.add_to_cart(self.PROD_2, 1) @@ -211,7 +211,7 @@ class FlagTestCases(RegistrationCartTestCase): self.assertTrue(self.PROD_2 in prods) def test_available_products_on_products_works_when_condition_not_met(self): - self.add_product_enabling_condition(mandatory=False) + self.add_product_flag(mandatory=False) prods = ProductController.available_products( self.USER_1, @@ -222,7 +222,7 @@ class FlagTestCases(RegistrationCartTestCase): self.assertTrue(self.PROD_2 in prods) def test_available_products_on_products_works_when_condition_is_met(self): - self.add_product_enabling_condition(mandatory=False) + self.add_product_flag(mandatory=False) cart_1 = TestingCartController.for_user(self.USER_1) cart_1.add_to_cart(self.PROD_2, 1) @@ -235,8 +235,8 @@ class FlagTestCases(RegistrationCartTestCase): self.assertTrue(self.PROD_1 in prods) self.assertTrue(self.PROD_2 in prods) - def test_category_enabling_condition_fails_if_cart_refunded(self): - self.add_category_enabling_condition(mandatory=False) + def test_category_flag_fails_if_cart_refunded(self): + self.add_category_flag(mandatory=False) cart = TestingCartController.for_user(self.USER_1) cart.add_to_cart(self.PROD_3, 1) @@ -253,8 +253,8 @@ class FlagTestCases(RegistrationCartTestCase): with self.assertRaises(ValidationError): cart_2.set_quantity(self.PROD_1, 1) - def test_product_enabling_condition_fails_if_cart_refunded(self): - self.add_product_enabling_condition(mandatory=False) + def test_product_flag_fails_if_cart_refunded(self): + self.add_product_flag(mandatory=False) cart = TestingCartController.for_user(self.USER_1) cart.add_to_cart(self.PROD_2, 1) @@ -272,7 +272,7 @@ class FlagTestCases(RegistrationCartTestCase): cart_2.set_quantity(self.PROD_1, 1) def test_available_categories(self): - self.add_product_enabling_condition_on_category(mandatory=False) + self.add_product_flag_on_category(mandatory=False) cart_1 = TestingCartController.for_user(self.USER_1) @@ -292,8 +292,8 @@ class FlagTestCases(RegistrationCartTestCase): self.assertTrue(self.CAT_1 in cats) self.assertTrue(self.CAT_2 in cats) - def test_validate_cart_when_enabling_conditions_become_unmet(self): - self.add_product_enabling_condition(mandatory=False) + def test_validate_cart_when_flags_become_unmet(self): + self.add_product_flag(mandatory=False) cart = TestingCartController.for_user(self.USER_1) cart.add_to_cart(self.PROD_2, 1) @@ -309,7 +309,7 @@ class FlagTestCases(RegistrationCartTestCase): cart.validate_cart() def test_fix_simple_errors_resolves_unavailable_products(self): - self.test_validate_cart_when_enabling_conditions_become_unmet() + self.test_validate_cart_when_flags_become_unmet() cart = TestingCartController.for_user(self.USER_1) # Should just remove all of the unavailable products diff --git a/registrasion/tests/test_voucher.py b/registrasion/tests/test_voucher.py index de3f93ee..894c6635 100644 --- a/registrasion/tests/test_voucher.py +++ b/registrasion/tests/test_voucher.py @@ -58,14 +58,14 @@ class VoucherTestCases(RegistrationCartTestCase): def test_voucher_enables_item(self): voucher = self.new_voucher() - enabling_condition = rego.VoucherFlag.objects.create( + flag = rego.VoucherFlag.objects.create( description="Voucher condition", voucher=voucher, mandatory=False, ) - enabling_condition.save() - enabling_condition.products.add(self.PROD_1) - enabling_condition.save() + flag.save() + flag.products.add(self.PROD_1) + flag.save() # Adding the product without a voucher will not work current_cart = TestingCartController.for_user(self.USER_1) From e88a287fefa68ac12a79ae1c2c51d77a5f84f32c Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Mon, 11 Apr 2016 17:59:20 +1000 Subject: [PATCH 3/7] renames test_enabling_condition to test_flag --- registrasion/tests/{test_enabling_condition.py => test_flag.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename registrasion/tests/{test_enabling_condition.py => test_flag.py} (100%) diff --git a/registrasion/tests/test_enabling_condition.py b/registrasion/tests/test_flag.py similarity index 100% rename from registrasion/tests/test_enabling_condition.py rename to registrasion/tests/test_flag.py From c4c8a7ab82963ee3c3ecec098a7f133efabbde44 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Mon, 11 Apr 2016 18:12:37 +1000 Subject: [PATCH 4/7] Tidies up the admin interface for flags --- .../migrations/0022_auto_20160411_0806.py | 31 +++++++++++++++++++ registrasion/models.py | 19 ++++++++++-- 2 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 registrasion/migrations/0022_auto_20160411_0806.py diff --git a/registrasion/migrations/0022_auto_20160411_0806.py b/registrasion/migrations/0022_auto_20160411_0806.py new file mode 100644 index 00000000..436937ce --- /dev/null +++ b/registrasion/migrations/0022_auto_20160411_0806.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.2 on 2016-04-11 08:06 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('registrasion', '0021_auto_20160411_0748'), + ] + + operations = [ + migrations.AlterModelOptions( + name='categoryflag', + options={'verbose_name': 'flag (dependency on product from category)', 'verbose_name_plural': 'flags (dependency on product from category)'}, + ), + migrations.AlterModelOptions( + name='productflag', + options={'verbose_name': 'flag (dependency on product)', 'verbose_name_plural': 'flags (dependency on product)'}, + ), + migrations.AlterModelOptions( + name='timeorstocklimitflag', + options={'verbose_name': 'flag (time/stock limit)', 'verbose_name_plural': 'flags (time/stock limit)'}, + ), + migrations.AlterModelOptions( + name='voucherflag', + options={'verbose_name': 'flag (dependency on voucher)', 'verbose_name_plural': 'flags (dependency on voucher)'}, + ), + ] diff --git a/registrasion/models.py b/registrasion/models.py index a69f67dc..a7137f02 100644 --- a/registrasion/models.py +++ b/registrasion/models.py @@ -375,8 +375,8 @@ class EnablingConditionBase(models.Model): condition defined on a Product or Category, it will only be enabled if at least one condition is met. ''' - # TODO: rename to EnablingConditionBase once https://code.djangoproject.com/ticket/26488 - # is solved. + # TODO: rename to EnablingConditionBase once + # https://code.djangoproject.com/ticket/26488 is solved. objects = InheritanceManager() @@ -412,7 +412,8 @@ class TimeOrStockLimitFlag(EnablingConditionBase): ''' Registration product ceilings ''' class Meta: - verbose_name = _("ceiling") + verbose_name = _("flag (time/stock limit)") + verbose_name_plural = _("flags (time/stock limit)") start_time = models.DateTimeField( null=True, @@ -438,6 +439,10 @@ class TimeOrStockLimitFlag(EnablingConditionBase): class ProductFlag(EnablingConditionBase): ''' The condition is met because a specific product is purchased. ''' + class Meta: + verbose_name = _("flag (dependency on product)") + verbose_name_plural = _("flags (dependency on product)") + def __str__(self): return "Enabled by products: " + str(self.enabling_products.all()) @@ -453,6 +458,10 @@ class CategoryFlag(EnablingConditionBase): ''' The condition is met because a product in a particular product is purchased. ''' + class Meta: + verbose_name = _("flag (dependency on product from category)") + verbose_name_plural = _("flags (dependency on product from category)") + def __str__(self): return "Enabled by product in category: " + str(self.enabling_category) @@ -468,6 +477,10 @@ class VoucherFlag(EnablingConditionBase): ''' The condition is met because a Voucher is present. This is for e.g. enabling sponsor tickets. ''' + class Meta: + verbose_name = _("flag (dependency on voucher)") + verbose_name_plural = _("flags (dependency on voucher)") + def __str__(self): return "Enabled by voucher: %s" % self.voucher From 638ec261265cb3e408808c58dbb7f620c791b847 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Mon, 11 Apr 2016 20:02:16 +1000 Subject: [PATCH 5/7] Replaces the mandatory/non-mandatory concept with the enabled_if_true/disabled_if_false concept. Closes #4. --- registrasion/controllers/conditions.py | 33 +++++----- ...1_1001_squashed_0024_auto_20160411_1002.py | 37 ++++++++++++ registrasion/models.py | 60 ++++++++++++++----- registrasion/tests/test_cart.py | 4 +- registrasion/tests/test_flag.py | 60 ++++++++++++------- registrasion/tests/test_voucher.py | 2 +- 6 files changed, 138 insertions(+), 58 deletions(-) create mode 100644 registrasion/migrations/0023_auto_20160411_1001_squashed_0024_auto_20160411_1002.py diff --git a/registrasion/controllers/conditions.py b/registrasion/controllers/conditions.py index 787aed05..ddae0de7 100644 --- a/registrasion/controllers/conditions.py +++ b/registrasion/controllers/conditions.py @@ -107,11 +107,11 @@ class ConditionController(object): else: 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) + # All disable-if-false conditions on a product need to be met + do_not_disable = defaultdict(lambda: True) + # At least one enable-if-true condition on a product must be met + do_enable = defaultdict(lambda: False) + # (if either sort of condition is present) messages = {} @@ -146,22 +146,23 @@ class ConditionController(object): message = base % {"items": items, "remainder": remainder} for product in all_products: - if condition.mandatory: - mandatory[product] &= met + if condition.is_disable_if_false: + do_not_disable[product] &= met else: - non_mandatory[product] |= met + do_enable[product] |= met if not met and product not in messages: messages[product] = message - 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] + valid = {} + for product in itertools.chain(do_not_disable, do_enable): + if product in do_enable: + # If there's an enable-if-true, we need need of those met too. + # (do_not_disable will default to true otherwise) + valid[product] = do_not_disable[product] and do_enable[product] + elif product in do_not_disable: + # If there's a disable-if-false condition, all must be met + valid[product] = do_not_disable[product] error_fields = [ (product, messages[product]) diff --git a/registrasion/migrations/0023_auto_20160411_1001_squashed_0024_auto_20160411_1002.py b/registrasion/migrations/0023_auto_20160411_1001_squashed_0024_auto_20160411_1002.py new file mode 100644 index 00000000..e9a9f6bb --- /dev/null +++ b/registrasion/migrations/0023_auto_20160411_1001_squashed_0024_auto_20160411_1002.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.2 on 2016-04-11 10:46 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + replaces = [('registrasion', '0023_auto_20160411_1001'), ('registrasion', '0024_auto_20160411_1002')] + + dependencies = [ + ('registrasion', '0022_auto_20160411_0806'), + ] + + operations = [ + migrations.AlterField( + model_name='enablingconditionbase', + name='categories', + field=models.ManyToManyField(blank=True, help_text="Categories whose products are affected by this flag's condition.", to=b'registrasion.Category'), + ), + migrations.RenameField( + model_name='enablingconditionbase', + old_name='mandatory', + new_name='condition', + ), + migrations.AlterField( + model_name='enablingconditionbase', + name='condition', + field=models.IntegerField(choices=[(1, 'Disable if false'), (2, 'Enable if true')], default=2, help_text="If there is at least one 'disable if false' flag defined on a product or category, all such flag conditions must be met. If there is at least one 'enable if true' flag, at least one such condition must be met. If both types of conditions exist on a product, both of these rules apply."), + ), + migrations.AlterField( + model_name='enablingconditionbase', + name='products', + field=models.ManyToManyField(blank=True, help_text="Products affected by this flag's condition.", to=b'registrasion.Product'), + ), + ] diff --git a/registrasion/models.py b/registrasion/models.py index a7137f02..58ec1f96 100644 --- a/registrasion/models.py +++ b/registrasion/models.py @@ -366,16 +366,28 @@ class RoleDiscount(object): pass +class FlagBase(object): + ''' This will replace EnablingConditionBase once it's ready. ''' + + DISABLE_IF_FALSE = 1 + ENABLE_IF_TRUE = 2 + + @python_2_unicode_compatible class EnablingConditionBase(models.Model): ''' This defines a condition which allows products or categories to - be made visible. If there is at least one mandatory enabling condition - defined on a Product or Category, it will only be enabled if *all* - mandatory conditions are met, otherwise, if there is at least one enabling - condition defined on a Product or Category, it will only be enabled if at - least one condition is met. ''' + be made visible, or be prevented from being visible. - # TODO: rename to EnablingConditionBase once + The various subclasses of this can define the conditions that enable + or disable products, by the following rules: + + If there is at least one 'disable if false' flag defined on a product or + category, all such flag conditions must be met. If there is at least one + 'enable if true' flag, at least one such condition must be met. + + If both types of conditions exist on a product, both of these rules apply. + ''' + # TODO: rename to FlagBase once # https://code.djangoproject.com/ticket/26488 is solved. objects = InheritanceManager() @@ -384,27 +396,43 @@ class EnablingConditionBase(models.Model): return self.description def effects(self): - ''' Returns all of the items enabled by this condition. ''' + ''' Returns all of the items affected by this condition. ''' return itertools.chain(self.products.all(), self.categories.all()) + @property + def is_disable_if_false(self): + return self.condition == FlagBase.DISABLE_IF_FALSE + + @property + def is_enable_if_true(self): + return self.condition == FlagBase.ENABLE_IF_TRUE + description = models.CharField(max_length=255) - mandatory = models.BooleanField( - default=False, - help_text=_("If there is at least one mandatory condition defined on " - "a product or category, all such conditions must be met. " - "Otherwise, at least one non-mandatory condition must be " - "met."), + condition = models.IntegerField( + default=FlagBase.ENABLE_IF_TRUE, + choices=( + (FlagBase.DISABLE_IF_FALSE, _("Disable if false")), + (FlagBase.ENABLE_IF_TRUE, _("Enable if true")), + ), + help_text=_("If there is at least one 'disable if false' flag " + "defined on a product or category, all such flag " + " conditions must be met. If there is at least one " + "'enable if true' flag, at least one such condition must " + "be met. If both types of conditions exist on a product, " + "both of these rules apply." + ), ) products = models.ManyToManyField( Product, blank=True, - help_text=_("Products that are enabled if this condition is met."), + help_text=_("Products affected by this flag's condition."), ) categories = models.ManyToManyField( Category, blank=True, - help_text=_("Categories whose products are enabled if this condition " - "is met."), + help_text=_("Categories whose products are affected by this flag's " + "condition." + ), ) diff --git a/registrasion/tests/test_cart.py b/registrasion/tests/test_cart.py index eb7ff528..066bf377 100644 --- a/registrasion/tests/test_cart.py +++ b/registrasion/tests/test_cart.py @@ -97,7 +97,7 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase): def make_ceiling(cls, name, limit=None, start_time=None, end_time=None): limit_ceiling = rego.TimeOrStockLimitFlag.objects.create( description=name, - mandatory=True, + condition=rego.FlagBase.DISABLE_IF_FALSE, limit=limit, start_time=start_time, end_time=end_time @@ -111,7 +111,7 @@ class RegistrationCartTestCase(SetTimeMixin, TestCase): cls, name, limit=None, start_time=None, end_time=None): limit_ceiling = rego.TimeOrStockLimitFlag.objects.create( description=name, - mandatory=True, + condition=rego.FlagBase.DISABLE_IF_FALSE, limit=limit, start_time=start_time, end_time=end_time diff --git a/registrasion/tests/test_flag.py b/registrasion/tests/test_flag.py index 36cf34f1..b1ccdc3c 100644 --- a/registrasion/tests/test_flag.py +++ b/registrasion/tests/test_flag.py @@ -15,12 +15,12 @@ UTC = pytz.timezone('UTC') class FlagTestCases(RegistrationCartTestCase): @classmethod - def add_product_flag(cls, mandatory=False): + def add_product_flag(cls, condition=rego.FlagBase.ENABLE_IF_TRUE): ''' Adds a product enabling condition: adding PROD_1 to a cart is predicated on adding PROD_2 beforehand. ''' flag = rego.ProductFlag.objects.create( description="Product condition", - mandatory=mandatory, + condition=condition, ) flag.save() flag.products.add(cls.PROD_1) @@ -28,24 +28,24 @@ class FlagTestCases(RegistrationCartTestCase): flag.save() @classmethod - def add_product_flag_on_category(cls, mandatory=False): + def add_product_flag_on_category(cls, condition=rego.FlagBase.ENABLE_IF_TRUE): ''' Adds a product enabling condition that operates on a category: adding an item from CAT_1 is predicated on adding PROD_3 beforehand ''' flag = rego.ProductFlag.objects.create( description="Product condition", - mandatory=mandatory, + condition=condition, ) flag.save() flag.categories.add(cls.CAT_1) flag.enabling_products.add(cls.PROD_3) flag.save() - def add_category_flag(cls, mandatory=False): + def add_category_flag(cls, condition=rego.FlagBase.ENABLE_IF_TRUE): ''' Adds a category enabling condition: adding PROD_1 to a cart is predicated on adding an item from CAT_2 beforehand.''' flag = rego.CategoryFlag.objects.create( description="Category condition", - mandatory=mandatory, + condition=condition, enabling_category=cls.CAT_2, ) flag.save() @@ -110,7 +110,7 @@ class FlagTestCases(RegistrationCartTestCase): current_cart = TestingCartController.for_user(self.USER_1) current_cart.add_to_cart(self.PROD_1, 1) - def test_multiple_non_mandatory_conditions(self): + def test_multiple_eit_conditions(self): self.add_product_flag() self.add_category_flag() @@ -130,9 +130,9 @@ class FlagTestCases(RegistrationCartTestCase): cart_2.add_to_cart(self.PROD_3, 1) cart_2.add_to_cart(self.PROD_1, 1) - def test_multiple_mandatory_conditions(self): - self.add_product_flag(mandatory=True) - self.add_category_flag(mandatory=True) + def test_multiple_dif_conditions(self): + self.add_product_flag(condition=rego.FlagBase.DISABLE_IF_FALSE) + self.add_category_flag(condition=rego.FlagBase.DISABLE_IF_FALSE) cart_1 = TestingCartController.for_user(self.USER_1) # Cannot add PROD_1 until both conditions are met @@ -144,18 +144,32 @@ class FlagTestCases(RegistrationCartTestCase): cart_1.add_to_cart(self.PROD_3, 1) # Meets the category condition cart_1.add_to_cart(self.PROD_1, 1) - def test_mandatory_conditions_are_mandatory(self): - self.add_product_flag(mandatory=False) - self.add_category_flag(mandatory=True) + def test_eit_and_dif_conditions_work_together(self): + self.add_product_flag(condition=rego.FlagBase.ENABLE_IF_TRUE) + self.add_category_flag(condition=rego.FlagBase.DISABLE_IF_FALSE) cart_1 = TestingCartController.for_user(self.USER_1) # Cannot add PROD_1 until both conditions are met with self.assertRaises(ValidationError): cart_1.add_to_cart(self.PROD_1, 1) - cart_1.add_to_cart(self.PROD_2, 1) # Meets the product condition + + cart_1.add_to_cart(self.PROD_2, 1) # Meets the EIT condition + + # Need to meet both conditions before you can add with self.assertRaises(ValidationError): cart_1.add_to_cart(self.PROD_1, 1) - cart_1.add_to_cart(self.PROD_3, 1) # Meets the category condition + + cart_1.set_quantity(self.PROD_2, 0) # Un-meets the EIT condition + + cart_1.add_to_cart(self.PROD_3, 1) # Meets the DIF condition + + # Need to meet both conditions before you can add + with self.assertRaises(ValidationError): + cart_1.add_to_cart(self.PROD_1, 1) + + cart_1.add_to_cart(self.PROD_2, 1) # Meets the EIT condition + + # Now that both conditions are met, we can add the product cart_1.add_to_cart(self.PROD_1, 1) def test_available_products_works_with_no_conditions_set(self): @@ -186,7 +200,7 @@ class FlagTestCases(RegistrationCartTestCase): self.assertTrue(self.PROD_4 in prods) def test_available_products_on_category_works_when_condition_not_met(self): - self.add_product_flag(mandatory=False) + self.add_product_flag(condition=rego.FlagBase.ENABLE_IF_TRUE) prods = ProductController.available_products( self.USER_1, @@ -197,7 +211,7 @@ class FlagTestCases(RegistrationCartTestCase): self.assertTrue(self.PROD_2 in prods) def test_available_products_on_category_works_when_condition_is_met(self): - self.add_product_flag(mandatory=False) + self.add_product_flag(condition=rego.FlagBase.ENABLE_IF_TRUE) cart_1 = TestingCartController.for_user(self.USER_1) cart_1.add_to_cart(self.PROD_2, 1) @@ -211,7 +225,7 @@ class FlagTestCases(RegistrationCartTestCase): self.assertTrue(self.PROD_2 in prods) def test_available_products_on_products_works_when_condition_not_met(self): - self.add_product_flag(mandatory=False) + self.add_product_flag(condition=rego.FlagBase.ENABLE_IF_TRUE) prods = ProductController.available_products( self.USER_1, @@ -222,7 +236,7 @@ class FlagTestCases(RegistrationCartTestCase): self.assertTrue(self.PROD_2 in prods) def test_available_products_on_products_works_when_condition_is_met(self): - self.add_product_flag(mandatory=False) + self.add_product_flag(condition=rego.FlagBase.ENABLE_IF_TRUE) cart_1 = TestingCartController.for_user(self.USER_1) cart_1.add_to_cart(self.PROD_2, 1) @@ -236,7 +250,7 @@ class FlagTestCases(RegistrationCartTestCase): self.assertTrue(self.PROD_2 in prods) def test_category_flag_fails_if_cart_refunded(self): - self.add_category_flag(mandatory=False) + self.add_category_flag(condition=rego.FlagBase.ENABLE_IF_TRUE) cart = TestingCartController.for_user(self.USER_1) cart.add_to_cart(self.PROD_3, 1) @@ -254,7 +268,7 @@ class FlagTestCases(RegistrationCartTestCase): cart_2.set_quantity(self.PROD_1, 1) def test_product_flag_fails_if_cart_refunded(self): - self.add_product_flag(mandatory=False) + self.add_product_flag(condition=rego.FlagBase.ENABLE_IF_TRUE) cart = TestingCartController.for_user(self.USER_1) cart.add_to_cart(self.PROD_2, 1) @@ -272,7 +286,7 @@ class FlagTestCases(RegistrationCartTestCase): cart_2.set_quantity(self.PROD_1, 1) def test_available_categories(self): - self.add_product_flag_on_category(mandatory=False) + self.add_product_flag_on_category(condition=rego.FlagBase.ENABLE_IF_TRUE) cart_1 = TestingCartController.for_user(self.USER_1) @@ -293,7 +307,7 @@ class FlagTestCases(RegistrationCartTestCase): self.assertTrue(self.CAT_2 in cats) def test_validate_cart_when_flags_become_unmet(self): - self.add_product_flag(mandatory=False) + self.add_product_flag(condition=rego.FlagBase.ENABLE_IF_TRUE) cart = TestingCartController.for_user(self.USER_1) cart.add_to_cart(self.PROD_2, 1) diff --git a/registrasion/tests/test_voucher.py b/registrasion/tests/test_voucher.py index 894c6635..5f1e07f0 100644 --- a/registrasion/tests/test_voucher.py +++ b/registrasion/tests/test_voucher.py @@ -61,7 +61,7 @@ class VoucherTestCases(RegistrationCartTestCase): flag = rego.VoucherFlag.objects.create( description="Voucher condition", voucher=voucher, - mandatory=False, + condition=rego.FlagBase.ENABLE_IF_TRUE, ) flag.save() flag.products.add(self.PROD_1) From c24b9ee213b9307d4435cc6c19d359d9e39dedac Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Tue, 12 Apr 2016 08:30:33 +1000 Subject: [PATCH 6/7] Makes EnablingConditionBase a minimal reification of an abstract base model FlagBase, replaces enablingconditionbase with flagbase where possible, and fixes method names and documentation --- registrasion/controllers/cart.py | 6 ++-- registrasion/controllers/conditions.py | 15 ++++---- registrasion/controllers/product.py | 6 ++-- .../migrations/0024_auto_20160411_2230.py | 25 +++++++++++++ registrasion/models.py | 36 +++++++++++-------- registrasion/tests/test_flag.py | 10 +++--- 6 files changed, 65 insertions(+), 33 deletions(-) create mode 100644 registrasion/migrations/0024_auto_20160411_2230.py diff --git a/registrasion/controllers/cart.py b/registrasion/controllers/cart.py index 2b711c70..70972be5 100644 --- a/registrasion/controllers/cart.py +++ b/registrasion/controllers/cart.py @@ -118,7 +118,7 @@ class CartController(object): 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. ''' + the limits and flag conditions imposed on the products. ''' errors = [] @@ -159,8 +159,8 @@ class CartController(object): ) )) - # Test the enabling conditions - errs = ConditionController.test_enabling_conditions( + # Test the flag conditions + errs = ConditionController.test_flags( self.cart.user, product_quantities=product_quantities, ) diff --git a/registrasion/controllers/conditions.py b/registrasion/controllers/conditions.py index ddae0de7..fde2f805 100644 --- a/registrasion/controllers/conditions.py +++ b/registrasion/controllers/conditions.py @@ -65,16 +65,16 @@ class ConditionController(object): } @classmethod - def test_enabling_conditions( + def test_flags( cls, user, products=None, product_quantities=None): - ''' Evaluates all of the enabling conditions on the given products. + ''' Evaluates all of the flag 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 + If all flag conditions pass, an empty list is returned, otherwise a list is returned containing all of the products that are *not enabled*. ''' @@ -90,14 +90,13 @@ class ConditionController(object): quantities = {} # Get the conditions covered by the products themselves - prods = ( - product.enablingconditionbase_set.select_subclasses() + product.flagbase_set.select_subclasses() for product in products ) # Get the conditions covered by their categories cats = ( - category.enablingconditionbase_set.select_subclasses() + category.flagbase_set.select_subclasses() for category in set(product.category for product in products) ) @@ -172,7 +171,7 @@ class ConditionController(object): return error_fields def user_quantity_remaining(self, user): - ''' Returns the number of items covered by this enabling condition the + ''' Returns the number of items covered by this flag condition the user can add to the current cart. This default implementation returns a big number if is_met() is true, otherwise 0. @@ -182,7 +181,7 @@ class ConditionController(object): return 99999999 if self.is_met(user) else 0 def is_met(self, user): - ''' Returns True if this enabling condition is met, otherwise returns + ''' Returns True if this flag condition is met, otherwise returns False. Either this method, or user_quantity_remaining() must be overridden diff --git a/registrasion/controllers/product.py b/registrasion/controllers/product.py index 4e9e6cab..a28f99cb 100644 --- a/registrasion/controllers/product.py +++ b/registrasion/controllers/product.py @@ -15,9 +15,9 @@ class ProductController(object): @classmethod def available_products(cls, user, category=None, products=None): ''' Returns a list of all of the products that are available per - enabling conditions from the given categories. + flag conditions from the given categories. TODO: refactor so that all conditions are tested here and - can_add_with_enabling_conditions calls this method. ''' + can_add_with_flags calls this method. ''' if category is None and products is None: raise ValueError("You must provide products or a category") @@ -45,7 +45,7 @@ class ProductController(object): if cls(product).user_quantity_remaining(user) > 0 ) - failed_and_messages = ConditionController.test_enabling_conditions( + failed_and_messages = ConditionController.test_flags( user, products=passed_limits ) failed_conditions = set(i[0] for i in failed_and_messages) diff --git a/registrasion/migrations/0024_auto_20160411_2230.py b/registrasion/migrations/0024_auto_20160411_2230.py new file mode 100644 index 00000000..e1baf8c4 --- /dev/null +++ b/registrasion/migrations/0024_auto_20160411_2230.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.2 on 2016-04-11 22:30 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('registrasion', '0023_auto_20160411_1001_squashed_0024_auto_20160411_1002'), + ] + + operations = [ + migrations.AlterField( + model_name='enablingconditionbase', + name='categories', + field=models.ManyToManyField(blank=True, help_text="Categories whose products are affected by this flag's condition.", related_name='flagbase_set', to='registrasion.Category'), + ), + migrations.AlterField( + model_name='enablingconditionbase', + name='products', + field=models.ManyToManyField(blank=True, help_text="Products affected by this flag's condition.", related_name='flagbase_set', to='registrasion.Product'), + ), + ] diff --git a/registrasion/models.py b/registrasion/models.py index 58ec1f96..734fd0f0 100644 --- a/registrasion/models.py +++ b/registrasion/models.py @@ -366,15 +366,8 @@ class RoleDiscount(object): pass -class FlagBase(object): - ''' This will replace EnablingConditionBase once it's ready. ''' - - DISABLE_IF_FALSE = 1 - ENABLE_IF_TRUE = 2 - - @python_2_unicode_compatible -class EnablingConditionBase(models.Model): +class FlagBase(models.Model): ''' This defines a condition which allows products or categories to be made visible, or be prevented from being visible. @@ -387,10 +380,14 @@ class EnablingConditionBase(models.Model): If both types of conditions exist on a product, both of these rules apply. ''' - # TODO: rename to FlagBase once - # https://code.djangoproject.com/ticket/26488 is solved. - objects = InheritanceManager() + class Meta: + # TODO: make concrete once https://code.djangoproject.com/ticket/26488 + # is solved. + abstract = True + + DISABLE_IF_FALSE = 1 + ENABLE_IF_TRUE = 2 def __str__(self): return self.description @@ -409,10 +406,10 @@ class EnablingConditionBase(models.Model): description = models.CharField(max_length=255) condition = models.IntegerField( - default=FlagBase.ENABLE_IF_TRUE, + default=ENABLE_IF_TRUE, choices=( - (FlagBase.DISABLE_IF_FALSE, _("Disable if false")), - (FlagBase.ENABLE_IF_TRUE, _("Enable if true")), + (DISABLE_IF_FALSE, _("Disable if false")), + (ENABLE_IF_TRUE, _("Enable if true")), ), help_text=_("If there is at least one 'disable if false' flag " "defined on a product or category, all such flag " @@ -426,6 +423,7 @@ class EnablingConditionBase(models.Model): Product, blank=True, help_text=_("Products affected by this flag's condition."), + related_name="flagbase_set", ) categories = models.ManyToManyField( Category, @@ -433,9 +431,19 @@ class EnablingConditionBase(models.Model): help_text=_("Categories whose products are affected by this flag's " "condition." ), + related_name="flagbase_set", ) +class EnablingConditionBase(FlagBase): + ''' Reifies the abstract FlagBase. This is necessary because django + prevents renaming base classes in migrations. ''' + # TODO: remove this, and make subclasses subclass FlagBase once + # https://code.djangoproject.com/ticket/26488 is solved. + + objects = InheritanceManager() + + class TimeOrStockLimitFlag(EnablingConditionBase): ''' Registration product ceilings ''' diff --git a/registrasion/tests/test_flag.py b/registrasion/tests/test_flag.py index b1ccdc3c..e1e1c166 100644 --- a/registrasion/tests/test_flag.py +++ b/registrasion/tests/test_flag.py @@ -16,7 +16,7 @@ class FlagTestCases(RegistrationCartTestCase): @classmethod def add_product_flag(cls, condition=rego.FlagBase.ENABLE_IF_TRUE): - ''' Adds a product enabling condition: adding PROD_1 to a cart is + ''' Adds a product flag condition: adding PROD_1 to a cart is predicated on adding PROD_2 beforehand. ''' flag = rego.ProductFlag.objects.create( description="Product condition", @@ -29,7 +29,7 @@ class FlagTestCases(RegistrationCartTestCase): @classmethod def add_product_flag_on_category(cls, condition=rego.FlagBase.ENABLE_IF_TRUE): - ''' Adds a product enabling condition that operates on a category: + ''' Adds a product flag condition that operates on a category: adding an item from CAT_1 is predicated on adding PROD_3 beforehand ''' flag = rego.ProductFlag.objects.create( description="Product condition", @@ -41,7 +41,7 @@ class FlagTestCases(RegistrationCartTestCase): flag.save() def add_category_flag(cls, condition=rego.FlagBase.ENABLE_IF_TRUE): - ''' Adds a category enabling condition: adding PROD_1 to a cart is + ''' Adds a category flag condition: adding PROD_1 to a cart is predicated on adding an item from CAT_2 beforehand.''' flag = rego.CategoryFlag.objects.create( description="Category condition", @@ -114,7 +114,7 @@ class FlagTestCases(RegistrationCartTestCase): self.add_product_flag() self.add_category_flag() - # User 1 is testing the product enabling condition + # User 1 is testing the product flag condition cart_1 = TestingCartController.for_user(self.USER_1) # Cannot add PROD_1 until a condition is met with self.assertRaises(ValidationError): @@ -122,7 +122,7 @@ class FlagTestCases(RegistrationCartTestCase): cart_1.add_to_cart(self.PROD_2, 1) cart_1.add_to_cart(self.PROD_1, 1) - # User 2 is testing the category enabling condition + # User 2 is testing the category flag condition cart_2 = TestingCartController.for_user(self.USER_2) # Cannot add PROD_1 until a condition is met with self.assertRaises(ValidationError): From d3f7431f7d55957cb7b900f1f203783cdfacfc4d Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Tue, 12 Apr 2016 08:47:17 +1000 Subject: [PATCH 7/7] Squashes migrations from rename_to_flags branch; marks as normal migration --- .../migrations/0021_auto_20160411_0748.py | 31 -------- ...1_0748_squashed_0024_auto_20160411_2230.py | 77 +++++++++++++++++++ .../migrations/0022_auto_20160411_0806.py | 31 -------- ...1_1001_squashed_0024_auto_20160411_1002.py | 37 --------- .../migrations/0024_auto_20160411_2230.py | 25 ------ 5 files changed, 77 insertions(+), 124 deletions(-) delete mode 100644 registrasion/migrations/0021_auto_20160411_0748.py create mode 100644 registrasion/migrations/0021_auto_20160411_0748_squashed_0024_auto_20160411_2230.py delete mode 100644 registrasion/migrations/0022_auto_20160411_0806.py delete mode 100644 registrasion/migrations/0023_auto_20160411_1001_squashed_0024_auto_20160411_1002.py delete mode 100644 registrasion/migrations/0024_auto_20160411_2230.py diff --git a/registrasion/migrations/0021_auto_20160411_0748.py b/registrasion/migrations/0021_auto_20160411_0748.py deleted file mode 100644 index 345cd4ad..00000000 --- a/registrasion/migrations/0021_auto_20160411_0748.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.2 on 2016-04-11 07:48 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('registrasion', '0020_auto_20160411_0258'), - ] - - operations = [ - migrations.RenameModel( - old_name='CategoryEnablingCondition', - new_name='CategoryFlag', - ), - migrations.RenameModel( - old_name='ProductEnablingCondition', - new_name='ProductFlag', - ), - migrations.RenameModel( - old_name='TimeOrStockLimitEnablingCondition', - new_name='TimeOrStockLimitFlag', - ), - migrations.RenameModel( - old_name='VoucherEnablingCondition', - new_name='VoucherFlag', - ), - ] diff --git a/registrasion/migrations/0021_auto_20160411_0748_squashed_0024_auto_20160411_2230.py b/registrasion/migrations/0021_auto_20160411_0748_squashed_0024_auto_20160411_2230.py new file mode 100644 index 00000000..53b82d10 --- /dev/null +++ b/registrasion/migrations/0021_auto_20160411_0748_squashed_0024_auto_20160411_2230.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.2 on 2016-04-11 22:46 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('registrasion', '0020_auto_20160411_0258'), + ] + + operations = [ + migrations.RenameModel( + old_name='CategoryEnablingCondition', + new_name='CategoryFlag', + ), + migrations.RenameModel( + old_name='ProductEnablingCondition', + new_name='ProductFlag', + ), + migrations.RenameModel( + old_name='TimeOrStockLimitEnablingCondition', + new_name='TimeOrStockLimitFlag', + ), + migrations.RenameModel( + old_name='VoucherEnablingCondition', + new_name='VoucherFlag', + ), + migrations.AlterModelOptions( + name='categoryflag', + options={'verbose_name': 'flag (dependency on product from category)', 'verbose_name_plural': 'flags (dependency on product from category)'}, + ), + migrations.AlterModelOptions( + name='productflag', + options={'verbose_name': 'flag (dependency on product)', 'verbose_name_plural': 'flags (dependency on product)'}, + ), + migrations.AlterModelOptions( + name='timeorstocklimitflag', + options={'verbose_name': 'flag (time/stock limit)', 'verbose_name_plural': 'flags (time/stock limit)'}, + ), + migrations.AlterModelOptions( + name='voucherflag', + options={'verbose_name': 'flag (dependency on voucher)', 'verbose_name_plural': 'flags (dependency on voucher)'}, + ), + migrations.AlterField( + model_name='enablingconditionbase', + name='categories', + field=models.ManyToManyField(blank=True, help_text="Categories whose products are affected by this flag's condition.", to=b'registrasion.Category'), + ), + migrations.RenameField( + model_name='enablingconditionbase', + old_name='mandatory', + new_name='condition', + ), + migrations.AlterField( + model_name='enablingconditionbase', + name='condition', + field=models.IntegerField(choices=[(1, 'Disable if false'), (2, 'Enable if true')], default=2, help_text="If there is at least one 'disable if false' flag defined on a product or category, all such flag conditions must be met. If there is at least one 'enable if true' flag, at least one such condition must be met. If both types of conditions exist on a product, both of these rules apply."), + ), + migrations.AlterField( + model_name='enablingconditionbase', + name='products', + field=models.ManyToManyField(blank=True, help_text="Products affected by this flag's condition.", to=b'registrasion.Product'), + ), + migrations.AlterField( + model_name='enablingconditionbase', + name='categories', + field=models.ManyToManyField(blank=True, help_text="Categories whose products are affected by this flag's condition.", related_name='flagbase_set', to=b'registrasion.Category'), + ), + migrations.AlterField( + model_name='enablingconditionbase', + name='products', + field=models.ManyToManyField(blank=True, help_text="Products affected by this flag's condition.", related_name='flagbase_set', to=b'registrasion.Product'), + ), + ] diff --git a/registrasion/migrations/0022_auto_20160411_0806.py b/registrasion/migrations/0022_auto_20160411_0806.py deleted file mode 100644 index 436937ce..00000000 --- a/registrasion/migrations/0022_auto_20160411_0806.py +++ /dev/null @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.2 on 2016-04-11 08:06 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('registrasion', '0021_auto_20160411_0748'), - ] - - operations = [ - migrations.AlterModelOptions( - name='categoryflag', - options={'verbose_name': 'flag (dependency on product from category)', 'verbose_name_plural': 'flags (dependency on product from category)'}, - ), - migrations.AlterModelOptions( - name='productflag', - options={'verbose_name': 'flag (dependency on product)', 'verbose_name_plural': 'flags (dependency on product)'}, - ), - migrations.AlterModelOptions( - name='timeorstocklimitflag', - options={'verbose_name': 'flag (time/stock limit)', 'verbose_name_plural': 'flags (time/stock limit)'}, - ), - migrations.AlterModelOptions( - name='voucherflag', - options={'verbose_name': 'flag (dependency on voucher)', 'verbose_name_plural': 'flags (dependency on voucher)'}, - ), - ] diff --git a/registrasion/migrations/0023_auto_20160411_1001_squashed_0024_auto_20160411_1002.py b/registrasion/migrations/0023_auto_20160411_1001_squashed_0024_auto_20160411_1002.py deleted file mode 100644 index e9a9f6bb..00000000 --- a/registrasion/migrations/0023_auto_20160411_1001_squashed_0024_auto_20160411_1002.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.2 on 2016-04-11 10:46 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - replaces = [('registrasion', '0023_auto_20160411_1001'), ('registrasion', '0024_auto_20160411_1002')] - - dependencies = [ - ('registrasion', '0022_auto_20160411_0806'), - ] - - operations = [ - migrations.AlterField( - model_name='enablingconditionbase', - name='categories', - field=models.ManyToManyField(blank=True, help_text="Categories whose products are affected by this flag's condition.", to=b'registrasion.Category'), - ), - migrations.RenameField( - model_name='enablingconditionbase', - old_name='mandatory', - new_name='condition', - ), - migrations.AlterField( - model_name='enablingconditionbase', - name='condition', - field=models.IntegerField(choices=[(1, 'Disable if false'), (2, 'Enable if true')], default=2, help_text="If there is at least one 'disable if false' flag defined on a product or category, all such flag conditions must be met. If there is at least one 'enable if true' flag, at least one such condition must be met. If both types of conditions exist on a product, both of these rules apply."), - ), - migrations.AlterField( - model_name='enablingconditionbase', - name='products', - field=models.ManyToManyField(blank=True, help_text="Products affected by this flag's condition.", to=b'registrasion.Product'), - ), - ] diff --git a/registrasion/migrations/0024_auto_20160411_2230.py b/registrasion/migrations/0024_auto_20160411_2230.py deleted file mode 100644 index e1baf8c4..00000000 --- a/registrasion/migrations/0024_auto_20160411_2230.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.2 on 2016-04-11 22:30 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('registrasion', '0023_auto_20160411_1001_squashed_0024_auto_20160411_1002'), - ] - - operations = [ - migrations.AlterField( - model_name='enablingconditionbase', - name='categories', - field=models.ManyToManyField(blank=True, help_text="Categories whose products are affected by this flag's condition.", related_name='flagbase_set', to='registrasion.Category'), - ), - migrations.AlterField( - model_name='enablingconditionbase', - name='products', - field=models.ManyToManyField(blank=True, help_text="Products affected by this flag's condition.", related_name='flagbase_set', to='registrasion.Product'), - ), - ]