commit
b8cfb57269
5 changed files with 493 additions and 83 deletions
|
@ -12,6 +12,7 @@ class EffectsDisplayMixin(object):
|
||||||
def effects(self, obj):
|
def effects(self, obj):
|
||||||
return list(obj.effects())
|
return list(obj.effects())
|
||||||
|
|
||||||
|
|
||||||
# Inventory admin
|
# Inventory admin
|
||||||
|
|
||||||
|
|
||||||
|
@ -69,14 +70,11 @@ class TimeOrStockLimitDiscountAdmin(admin.ModelAdmin, EffectsDisplayMixin):
|
||||||
|
|
||||||
|
|
||||||
@admin.register(conditions.IncludedProductDiscount)
|
@admin.register(conditions.IncludedProductDiscount)
|
||||||
class IncludedProductDiscountAdmin(admin.ModelAdmin):
|
class IncludedProductDiscountAdmin(admin.ModelAdmin, EffectsDisplayMixin):
|
||||||
|
|
||||||
def enablers(self, obj):
|
def enablers(self, obj):
|
||||||
return list(obj.enabling_products.all())
|
return list(obj.enabling_products.all())
|
||||||
|
|
||||||
def effects(self, obj):
|
|
||||||
return list(obj.effects())
|
|
||||||
|
|
||||||
list_display = ("description", "enablers", "effects")
|
list_display = ("description", "enablers", "effects")
|
||||||
|
|
||||||
inlines = [
|
inlines = [
|
||||||
|
@ -84,6 +82,20 @@ class IncludedProductDiscountAdmin(admin.ModelAdmin):
|
||||||
DiscountForCategoryInline,
|
DiscountForCategoryInline,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@admin.register(conditions.SpeakerDiscount)
|
||||||
|
class SpeakerDiscountAdmin(admin.ModelAdmin, EffectsDisplayMixin):
|
||||||
|
|
||||||
|
fields = ("description", "is_presenter", "is_copresenter", "proposal_kind")
|
||||||
|
|
||||||
|
list_display = ("description", "is_presenter", "is_copresenter", "effects")
|
||||||
|
|
||||||
|
ordering = ("-is_presenter", "-is_copresenter")
|
||||||
|
|
||||||
|
inlines = [
|
||||||
|
DiscountForProductInline,
|
||||||
|
DiscountForCategoryInline,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
# Vouchers
|
# Vouchers
|
||||||
|
|
||||||
|
@ -172,18 +184,13 @@ class CategoryFlagAdmin(
|
||||||
ordering = ("enabling_category",)
|
ordering = ("enabling_category",)
|
||||||
|
|
||||||
|
|
||||||
# Enabling conditions
|
@admin.register(conditions.SpeakerFlag)
|
||||||
@admin.register(conditions.TimeOrStockLimitFlag)
|
class SpeakerFlagAdmin(nested_admin.NestedAdmin, EffectsDisplayMixin):
|
||||||
class TimeOrStockLimitFlagAdmin(
|
|
||||||
nested_admin.NestedAdmin,
|
|
||||||
EffectsDisplayMixin):
|
|
||||||
model = conditions.TimeOrStockLimitFlag
|
|
||||||
|
|
||||||
list_display = (
|
model = conditions.SpeakerFlag
|
||||||
"description",
|
fields = ("description", "is_presenter", "is_copresenter", "proposal_kind",
|
||||||
"start_time",
|
"products", "categories")
|
||||||
"end_time",
|
|
||||||
"limit",
|
list_display = ("description", "is_presenter", "is_copresenter", "effects")
|
||||||
"effects",
|
|
||||||
)
|
ordering = ("-is_presenter", "-is_copresenter")
|
||||||
ordering = ("start_time", "end_time", "limit")
|
|
||||||
|
|
|
@ -25,6 +25,8 @@ class ConditionController(object):
|
||||||
conditions.CategoryFlag: CategoryConditionController,
|
conditions.CategoryFlag: CategoryConditionController,
|
||||||
conditions.IncludedProductDiscount: ProductConditionController,
|
conditions.IncludedProductDiscount: ProductConditionController,
|
||||||
conditions.ProductFlag: ProductConditionController,
|
conditions.ProductFlag: ProductConditionController,
|
||||||
|
conditions.SpeakerFlag: SpeakerConditionController,
|
||||||
|
conditions.SpeakerDiscount: SpeakerConditionController,
|
||||||
conditions.TimeOrStockLimitDiscount:
|
conditions.TimeOrStockLimitDiscount:
|
||||||
TimeOrStockLimitDiscountController,
|
TimeOrStockLimitDiscountController,
|
||||||
conditions.TimeOrStockLimitFlag:
|
conditions.TimeOrStockLimitFlag:
|
||||||
|
@ -299,3 +301,25 @@ class VoucherConditionController(IsMetByFilter, ConditionController):
|
||||||
a voucher that invokes that item's condition in one of their carts. '''
|
a voucher that invokes that item's condition in one of their carts. '''
|
||||||
|
|
||||||
return queryset.filter(voucher__cart__user=user)
|
return queryset.filter(voucher__cart__user=user)
|
||||||
|
|
||||||
|
|
||||||
|
class SpeakerConditionController(IsMetByFilter, ConditionController):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def pre_filter(self, queryset, user):
|
||||||
|
''' Returns all of the items from queryset which are enabled by a user
|
||||||
|
being a presenter or copresenter of a proposal. '''
|
||||||
|
|
||||||
|
u = user
|
||||||
|
# User is a presenter
|
||||||
|
user_is_presenter = Q(
|
||||||
|
is_presenter=True,
|
||||||
|
proposal_kind__proposalbase__presentation__speaker__user=u,
|
||||||
|
)
|
||||||
|
# User is a copresenter
|
||||||
|
user_is_copresenter = Q(
|
||||||
|
is_copresenter=True,
|
||||||
|
proposal_kind__proposalbase__presentation__additional_speakers__user=u,
|
||||||
|
)
|
||||||
|
|
||||||
|
return queryset.filter(user_is_presenter | user_is_copresenter)
|
||||||
|
|
90
registrasion/migrations/0003_auto_20160904_0235.py
Normal file
90
registrasion/migrations/0003_auto_20160904_0235.py
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.9.2 on 2016-09-04 02:35
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('symposion_proposals', '0001_initial'),
|
||||||
|
('registrasion', '0002_auto_20160822_0034'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SpeakerDiscount',
|
||||||
|
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')),
|
||||||
|
('is_presenter', models.BooleanField(help_text='This condition is met if the user is the primary presenter of a presentation.')),
|
||||||
|
('is_copresenter', models.BooleanField(help_text='This condition is met if the user is a copresenter of a presentation.')),
|
||||||
|
('proposal_kind', models.ManyToManyField(help_text='The types of proposals that these users may be presenters of.', to='symposion_proposals.ProposalKind')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'discount (speaker)',
|
||||||
|
'verbose_name_plural': 'discounts (speaker)',
|
||||||
|
},
|
||||||
|
bases=('registrasion.discountbase', models.Model),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SpeakerFlag',
|
||||||
|
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')),
|
||||||
|
('is_presenter', models.BooleanField(help_text='This condition is met if the user is the primary presenter of a presentation.')),
|
||||||
|
('is_copresenter', models.BooleanField(help_text='This condition is met if the user is a copresenter of a presentation.')),
|
||||||
|
('proposal_kind', models.ManyToManyField(help_text='The types of proposals that these users may be presenters of.', to='symposion_proposals.ProposalKind')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'flag (speaker)',
|
||||||
|
'verbose_name_plural': 'flags (speaker)',
|
||||||
|
},
|
||||||
|
bases=('registrasion.flagbase', models.Model),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='includedproductdiscount',
|
||||||
|
name='enabling_products',
|
||||||
|
field=models.ManyToManyField(help_text='If one of these products are purchased, this condition is met.', to='registrasion.Product', verbose_name='Including product'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='productflag',
|
||||||
|
name='enabling_products',
|
||||||
|
field=models.ManyToManyField(help_text='If one of these products are purchased, this condition is met.', to='registrasion.Product', verbose_name='Including product'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='timeorstocklimitdiscount',
|
||||||
|
name='end_time',
|
||||||
|
field=models.DateTimeField(blank=True, help_text='When the condition should stop being true.', null=True, verbose_name='End time'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='timeorstocklimitdiscount',
|
||||||
|
name='limit',
|
||||||
|
field=models.PositiveIntegerField(blank=True, help_text='How many times this condition may be applied for all users.', null=True, verbose_name='Limit'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='timeorstocklimitdiscount',
|
||||||
|
name='start_time',
|
||||||
|
field=models.DateTimeField(blank=True, help_text='When the condition should start being true', null=True, verbose_name='Start time'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='timeorstocklimitflag',
|
||||||
|
name='end_time',
|
||||||
|
field=models.DateTimeField(blank=True, help_text='When the condition should stop being true.', null=True, verbose_name='End time'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='timeorstocklimitflag',
|
||||||
|
name='limit',
|
||||||
|
field=models.PositiveIntegerField(blank=True, help_text='How many times this condition may be applied for all users.', null=True, verbose_name='Limit'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='timeorstocklimitflag',
|
||||||
|
name='start_time',
|
||||||
|
field=models.DateTimeField(blank=True, help_text='When the condition should start being true', null=True, verbose_name='Start time'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='voucherflag',
|
||||||
|
name='voucher',
|
||||||
|
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='registrasion.Voucher', verbose_name='Voucher'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -8,8 +8,102 @@ from django.utils.encoding import python_2_unicode_compatible
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from model_utils.managers import InheritanceManager
|
from model_utils.managers import InheritanceManager
|
||||||
|
|
||||||
|
from symposion import proposals
|
||||||
|
|
||||||
# Product Modifiers
|
|
||||||
|
# Condition Types
|
||||||
|
|
||||||
|
class TimeOrStockLimitCondition(models.Model):
|
||||||
|
''' Attributes for a condition that is limited by timespan or a count of
|
||||||
|
purchased or reserved items.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
start_time (Optional[datetime]): When the condition should start being
|
||||||
|
true.
|
||||||
|
|
||||||
|
end_time (Optional[datetime]): When the condition should stop being
|
||||||
|
true.
|
||||||
|
|
||||||
|
limit (Optional[int]): How many items may fall under the condition
|
||||||
|
the condition until it stops being false -- for all users.
|
||||||
|
'''
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
start_time = models.DateTimeField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
verbose_name=_("Start time"),
|
||||||
|
help_text=_("When the condition should start being true"),
|
||||||
|
)
|
||||||
|
end_time = models.DateTimeField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
verbose_name=_("End time"),
|
||||||
|
help_text=_("When the condition should stop being true."),
|
||||||
|
)
|
||||||
|
limit = models.PositiveIntegerField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
verbose_name=_("Limit"),
|
||||||
|
help_text=_(
|
||||||
|
"How many times this condition may be applied for all users."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class VoucherCondition(models.Model):
|
||||||
|
''' A condition is met when a voucher code is in the current cart. '''
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
voucher = models.OneToOneField(
|
||||||
|
inventory.Voucher,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
verbose_name=_("Voucher"),
|
||||||
|
db_index=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class IncludedProductCondition(models.Model):
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
enabling_products = models.ManyToManyField(
|
||||||
|
inventory.Product,
|
||||||
|
verbose_name=_("Including product"),
|
||||||
|
help_text=_("If one of these products are purchased, this condition "
|
||||||
|
"is met."),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SpeakerCondition(models.Model):
|
||||||
|
''' Conditions that are met if a user is a presenter, or copresenter,
|
||||||
|
of a specific of presentation. '''
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
is_presenter = models.BooleanField(
|
||||||
|
blank=True,
|
||||||
|
help_text=_("This condition is met if the user is the primary "
|
||||||
|
"presenter of a presentation."),
|
||||||
|
)
|
||||||
|
is_copresenter = models.BooleanField(
|
||||||
|
blank=True,
|
||||||
|
help_text=_("This condition is met if the user is a copresenter of a "
|
||||||
|
"presentation."),
|
||||||
|
)
|
||||||
|
proposal_kind = models.ManyToManyField(
|
||||||
|
proposals.models.ProposalKind,
|
||||||
|
help_text=_("The types of proposals that these users may be "
|
||||||
|
"presenters of."),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Discounts
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class DiscountBase(models.Model):
|
class DiscountBase(models.Model):
|
||||||
|
@ -154,7 +248,7 @@ class DiscountForCategory(models.Model):
|
||||||
quantity = models.PositiveIntegerField()
|
quantity = models.PositiveIntegerField()
|
||||||
|
|
||||||
|
|
||||||
class TimeOrStockLimitDiscount(DiscountBase):
|
class TimeOrStockLimitDiscount(TimeOrStockLimitCondition, DiscountBase):
|
||||||
''' Discounts that are generally available, but are limited by timespan or
|
''' Discounts that are generally available, but are limited by timespan or
|
||||||
usage count. This is for e.g. Early Bird discounts.
|
usage count. This is for e.g. Early Bird discounts.
|
||||||
|
|
||||||
|
@ -175,27 +269,8 @@ class TimeOrStockLimitDiscount(DiscountBase):
|
||||||
verbose_name = _("discount (time/stock limit)")
|
verbose_name = _("discount (time/stock limit)")
|
||||||
verbose_name_plural = _("discounts (time/stock limit)")
|
verbose_name_plural = _("discounts (time/stock limit)")
|
||||||
|
|
||||||
start_time = models.DateTimeField(
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
verbose_name=_("Start time"),
|
|
||||||
help_text=_("This discount will only be available after this time."),
|
|
||||||
)
|
|
||||||
end_time = models.DateTimeField(
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
verbose_name=_("End time"),
|
|
||||||
help_text=_("This discount will only be available before this time."),
|
|
||||||
)
|
|
||||||
limit = models.PositiveIntegerField(
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
verbose_name=_("Limit"),
|
|
||||||
help_text=_("This discount may only be applied this many times."),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
class VoucherDiscount(VoucherCondition, DiscountBase):
|
||||||
class VoucherDiscount(DiscountBase):
|
|
||||||
''' Discounts that are enabled when a voucher code is in the current
|
''' Discounts that are enabled when a voucher code is in the current
|
||||||
cart. These are normally configured in the Admin page at the same time as
|
cart. These are normally configured in the Admin page at the same time as
|
||||||
creating a Voucher object.
|
creating a Voucher object.
|
||||||
|
@ -210,15 +285,8 @@ class VoucherDiscount(DiscountBase):
|
||||||
verbose_name = _("discount (enabled by voucher)")
|
verbose_name = _("discount (enabled by voucher)")
|
||||||
verbose_name_plural = _("discounts (enabled by voucher)")
|
verbose_name_plural = _("discounts (enabled by voucher)")
|
||||||
|
|
||||||
voucher = models.OneToOneField(
|
|
||||||
inventory.Voucher,
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
verbose_name=_("Voucher"),
|
|
||||||
db_index=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
class IncludedProductDiscount(IncludedProductCondition, DiscountBase):
|
||||||
class IncludedProductDiscount(DiscountBase):
|
|
||||||
''' Discounts that are enabled because another product has been purchased.
|
''' Discounts that are enabled because another product has been purchased.
|
||||||
e.g. A conference ticket includes a free t-shirt.
|
e.g. A conference ticket includes a free t-shirt.
|
||||||
|
|
||||||
|
@ -233,12 +301,28 @@ class IncludedProductDiscount(DiscountBase):
|
||||||
verbose_name = _("discount (product inclusions)")
|
verbose_name = _("discount (product inclusions)")
|
||||||
verbose_name_plural = _("discounts (product inclusions)")
|
verbose_name_plural = _("discounts (product inclusions)")
|
||||||
|
|
||||||
enabling_products = models.ManyToManyField(
|
|
||||||
inventory.Product,
|
class SpeakerDiscount(SpeakerCondition, DiscountBase):
|
||||||
verbose_name=_("Including product"),
|
''' Discounts that are enabled because the user is a presenter or
|
||||||
help_text=_("If one of these products are purchased, the discounts "
|
co-presenter of a kind of presentation.
|
||||||
"below will be enabled."),
|
|
||||||
)
|
Attributes:
|
||||||
|
is_presenter (bool): The condition should be met if the user is a
|
||||||
|
presenter of a presentation.
|
||||||
|
|
||||||
|
is_copresenter (bool): The condition should be met if the user is a
|
||||||
|
copresenter of a presentation.
|
||||||
|
|
||||||
|
proposal_kind ([symposion.proposals.models.ProposalKind, ...]): The
|
||||||
|
kinds of proposals that the user may be a presenter or
|
||||||
|
copresenter of for this condition to be met.
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
app_label = "registrasion"
|
||||||
|
verbose_name = _("discount (speaker)")
|
||||||
|
verbose_name_plural = _("discounts (speaker)")
|
||||||
|
|
||||||
|
|
||||||
class RoleDiscount(object):
|
class RoleDiscount(object):
|
||||||
|
@ -330,7 +414,7 @@ class FlagBase(models.Model):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TimeOrStockLimitFlag(FlagBase):
|
class TimeOrStockLimitFlag(TimeOrStockLimitCondition, FlagBase):
|
||||||
''' Product groupings that can be used to enable a product during a
|
''' Product groupings that can be used to enable a product during a
|
||||||
specific date range, or when fewer than a limit of products have been
|
specific date range, or when fewer than a limit of products have been
|
||||||
sold.
|
sold.
|
||||||
|
@ -352,28 +436,9 @@ class TimeOrStockLimitFlag(FlagBase):
|
||||||
verbose_name = _("flag (time/stock limit)")
|
verbose_name = _("flag (time/stock limit)")
|
||||||
verbose_name_plural = _("flags (time/stock limit)")
|
verbose_name_plural = _("flags (time/stock limit)")
|
||||||
|
|
||||||
start_time = models.DateTimeField(
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
help_text=_("Products included in this condition will only be "
|
|
||||||
"available after this time."),
|
|
||||||
)
|
|
||||||
end_time = models.DateTimeField(
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
help_text=_("Products included in this condition will only be "
|
|
||||||
"available before this time."),
|
|
||||||
)
|
|
||||||
limit = models.PositiveIntegerField(
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
help_text=_("The number of items under this grouping that can be "
|
|
||||||
"purchased."),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class ProductFlag(FlagBase):
|
class ProductFlag(IncludedProductCondition, FlagBase):
|
||||||
''' The condition is met because a specific product is purchased.
|
''' The condition is met because a specific product is purchased.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
|
@ -389,12 +454,6 @@ class ProductFlag(FlagBase):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Enabled by products: " + str(self.enabling_products.all())
|
return "Enabled by products: " + str(self.enabling_products.all())
|
||||||
|
|
||||||
enabling_products = models.ManyToManyField(
|
|
||||||
inventory.Product,
|
|
||||||
help_text=_("If one of these products are purchased, this condition "
|
|
||||||
"is met."),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class CategoryFlag(FlagBase):
|
class CategoryFlag(FlagBase):
|
||||||
|
@ -422,7 +481,7 @@ class CategoryFlag(FlagBase):
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class VoucherFlag(FlagBase):
|
class VoucherFlag(VoucherCondition, FlagBase):
|
||||||
''' The condition is met because a Voucher is present. This is for e.g.
|
''' The condition is met because a Voucher is present. This is for e.g.
|
||||||
enabling sponsor tickets. '''
|
enabling sponsor tickets. '''
|
||||||
|
|
||||||
|
@ -434,7 +493,28 @@ class VoucherFlag(FlagBase):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Enabled by voucher: %s" % self.voucher
|
return "Enabled by voucher: %s" % self.voucher
|
||||||
|
|
||||||
voucher = models.OneToOneField(inventory.Voucher)
|
|
||||||
|
class SpeakerFlag(SpeakerCondition, FlagBase):
|
||||||
|
''' Conditions that are enabled because the user is a presenter or
|
||||||
|
co-presenter of a kind of presentation.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
is_presenter (bool): The condition should be met if the user is a
|
||||||
|
presenter of a presentation.
|
||||||
|
|
||||||
|
is_copresenter (bool): The condition should be met if the user is a
|
||||||
|
copresenter of a presentation.
|
||||||
|
|
||||||
|
proposal_kind ([symposion.proposals.models.ProposalKind, ...]): The
|
||||||
|
kinds of proposals that the user may be a presenter or
|
||||||
|
copresenter of for this condition to be met.
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
app_label = "registrasion"
|
||||||
|
verbose_name = _("flag (speaker)")
|
||||||
|
verbose_name_plural = _("flags (speaker)")
|
||||||
|
|
||||||
|
|
||||||
# @python_2_unicode_compatible
|
# @python_2_unicode_compatible
|
||||||
|
|
209
registrasion/tests/test_speaker.py
Normal file
209
registrasion/tests/test_speaker.py
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
import pytz
|
||||||
|
|
||||||
|
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 symposion.conference import models as conference_models
|
||||||
|
from symposion.proposals import models as proposal_models
|
||||||
|
from symposion.speakers import models as speaker_models
|
||||||
|
from symposion.reviews.models import promote_proposal
|
||||||
|
|
||||||
|
from test_cart import RegistrationCartTestCase
|
||||||
|
|
||||||
|
UTC = pytz.timezone('UTC')
|
||||||
|
|
||||||
|
|
||||||
|
class SpeakerTestCase(RegistrationCartTestCase):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _create_proposals(cls):
|
||||||
|
''' Creates two proposals:
|
||||||
|
|
||||||
|
- User 1 will be presenter
|
||||||
|
- User 2 will be an additional presenter
|
||||||
|
|
||||||
|
Each proposal is of a different ProposalKind.
|
||||||
|
'''
|
||||||
|
|
||||||
|
conference = conference_models.Conference.objects.create(
|
||||||
|
title="TEST CONFERENCE.",
|
||||||
|
)
|
||||||
|
section = conference_models.Section.objects.create(
|
||||||
|
conference=conference,
|
||||||
|
name="TEST_SECTION",
|
||||||
|
slug="testsection",
|
||||||
|
)
|
||||||
|
proposal_section = proposal_models.ProposalSection.objects.create(
|
||||||
|
section=section,
|
||||||
|
closed=False,
|
||||||
|
published=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
kind_1 = proposal_models.ProposalKind.objects.create(
|
||||||
|
section=section,
|
||||||
|
name="Kind 1",
|
||||||
|
slug="kind1",
|
||||||
|
)
|
||||||
|
kind_2 = proposal_models.ProposalKind.objects.create(
|
||||||
|
section=section,
|
||||||
|
name="Kind 2",
|
||||||
|
slug="kind2",
|
||||||
|
)
|
||||||
|
|
||||||
|
speaker_1 = speaker_models.Speaker.objects.create(
|
||||||
|
user=cls.USER_1,
|
||||||
|
name="Speaker 1",
|
||||||
|
annotation="",
|
||||||
|
)
|
||||||
|
speaker_2 = speaker_models.Speaker.objects.create(
|
||||||
|
user=cls.USER_2,
|
||||||
|
name="Speaker 2",
|
||||||
|
annotation="",
|
||||||
|
)
|
||||||
|
|
||||||
|
proposal_1 = proposal_models.ProposalBase.objects.create(
|
||||||
|
kind=kind_1,
|
||||||
|
title="Proposal 1",
|
||||||
|
abstract="Abstract",
|
||||||
|
description="Description",
|
||||||
|
speaker=speaker_1,
|
||||||
|
)
|
||||||
|
proposal_models.AdditionalSpeaker.objects.create(
|
||||||
|
speaker=speaker_2,
|
||||||
|
proposalbase=proposal_1,
|
||||||
|
status=proposal_models.AdditionalSpeaker.SPEAKING_STATUS_ACCEPTED,
|
||||||
|
)
|
||||||
|
|
||||||
|
proposal_2 = proposal_models.ProposalBase.objects.create(
|
||||||
|
kind=kind_2,
|
||||||
|
title="Proposal 2",
|
||||||
|
abstract="Abstract",
|
||||||
|
description="Description",
|
||||||
|
speaker=speaker_1,
|
||||||
|
)
|
||||||
|
proposal_models.AdditionalSpeaker.objects.create(
|
||||||
|
speaker=speaker_2,
|
||||||
|
proposalbase=proposal_2,
|
||||||
|
status=proposal_models.AdditionalSpeaker.SPEAKING_STATUS_ACCEPTED,
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.KIND_1 = kind_1
|
||||||
|
cls.KIND_2 = kind_2
|
||||||
|
cls.PROPOSAL_1 = proposal_1
|
||||||
|
cls.PROPOSAL_2 = proposal_2
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _create_flag_for_primary_speaker(cls):
|
||||||
|
''' Adds flag -- PROD_1 is not available unless user is a primary
|
||||||
|
presenter of a KIND_1 '''
|
||||||
|
flag = conditions.SpeakerFlag.objects.create(
|
||||||
|
description="User must be presenter",
|
||||||
|
condition=conditions.FlagBase.ENABLE_IF_TRUE,
|
||||||
|
is_presenter=True,
|
||||||
|
is_copresenter=False,
|
||||||
|
)
|
||||||
|
flag.proposal_kind.add(cls.KIND_1)
|
||||||
|
flag.products.add(cls.PROD_1)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _create_flag_for_additional_speaker(cls):
|
||||||
|
''' Adds flag -- PROD_1 is not available unless user is a primary
|
||||||
|
presenter of a KIND_2 '''
|
||||||
|
flag = conditions.SpeakerFlag.objects.create(
|
||||||
|
description="User must be copresenter",
|
||||||
|
condition=conditions.FlagBase.ENABLE_IF_TRUE,
|
||||||
|
is_presenter=False,
|
||||||
|
is_copresenter=True,
|
||||||
|
)
|
||||||
|
flag.proposal_kind.add(cls.KIND_1)
|
||||||
|
flag.products.add(cls.PROD_1)
|
||||||
|
|
||||||
|
def test_create_proposals(self):
|
||||||
|
self._create_proposals()
|
||||||
|
|
||||||
|
self.assertIsNotNone(self.KIND_1)
|
||||||
|
self.assertIsNotNone(self.KIND_2)
|
||||||
|
self.assertIsNotNone(self.PROPOSAL_1)
|
||||||
|
self.assertIsNotNone(self.PROPOSAL_2)
|
||||||
|
|
||||||
|
def test_primary_speaker_enables_item(self):
|
||||||
|
self._create_proposals()
|
||||||
|
self._create_flag_for_primary_speaker()
|
||||||
|
|
||||||
|
# USER_1 cannot see PROD_1 until proposal is promoted.
|
||||||
|
available = ProductController.available_products(
|
||||||
|
self.USER_1,
|
||||||
|
products=[self.PROD_1],
|
||||||
|
)
|
||||||
|
self.assertNotIn(self.PROD_1, available)
|
||||||
|
|
||||||
|
# promote proposal_1 so that USER_1 becomes a speaker
|
||||||
|
promote_proposal(self.PROPOSAL_1)
|
||||||
|
|
||||||
|
# USER_1 can see PROD_1
|
||||||
|
available_1 = ProductController.available_products(
|
||||||
|
self.USER_1,
|
||||||
|
products=[self.PROD_1],
|
||||||
|
)
|
||||||
|
self.assertIn(self.PROD_1, available_1)
|
||||||
|
# USER_2 can *NOT* see PROD_1 because they're a copresenter
|
||||||
|
available_2 = ProductController.available_products(
|
||||||
|
self.USER_2,
|
||||||
|
products=[self.PROD_1],
|
||||||
|
)
|
||||||
|
self.assertNotIn(self.PROD_1, available_2)
|
||||||
|
|
||||||
|
def test_additional_speaker_enables_item(self):
|
||||||
|
self._create_proposals()
|
||||||
|
self._create_flag_for_additional_speaker()
|
||||||
|
|
||||||
|
# USER_2 cannot see PROD_1 until proposal is promoted.
|
||||||
|
available = ProductController.available_products(
|
||||||
|
self.USER_2,
|
||||||
|
products=[self.PROD_1],
|
||||||
|
)
|
||||||
|
self.assertNotIn(self.PROD_1, available)
|
||||||
|
|
||||||
|
# promote proposal_1 so that USER_2 becomes an additional speaker
|
||||||
|
promote_proposal(self.PROPOSAL_1)
|
||||||
|
|
||||||
|
# USER_2 can see PROD_1
|
||||||
|
available_2 = ProductController.available_products(
|
||||||
|
self.USER_2,
|
||||||
|
products=[self.PROD_1],
|
||||||
|
)
|
||||||
|
self.assertIn(self.PROD_1, available_2)
|
||||||
|
# USER_1 can *NOT* see PROD_1 because they're a presenter
|
||||||
|
available_1 = ProductController.available_products(
|
||||||
|
self.USER_1,
|
||||||
|
products=[self.PROD_1],
|
||||||
|
)
|
||||||
|
self.assertNotIn(self.PROD_1, available_1)
|
||||||
|
|
||||||
|
def test_speaker_on_different_proposal_kind_does_not_enable_item(self):
|
||||||
|
self._create_proposals()
|
||||||
|
self._create_flag_for_primary_speaker()
|
||||||
|
|
||||||
|
# USER_1 cannot see PROD_1 until proposal is promoted.
|
||||||
|
available = ProductController.available_products(
|
||||||
|
self.USER_1,
|
||||||
|
products=[self.PROD_1],
|
||||||
|
)
|
||||||
|
self.assertNotIn(self.PROD_1, available)
|
||||||
|
|
||||||
|
# promote proposal_2 so that USER_1 becomes a speaker, but of
|
||||||
|
# KIND_2, which is not covered by this condition
|
||||||
|
promote_proposal(self.PROPOSAL_2)
|
||||||
|
|
||||||
|
# USER_1 cannot see PROD_1
|
||||||
|
available_1 = ProductController.available_products(
|
||||||
|
self.USER_1,
|
||||||
|
products=[self.PROD_1],
|
||||||
|
)
|
||||||
|
self.assertNotIn(self.PROD_1, available_1)
|
Loading…
Reference in a new issue