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…
	
	Add table
		
		Reference in a new issue
	
	 Christopher Neugebauer
						Christopher Neugebauer