diff --git a/pinaxcon/registrasion/management/__init__.py b/pinaxcon/registrasion/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pinaxcon/registrasion/management/commands/__init__.py b/pinaxcon/registrasion/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pinaxcon/registrasion/management/commands/populate_inventory.py b/pinaxcon/registrasion/management/commands/populate_inventory.py new file mode 100644 index 0000000..a0bffcc --- /dev/null +++ b/pinaxcon/registrasion/management/commands/populate_inventory.py @@ -0,0 +1,417 @@ +from collections import namedtuple +from datetime import datetime +from datetime import timedelta +from decimal import Decimal +from django.contrib.auth.models import Group +from django.core.exceptions import ObjectDoesNotExist +from django.core.management.base import BaseCommand, CommandError + +from registrasion.models import inventory as inv +from registrasion.models import conditions as cond +from symposion import proposals + +class Command(BaseCommand): + help = 'Populates the inventory with the NBPy2017 inventory model' + + def add_arguments(self, parser): + pass + + def handle(self, *args, **options): + + kinds = [] + for i in ("talk", ): + kinds.append(proposals.models.ProposalKind.objects.get(name=i)) + self.main_conference_proposals = kinds + + self.populate_groups() + self.populate_inventory() + self.populate_restrictions() + self.populate_discounts() + + def populate_groups(self): + self.group_team = self.find_or_make( + Group, + ("name", ), + name="Conference organisers", + ) + self.group_volunteers = self.find_or_make( + Group, + ("name", ), + name="Conference volunteers", + ) + self.group_unpublish = self.find_or_make( + Group, + ("name", ), + name="Can see unpublished products", + ) + + def populate_inventory(self): + # Categories + + self.ticket = self.find_or_make( + inv.Category, + ("name",), + name="Ticket", + description="Each type of ticket has different included products. " + "For details of what products are included, see our " + "ticket sales page.", + required = True, + render_type=inv.Category.RENDER_TYPE_RADIO, + limit_per_user=1, + order=1, + ) + self.t_shirt = self.find_or_make( + inv.Category, + ("name",), + name="T-Shirt", + description="Commemorative conference t-shirts, featuring secret " + "North Bay Python 2017 artwork. Details of sizing and " + "manufacturer are on our " + "t-shirts page", + required = False, + render_type=inv.Category.RENDER_TYPE_ITEM_QUANTITY, + order=40, + ) + self.extras = self.find_or_make( + inv.Category, + ("name",), + name="Extras", + description="Other items that can improve your conference " + "experience.", + required = False, + render_type=inv.Category.RENDER_TYPE_QUANTITY, + order=60, + ) + + # Tickets + + self.ticket_ind_sponsor = self.find_or_make( + inv.Product, + ("name", "category",), + category=self.ticket, + name="Individual Sponsor", + price=Decimal("500.00"), + reservation_duration=hours(24), + order=1, + ) + self.ticket_corporate = self.find_or_make( + inv.Product, + ("name", "category",), + category=self.ticket, + name="Corporate", + price=Decimal("200.00"), + reservation_duration=hours(24), + order=10, + ) + self.ticket_supporter = self.find_or_make( + inv.Product, + ("name", "category",), + category=self.ticket, + name="Individual Supporter", + price=Decimal("100.00"), + reservation_duration=hours(24), + order=20, + ) + self.ticket_unaffiliated = self.find_or_make( + inv.Product, + ("name", "category",), + category=self.ticket, + name="Unaffiliated Individual", + price=Decimal("50.00"), + reservation_duration=hours(24), + order=30, + ) + self.ticket_speaker = self.find_or_make( + inv.Product, + ("name", "category",), + category=self.ticket, + name="Speaker", + price=Decimal("00.00"), + reservation_duration=hours(24), + order=50, + ) + self.ticket_media = self.find_or_make( + inv.Product, + ("name", "category",), + category=self.ticket, + name="Media", + price=Decimal("00.00"), + reservation_duration=hours(24), + order=60, + ) + self.ticket_sponsor = self.find_or_make( + inv.Product, + ("name", "category",), + category=self.ticket, + name="Sponsor", + price=Decimal("00.00"), + reservation_duration=hours(24), + order=70, + ) + self.ticket_team = self.find_or_make( + inv.Product, + ("name", "category",), + category=self.ticket, + name="Conference Organiser", + price=Decimal("00.00"), + reservation_duration=hours(24), + order=80, + ) + self.ticket_volunteer = self.find_or_make( + inv.Product, + ("name", "category",), + category=self.ticket, + name="Conference Volunteer", + price=Decimal("00.00"), + reservation_duration=hours(24), + order=90, + ) + + # Shirts + ShirtGroup = namedtuple("ShirtGroup", ("prefix", "sizes")) + shirt_names = { + "mens": ShirtGroup( + "Men's/Straight Cut Size", + ("S", "M", "L", "XL", "2XL", "3XL", "5XL"), + ), + "womens_classic": ShirtGroup( + "Women's Classic Fit", + ("XS", "S", "M", "L", "XL", "2XL", "3XL"), + ), + "womens_semi": ShirtGroup( + "Women's Semi-Fitted", + ("S", "M", "L", "XL", "2XL", "3XL"), + ), + } + + self.shirts = {} + order = 0 + for name, group in shirt_names.items(): + self.shirts[name] = {} + prefix = group.prefix + for size in group.sizes: + product_name = "%s %s" % (prefix, size) + order += 10 + self.shirts[name][size] = self.find_or_make( + inv.Product, + ("name", "category",), + name=product_name, + category=self.t_shirt, + price=Decimal("30.00"), + reservation_duration=hours(1), + order=order, + ) + + def populate_restrictions(self): + + # Hide the products that will eventually need a voucher + hide_voucher_products = self.find_or_make( + cond.GroupMemberFlag, + ("description", ), + description="Can see hidden products", + condition=cond.FlagBase.ENABLE_IF_TRUE, + ) + hide_voucher_products.group.set([self.group_unpublish]) + hide_voucher_products.products.set([ + self.ticket_media, + self.ticket_sponsor, + ]) + + # Set limits. + public_ticket_cap = self.find_or_make( + cond.TimeOrStockLimitFlag, + ("description", ), + description="Public ticket cap", + condition=cond.FlagBase.DISABLE_IF_FALSE, + limit=350, + ) + public_ticket_cap.products.set([ + self.ticket_ind_sponsor, + self.ticket_corporate, + self.ticket_supporter, + self.ticket_unaffiliated, + ]) + + non_public_ticket_cap = self.find_or_make( + cond.TimeOrStockLimitFlag, + ("description", ), + description="Non-public ticket cap", + condition=cond.FlagBase.DISABLE_IF_FALSE, + limit=200, + ) + non_public_ticket_cap.products.set([ + self.ticket_speaker, + self.ticket_sponsor, + self.ticket_media, + self.ticket_team, + self.ticket_volunteer, + ]) + + # Volunteer tickets are for volunteers only + volunteers = self.find_or_make( + cond.GroupMemberFlag, + ("description", ), + description="Volunteer tickets", + condition=cond.FlagBase.ENABLE_IF_TRUE, + ) + volunteers.group.set([self.group_volunteers]) + volunteers.products.set([ + self.ticket_volunteer, + ]) + + # Team tickets are for team members only + team = self.find_or_make( + cond.GroupMemberFlag, + ("description", ), + description="Team tickets", + condition=cond.FlagBase.ENABLE_IF_TRUE, + ) + team.group.set([self.group_team]) + team.products.set([ + self.ticket_team, + ]) + + # Speaker tickets are for primary speakers only + speaker_tickets = self.find_or_make( + cond.SpeakerFlag, + ("description", ), + description="Speaker tickets", + condition=cond.FlagBase.ENABLE_IF_TRUE, + is_presenter=True, + is_copresenter=False, + ) + speaker_tickets.proposal_kind.set(self.main_conference_proposals) + speaker_tickets.products.set([self.ticket_speaker, ]) + + def populate_discounts(self): + + def add_early_birds(discount): + self.find_or_make( + cond.DiscountForProduct, + ("discount", "product"), + discount=discount, + product=self.ticket_ind_sponsor, + price=Decimal("50.00"), + quantity=1, # Per user + ) + self.find_or_make( + cond.DiscountForProduct, + ("discount", "product"), + discount=discount, + product=self.ticket_corporate, + price=Decimal("20.00"), + quantity=1, # Per user + ) + self.find_or_make( + cond.DiscountForProduct, + ("discount", "product"), + discount=discount, + product=self.ticket_supporter, + price=Decimal("20.00"), + quantity=1, # Per user + ) + self.find_or_make( + cond.DiscountForProduct, + ("discount", "product"), + discount=discount, + product=self.ticket_unaffiliated, + price=Decimal("25.00"), + quantity=1, # Per user + ) + + def free_category(parent_discount, category, quantity=1): + self.find_or_make( + cond.DiscountForCategory, + ("discount", "category",), + discount=parent_discount, + category=category, + percentage=Decimal("100.00"), + quantity=quantity, + ) + + # Early Bird Discount (general public) + early_bird = self.find_or_make( + cond.TimeOrStockLimitDiscount, + ("description", ), + description="Early Bird", + end_time=datetime(year=2017, month=10, day=20), + limit=100, # Across all users + ) + add_early_birds(early_bird) + + # Early bird rates for speakers + speaker_ticket_discounts = self.find_or_make( + cond.SpeakerDiscount, + ("description", ), + description="Speaker Ticket Discount", + is_presenter=True, + is_copresenter=True, + ) + speaker_ticket_discounts.proposal_kind.set( + self.main_conference_proposals, + ) + add_early_birds(speaker_ticket_discounts) + + # Professional-Like ticket inclusions + ticket_prolike_inclusions = self.find_or_make( + cond.IncludedProductDiscount, + ("description", ), + description="Complimentary for ticket holder (Supporter-level and above)", + ) + ticket_prolike_inclusions.enabling_products.set([ + self.ticket_ind_sponsor, + self.ticket_corporate, + self.ticket_supporter, + self.ticket_sponsor, + self.ticket_speaker, + ]) + free_category(ticket_prolike_inclusions, self.t_shirt) + + # Team & volunteer ticket inclusions + ticket_staff_inclusions = self.find_or_make( + cond.IncludedProductDiscount, + ("description", ), + description="Complimentary for ticket holder (staff/volunteer)", + ) + ticket_staff_inclusions.enabling_products.set([ + self.ticket_team, + self.ticket_volunteer, + ]) + + # Team & volunteer t-shirts, regardless of ticket type + staff_t_shirts = self.find_or_make( + cond.GroupMemberDiscount, + ("description", ), + description="T-shirts complimentary for staff and volunteers", + ) + staff_t_shirts.group.set([ + self.group_team, + self.group_volunteers, + ]) + free_category(staff_t_shirts, self.t_shirt, quantity=2) + + def find_or_make(self, model, search_keys, **k): + ''' Either makes or finds an object of type _model_, with the given + kwargs. + + Arguments: + search_keys ([str, ...]): A sequence of keys that are used to search + for an existing version in the database. The remaining arguments are + only used when creating a new object. + ''' + + try: + keys = dict((key, k[key]) for key in search_keys) + a = model.objects.get(**keys) + self.stdout.write("FOUND : " + str(keys)) + model.objects.filter(id=a.id).update(**k) + a.refresh_from_db() + return a + except ObjectDoesNotExist: + a = model.objects.create(**k) + self.stdout.write("CREATED: " + str(k)) + return a + + +def hours(n): + return timedelta(hours=n) diff --git a/pinaxcon/urls.py b/pinaxcon/urls.py index d2440d9..72bcce7 100644 --- a/pinaxcon/urls.py +++ b/pinaxcon/urls.py @@ -35,6 +35,7 @@ urlpatterns = [ url(r"^attend/business-case$", TemplateView.as_view(template_name="static_pages/attend/business-case.html"), name="attend/business-case"), url(r"^attend/travel$", TemplateView.as_view(template_name="static_pages/attend/travel.html"), name="attend/travel"), url(r"^attend/hotels$", TemplateView.as_view(template_name="static_pages/attend/hotels.html"), name="attend/hotels"), + url(r"^attend/tshirts$", TemplateView.as_view(template_name="static_pages/attend/tshirts.html"), name="attend/tshirts"), url(r"^code-of-conduct$", TemplateView.as_view(template_name="static_pages/code_of_conduct/code_of_conduct.html"), name="code-of-conduct"), url(r"^code-of-conduct/harassment-incidents$", TemplateView.as_view(template_name="static_pages/code_of_conduct/harassment_procedure_attendee.html"), name="code-of-conduct/harassment-incidents"),