From 136c68aa0a6e9b1fdd111248caa538f49eb9f612 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Mon, 5 Sep 2016 10:01:36 +1000 Subject: [PATCH 1/4] Adds GroupMemberCondition, derivatives, and controllers. --- registrasion/controllers/conditions.py | 14 ++++++- registrasion/models/conditions.py | 58 ++++++++++++++++++++------ 2 files changed, 59 insertions(+), 13 deletions(-) diff --git a/registrasion/controllers/conditions.py b/registrasion/controllers/conditions.py index ac198347..31413d27 100644 --- a/registrasion/controllers/conditions.py +++ b/registrasion/controllers/conditions.py @@ -23,6 +23,8 @@ class ConditionController(object): def _controllers(): return { conditions.CategoryFlag: CategoryConditionController, + conditions.GroupMemberDiscount: GroupMemberConditionController, + conditions.GroupMemberFlag: GroupMemberConditionController, conditions.IncludedProductDiscount: ProductConditionController, conditions.ProductFlag: ProductConditionController, conditions.SpeakerFlag: SpeakerConditionController, @@ -319,7 +321,17 @@ class SpeakerConditionController(IsMetByFilter, ConditionController): # User is a copresenter user_is_copresenter = Q( is_copresenter=True, - proposal_kind__proposalbase__presentation__additional_speakers__user=u, + proposal_kind__proposalbase__presentation__additional_speakers__user=u, # NOQA ) return queryset.filter(user_is_presenter | user_is_copresenter) + + +class GroupMemberConditionController(IsMetByFilter, ConditionController): + + @classmethod + def pre_filter(self, queryset, user): + ''' Returns all of the items from queryset which are enabled by a user + being member of a Django Auth Group. ''' + + return queryset diff --git a/registrasion/models/conditions.py b/registrasion/models/conditions.py index c04b453f..8fac369b 100644 --- a/registrasion/models/conditions.py +++ b/registrasion/models/conditions.py @@ -2,6 +2,7 @@ import itertools from . import inventory +from django.contrib.auth.models import Group from django.core.exceptions import ValidationError from django.db import models from django.utils.encoding import python_2_unicode_compatible @@ -81,7 +82,7 @@ class IncludedProductCondition(models.Model): class SpeakerCondition(models.Model): ''' Conditions that are met if a user is a presenter, or copresenter, - of a specific of presentation. ''' + of a specific kind of presentation. ''' class Meta: abstract = True @@ -103,6 +104,20 @@ class SpeakerCondition(models.Model): ) +class GroupMemberCondition(models.Model): + ''' Conditions that are met if a user is a member (not declined or + rejected) of a specific django auth group. ''' + + class Meta: + abstract = True + + group = models.ManyToManyField( + Group, + help_text=_("The groups a user needs to be a member of for this" + "condition to be met."), + ) + + # Discounts @python_2_unicode_compatible @@ -325,12 +340,23 @@ class SpeakerDiscount(SpeakerCondition, DiscountBase): verbose_name_plural = _("discounts (speaker)") -class RoleDiscount(object): - ''' Discounts that are enabled because the active user has a specific - role. This is for e.g. volunteers who can get a discount ticket. ''' - # TODO: implement RoleDiscount - pass +class GroupMemberDiscount(GroupMemberCondition, DiscountBase): + ''' Discounts that are enabled because the user is a member of a specific + django auth Group. + Attributes: + group ([Group, ...]): The condition should be met if the user is a + member of one of these groups. + + ''' + + class Meta: + app_label = "registrasion" + verbose_name = _("discount (group member)") + verbose_name_plural = _("discounts (group member)") + + +# Flags @python_2_unicode_compatible class FlagBase(models.Model): @@ -517,9 +543,17 @@ class SpeakerFlag(SpeakerCondition, FlagBase): verbose_name_plural = _("flags (speaker)") -# @python_2_unicode_compatible -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 RoleFlag - pass +class GroupMemberFlag(GroupMemberCondition, FlagBase): + ''' Flag whose conditions are metbecause the user is a member of a specific + django auth Group. + + Attributes: + group ([Group, ...]): The condition should be met if the user is a + member of one of these groups. + + ''' + + class Meta: + app_label = "registrasion" + verbose_name = _("flag (group member)") + verbose_name_plural = _("flags (group member)") From 1128e43150bba9753ebc3d81fa4370beffb2dc74 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Mon, 5 Sep 2016 10:18:08 +1000 Subject: [PATCH 2/4] =?UTF-8?q?Adds=20test=20for=20GroupMemberCondition=20?= =?UTF-8?q?=E2=80=94=20it=20fails,=20obviously.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- registrasion/tests/test_group_member.py | 65 +++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 registrasion/tests/test_group_member.py diff --git a/registrasion/tests/test_group_member.py b/registrasion/tests/test_group_member.py new file mode 100644 index 00000000..84b194aa --- /dev/null +++ b/registrasion/tests/test_group_member.py @@ -0,0 +1,65 @@ +import pytz + +from django.contrib.auth.models import Group +from django.core.exceptions import ValidationError + +from registrasion.models import commerce +from registrasion.models import conditions +from registrasion.controllers.category import CategoryController +from controller_helpers import TestingCartController +from controller_helpers import TestingInvoiceController +from registrasion.controllers.product import ProductController + +from test_cart import RegistrationCartTestCase + +UTC = pytz.timezone('UTC') + + +class GroupMemberTestCase(RegistrationCartTestCase): + + @classmethod + def _create_group_and_flag(cls): + ''' Creates cls.GROUP, and restricts cls.PROD_1 only to users who are + members of the group. ''' + + group = Group.objects.create( + name="TEST GROUP", + ) + + flag = conditions.GroupMemberFlag.objects.create( + description="Group member flag", + condition=conditions.FlagBase.ENABLE_IF_TRUE, + ) + flag.group.add(group) + flag.products.add(cls.PROD_1) + + cls.GROUP = group + + def test_product_not_enabled_until_user_joins_group(self): + ''' Tests that GroupMemberFlag disables a product for a user until + they are a member of a specific group. ''' + + self._create_group_and_flag() + + # USER_1 cannot see PROD_1 until they're in GROUP. + available = ProductController.available_products( + self.USER_1, + products=[self.PROD_1], + ) + self.assertNotIn(self.PROD_1, available) + + self.USER_1.groups.add(self.GROUP) + + # USER_1 cannot see PROD_1 until they're in GROUP. + available = ProductController.available_products( + self.USER_1, + products=[self.PROD_1], + ) + self.assertIn(self.PROD_1, available) + + # USER_2 is still locked out + available = ProductController.available_products( + self.USER_2, + products=[self.PROD_1], + ) + self.assertNotIn(self.PROD_1, available) From 0f488e7a127b82a1a847d8e00cc030fa61038510 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Mon, 5 Sep 2016 10:42:50 +1000 Subject: [PATCH 3/4] Makes TeamMemberCondition work --- registrasion/controllers/conditions.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/registrasion/controllers/conditions.py b/registrasion/controllers/conditions.py index 31413d27..e9efb625 100644 --- a/registrasion/controllers/conditions.py +++ b/registrasion/controllers/conditions.py @@ -330,8 +330,10 @@ class SpeakerConditionController(IsMetByFilter, ConditionController): class GroupMemberConditionController(IsMetByFilter, ConditionController): @classmethod - def pre_filter(self, queryset, user): - ''' Returns all of the items from queryset which are enabled by a user - being member of a Django Auth Group. ''' + def pre_filter(self, conditions, user): + ''' Returns all of the items from conditions which are enabled by a + user being member of a Django Auth Group. ''' - return queryset + return conditions.filter( + group=user.groups.all(), + ) From 1214b23077f9c58aa9e6bfe3397958cd5a2ddd79 Mon Sep 17 00:00:00 2001 From: Christopher Neugebauer Date: Mon, 5 Sep 2016 10:48:38 +1000 Subject: [PATCH 4/4] Adds admin and migration for GroupMember conditions --- registrasion/admin.py | 21 ++++++++++ ...004_groupmemberdiscount_groupmemberflag.py | 41 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 registrasion/migrations/0004_groupmemberdiscount_groupmemberflag.py diff --git a/registrasion/admin.py b/registrasion/admin.py index 4d704c9b..95e46b70 100644 --- a/registrasion/admin.py +++ b/registrasion/admin.py @@ -97,6 +97,19 @@ class SpeakerDiscountAdmin(admin.ModelAdmin, EffectsDisplayMixin): ] +@admin.register(conditions.GroupMemberDiscount) +class GroupMemberDiscountAdmin(admin.ModelAdmin, EffectsDisplayMixin): + + fields = ("description", "group") + + list_display = ("description", "effects") + + inlines = [ + DiscountForProductInline, + DiscountForCategoryInline, + ] + + # Vouchers class VoucherDiscountInline(nested_admin.NestedStackedInline): @@ -194,3 +207,11 @@ class SpeakerFlagAdmin(nested_admin.NestedAdmin, EffectsDisplayMixin): list_display = ("description", "is_presenter", "is_copresenter", "effects") ordering = ("-is_presenter", "-is_copresenter") + + +@admin.register(conditions.GroupMemberFlag) +class GroupMemberFlagAdmin(admin.ModelAdmin, EffectsDisplayMixin): + + fields = ("description", "group") + + list_display = ("description", "effects") diff --git a/registrasion/migrations/0004_groupmemberdiscount_groupmemberflag.py b/registrasion/migrations/0004_groupmemberdiscount_groupmemberflag.py new file mode 100644 index 00000000..0b980e57 --- /dev/null +++ b/registrasion/migrations/0004_groupmemberdiscount_groupmemberflag.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.2 on 2016-09-04 23:59 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0007_alter_validators_add_error_messages'), + ('registrasion', '0003_auto_20160904_0235'), + ] + + operations = [ + migrations.CreateModel( + name='GroupMemberDiscount', + fields=[ + ('discountbase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registrasion.DiscountBase')), + ('group', models.ManyToManyField(help_text='The groups a user needs to be a member of for thiscondition to be met.', to='auth.Group')), + ], + options={ + 'verbose_name': 'discount (group member)', + 'verbose_name_plural': 'discounts (group member)', + }, + bases=('registrasion.discountbase', models.Model), + ), + migrations.CreateModel( + name='GroupMemberFlag', + fields=[ + ('flagbase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registrasion.FlagBase')), + ('group', models.ManyToManyField(help_text='The groups a user needs to be a member of for thiscondition to be met.', to='auth.Group')), + ], + options={ + 'verbose_name': 'flag (group member)', + 'verbose_name_plural': 'flags (group member)', + }, + bases=('registrasion.flagbase', models.Model), + ), + ]