commit
27d0e1c6be
5 changed files with 188 additions and 13 deletions
|
@ -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
|
# Vouchers
|
||||||
|
|
||||||
class VoucherDiscountInline(nested_admin.NestedStackedInline):
|
class VoucherDiscountInline(nested_admin.NestedStackedInline):
|
||||||
|
@ -194,3 +207,11 @@ class SpeakerFlagAdmin(nested_admin.NestedAdmin, EffectsDisplayMixin):
|
||||||
list_display = ("description", "is_presenter", "is_copresenter", "effects")
|
list_display = ("description", "is_presenter", "is_copresenter", "effects")
|
||||||
|
|
||||||
ordering = ("-is_presenter", "-is_copresenter")
|
ordering = ("-is_presenter", "-is_copresenter")
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(conditions.GroupMemberFlag)
|
||||||
|
class GroupMemberFlagAdmin(admin.ModelAdmin, EffectsDisplayMixin):
|
||||||
|
|
||||||
|
fields = ("description", "group")
|
||||||
|
|
||||||
|
list_display = ("description", "effects")
|
||||||
|
|
|
@ -23,6 +23,8 @@ class ConditionController(object):
|
||||||
def _controllers():
|
def _controllers():
|
||||||
return {
|
return {
|
||||||
conditions.CategoryFlag: CategoryConditionController,
|
conditions.CategoryFlag: CategoryConditionController,
|
||||||
|
conditions.GroupMemberDiscount: GroupMemberConditionController,
|
||||||
|
conditions.GroupMemberFlag: GroupMemberConditionController,
|
||||||
conditions.IncludedProductDiscount: ProductConditionController,
|
conditions.IncludedProductDiscount: ProductConditionController,
|
||||||
conditions.ProductFlag: ProductConditionController,
|
conditions.ProductFlag: ProductConditionController,
|
||||||
conditions.SpeakerFlag: SpeakerConditionController,
|
conditions.SpeakerFlag: SpeakerConditionController,
|
||||||
|
@ -319,7 +321,19 @@ class SpeakerConditionController(IsMetByFilter, ConditionController):
|
||||||
# User is a copresenter
|
# User is a copresenter
|
||||||
user_is_copresenter = Q(
|
user_is_copresenter = Q(
|
||||||
is_copresenter=True,
|
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)
|
return queryset.filter(user_is_presenter | user_is_copresenter)
|
||||||
|
|
||||||
|
|
||||||
|
class GroupMemberConditionController(IsMetByFilter, ConditionController):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
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 conditions.filter(
|
||||||
|
group=user.groups.all(),
|
||||||
|
)
|
||||||
|
|
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -2,6 +2,7 @@ import itertools
|
||||||
|
|
||||||
from . import inventory
|
from . import inventory
|
||||||
|
|
||||||
|
from django.contrib.auth.models import Group
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
|
@ -81,7 +82,7 @@ class IncludedProductCondition(models.Model):
|
||||||
|
|
||||||
class SpeakerCondition(models.Model):
|
class SpeakerCondition(models.Model):
|
||||||
''' Conditions that are met if a user is a presenter, or copresenter,
|
''' 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:
|
class Meta:
|
||||||
abstract = True
|
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
|
# Discounts
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
|
@ -325,12 +340,23 @@ class SpeakerDiscount(SpeakerCondition, DiscountBase):
|
||||||
verbose_name_plural = _("discounts (speaker)")
|
verbose_name_plural = _("discounts (speaker)")
|
||||||
|
|
||||||
|
|
||||||
class RoleDiscount(object):
|
class GroupMemberDiscount(GroupMemberCondition, DiscountBase):
|
||||||
''' Discounts that are enabled because the active user has a specific
|
''' Discounts that are enabled because the user is a member of a specific
|
||||||
role. This is for e.g. volunteers who can get a discount ticket. '''
|
django auth Group.
|
||||||
# TODO: implement RoleDiscount
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
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
|
@python_2_unicode_compatible
|
||||||
class FlagBase(models.Model):
|
class FlagBase(models.Model):
|
||||||
|
@ -517,9 +543,17 @@ class SpeakerFlag(SpeakerCondition, FlagBase):
|
||||||
verbose_name_plural = _("flags (speaker)")
|
verbose_name_plural = _("flags (speaker)")
|
||||||
|
|
||||||
|
|
||||||
# @python_2_unicode_compatible
|
class GroupMemberFlag(GroupMemberCondition, FlagBase):
|
||||||
class RoleFlag(object):
|
''' Flag whose conditions are metbecause the user is a member of a specific
|
||||||
''' The condition is met because the active user has a particular Role.
|
django auth Group.
|
||||||
This is for e.g. enabling Team tickets. '''
|
|
||||||
# TODO: implement RoleFlag
|
Attributes:
|
||||||
pass
|
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)")
|
||||||
|
|
65
registrasion/tests/test_group_member.py
Normal file
65
registrasion/tests/test_group_member.py
Normal file
|
@ -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)
|
Loading…
Reference in a new issue