commit
						b8cfb57269
					
				
					 5 changed files with 493 additions and 83 deletions
				
			
		|  | @ -12,6 +12,7 @@ class EffectsDisplayMixin(object): | |||
|     def effects(self, obj): | ||||
|         return list(obj.effects()) | ||||
| 
 | ||||
| 
 | ||||
| # Inventory admin | ||||
| 
 | ||||
| 
 | ||||
|  | @ -69,14 +70,11 @@ class TimeOrStockLimitDiscountAdmin(admin.ModelAdmin, EffectsDisplayMixin): | |||
| 
 | ||||
| 
 | ||||
| @admin.register(conditions.IncludedProductDiscount) | ||||
| class IncludedProductDiscountAdmin(admin.ModelAdmin): | ||||
| class IncludedProductDiscountAdmin(admin.ModelAdmin, EffectsDisplayMixin): | ||||
| 
 | ||||
|     def enablers(self, obj): | ||||
|         return list(obj.enabling_products.all()) | ||||
| 
 | ||||
|     def effects(self, obj): | ||||
|         return list(obj.effects()) | ||||
| 
 | ||||
|     list_display = ("description", "enablers", "effects") | ||||
| 
 | ||||
|     inlines = [ | ||||
|  | @ -84,6 +82,20 @@ class IncludedProductDiscountAdmin(admin.ModelAdmin): | |||
|         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 | ||||
| 
 | ||||
|  | @ -172,18 +184,13 @@ class CategoryFlagAdmin( | |||
|     ordering = ("enabling_category",) | ||||
| 
 | ||||
| 
 | ||||
| # Enabling conditions | ||||
| @admin.register(conditions.TimeOrStockLimitFlag) | ||||
| class TimeOrStockLimitFlagAdmin( | ||||
|         nested_admin.NestedAdmin, | ||||
|         EffectsDisplayMixin): | ||||
|     model = conditions.TimeOrStockLimitFlag | ||||
| @admin.register(conditions.SpeakerFlag) | ||||
| class SpeakerFlagAdmin(nested_admin.NestedAdmin, EffectsDisplayMixin): | ||||
| 
 | ||||
|     list_display = ( | ||||
|         "description", | ||||
|         "start_time", | ||||
|         "end_time", | ||||
|         "limit", | ||||
|         "effects", | ||||
|     ) | ||||
|     ordering = ("start_time", "end_time", "limit") | ||||
|     model = conditions.SpeakerFlag | ||||
|     fields = ("description", "is_presenter", "is_copresenter", "proposal_kind", | ||||
|               "products", "categories") | ||||
| 
 | ||||
|     list_display = ("description", "is_presenter", "is_copresenter", "effects") | ||||
| 
 | ||||
|     ordering = ("-is_presenter", "-is_copresenter") | ||||
|  |  | |||
|  | @ -25,6 +25,8 @@ class ConditionController(object): | |||
|             conditions.CategoryFlag: CategoryConditionController, | ||||
|             conditions.IncludedProductDiscount: ProductConditionController, | ||||
|             conditions.ProductFlag: ProductConditionController, | ||||
|             conditions.SpeakerFlag: SpeakerConditionController, | ||||
|             conditions.SpeakerDiscount: SpeakerConditionController, | ||||
|             conditions.TimeOrStockLimitDiscount: | ||||
|                 TimeOrStockLimitDiscountController, | ||||
|             conditions.TimeOrStockLimitFlag: | ||||
|  | @ -299,3 +301,25 @@ class VoucherConditionController(IsMetByFilter, ConditionController): | |||
|         a voucher that invokes that item's condition in one of their carts. ''' | ||||
| 
 | ||||
|         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 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 | ||||
| class DiscountBase(models.Model): | ||||
|  | @ -154,7 +248,7 @@ class DiscountForCategory(models.Model): | |||
|     quantity = models.PositiveIntegerField() | ||||
| 
 | ||||
| 
 | ||||
| class TimeOrStockLimitDiscount(DiscountBase): | ||||
| class TimeOrStockLimitDiscount(TimeOrStockLimitCondition, DiscountBase): | ||||
|     ''' Discounts that are generally available, but are limited by timespan or | ||||
|     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_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(DiscountBase): | ||||
| class VoucherDiscount(VoucherCondition, DiscountBase): | ||||
|     ''' 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 | ||||
|     creating a Voucher object. | ||||
|  | @ -210,15 +285,8 @@ class VoucherDiscount(DiscountBase): | |||
|         verbose_name = _("discount (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(DiscountBase): | ||||
| class IncludedProductDiscount(IncludedProductCondition, DiscountBase): | ||||
|     ''' Discounts that are enabled because another product has been purchased. | ||||
|     e.g. A conference ticket includes a free t-shirt. | ||||
| 
 | ||||
|  | @ -233,12 +301,28 @@ class IncludedProductDiscount(DiscountBase): | |||
|         verbose_name = _("discount (product inclusions)") | ||||
|         verbose_name_plural = _("discounts (product inclusions)") | ||||
| 
 | ||||
|     enabling_products = models.ManyToManyField( | ||||
|         inventory.Product, | ||||
|         verbose_name=_("Including product"), | ||||
|         help_text=_("If one of these products are purchased, the discounts " | ||||
|                     "below will be enabled."), | ||||
|     ) | ||||
| 
 | ||||
| class SpeakerDiscount(SpeakerCondition, DiscountBase): | ||||
|     ''' Discounts 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 = _("discount (speaker)") | ||||
|         verbose_name_plural = _("discounts (speaker)") | ||||
| 
 | ||||
| 
 | ||||
| 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 | ||||
|     specific date range, or when fewer than a limit of products have been | ||||
|     sold. | ||||
|  | @ -352,28 +436,9 @@ class TimeOrStockLimitFlag(FlagBase): | |||
|         verbose_name = _("flag (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 | ||||
| class ProductFlag(FlagBase): | ||||
| class ProductFlag(IncludedProductCondition, FlagBase): | ||||
|     ''' The condition is met because a specific product is purchased. | ||||
| 
 | ||||
|     Attributes: | ||||
|  | @ -389,12 +454,6 @@ class ProductFlag(FlagBase): | |||
|     def __str__(self): | ||||
|         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 | ||||
| class CategoryFlag(FlagBase): | ||||
|  | @ -422,7 +481,7 @@ class CategoryFlag(FlagBase): | |||
| 
 | ||||
| 
 | ||||
| @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. | ||||
|     enabling sponsor tickets. ''' | ||||
| 
 | ||||
|  | @ -434,7 +493,28 @@ class VoucherFlag(FlagBase): | |||
|     def __str__(self): | ||||
|         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 | ||||
|  |  | |||
							
								
								
									
										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