Changeset - 539fa2dfdd2b
[Not reviewed]
make_dev_container.sh
Show inline comments
 
#!/bin/bash -x
 

	
 
IMAGE_NAME=${1:-symposion_app}
 

	
 
if [ -e ./symposion-tools ]; then
 
    pushd ./symposion-tools
 
    ./save_db_from_docker.sh
 
    popd
 
fi
 

	
 
docker image build -f docker/Dockerfile -t ${IMAGE_NAME} --target symposion_dev .
 
docker container stop symposion
 
docker container rm symposion
 
docker container create --env-file docker/laptop-mode-env -p 28000:8000 -v $(pwd):/app/symposion_app --name symposion ${IMAGE_NAME}
 
docker container start symposion
 
## When we started the container and mounted . into /app/symposion_app, it hides the static/build directory
 
## As a kludge, re-run collectstatic to recreate it
 
## Possible alternative here: don't mount all of ., just mount the bits that we'd live to have update live
 
docker exec symposion ./manage.py collectstatic --noinput -v 0
 
docker exec symposion ./manage.py migrate
 
docker exec symposion ./manage.py loaddata ./fixtures/{conference,sites,sitetree,flatpages}.json
 
docker exec symposion ./manage.py create_review_permissions
 
docker exec symposion ./manage.py loaddata ./fixtures/????/*.json
 
#docker exec symposion ./manage.py populate_inventory
 
docker exec symposion ./manage.py populate_inventory
 

	
 
if [ -e ./symposion-tools ]; then
 
    pushd ./symposion-tools
 
    ./fixture_to_docker.sh fixtures/dev_dummy_superuser.json
 
    ./fixture_to_docker.sh fixtures/????_*.json
 
    popd
 
else
 
    echo Now creating a Django superuser. Please enter a
 
    docker exec -it symposion ./manage.py createsuperuser --username admin1 --email root@example.com
 
fi
 

	
 
set +x
 
echo "Now you can log into http://localhost:28000/admin"
 
echo "Username: admin1      Password: the one you just typed twice"
 
echo "If you need to test as a non-admin user, create one at"
 
echo "http://localhost:28000/admin/auth/user/add/ - then log out"
 
echo "and log back in at http://localhost:28000"
 

	
pinaxcon/registrasion/management/commands/populate_inventory.py
Show inline comments
 
from collections import namedtuple
 
from datetime import timedelta
 
from decimal import Decimal
 
from django.conf import settings
 
from django.contrib.auth.models import Group
 
from django.core.exceptions import ObjectDoesNotExist
 
from django.core.management.base import BaseCommand
 

	
 
from registrasion.models import inventory as inv
 
from registrasion.models import conditions as cond
 
from symposion import proposals
 

	
 

	
 
class Command(BaseCommand):
 
    help = 'Populates the tickets and product inventory models'
 
    count = 0
 

	
 
    def add_arguments(self, parser):
 
        pass
 

	
 
    def handle(self, *args, **options):
 

	
 
        kinds = []
 
        for i in ("Talk", "Tutorial", "Miniconf"):
 
            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",
 
        )
 
        self.group_prepurchase = self.find_or_make(
 
            Group,
 
            ("name", ),
 
            name="Pre-purchase",
 
        )
 

	
 
    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 "
 
                        "<a href='https://linux.conf.au/attend/tickets/'>registration page</a>",
 
                        "<a href='https://linux.conf.au/attend/tickets/'>registration page</a>.",
 
            required=True,
 
            render_type=inv.Category.RENDER_TYPE_RADIO,
 
            limit_per_user=1,
 
            order=1,
 
        )
 
        self.terms = self.find_or_make(
 
            inv.Category,
 
            ("name",),
 
            name="Terms, Conditions, and Code of Conduct Acceptance",
 
            description="I agree to the "
 
                        "<a href=\"https://linux.conf.au/attend/terms-and-conditions\"> "
 
                        "terms and conditions of attendance</a>, and I have read, "
 
                        "understood, and agree to act according to the standards set "
 
                        "forth in our <a href=\"https://linux.conf.au/attend/code-of-conduct\">"
 
                        "Code of Conduct</a>.",
 
            required=True,
 
            render_type=inv.Category.RENDER_TYPE_CHECKBOX,
 
            order=10,
 
        )
 
        self.penguin_dinner = self.find_or_make(
 
            inv.Category,
 
            ("name",),
 
            name="Penguin Dinner Ticket",
 
            description="Tickets to our conference dinner on the evening of "
 
                        f"{settings.PENGUIN_DINNER_TICKET_DATE: %A %d %B}. "
 
                        "All attendees may purchase "
 
                        "seats at the dinner, even if a dinner ticket is not "
 
                        "included in your conference ticket price.",
 
            required=False,
 
            render_type=inv.Category.RENDER_TYPE_QUANTITY,
 
            limit_per_user=10,
 
            order=20,
 
        )
 
        self.speakers_dinner_ticket = self.find_or_make(
 
            inv.Category,
 
            ("name",),
 
            name="Speakers' Dinner Ticket",
 
            description="Tickets to our exclusive Speakers' Dinner on the "
 
                        "evening of "
 
                        f"{settings.SPEAKER_DINNER_TICKET_DATE: %A %d %B}. "
 
                        "You may purchase up "
 
                        "to 5 tickets in total, for significant others and "
 
                        "family members.",
 
                        f"{settings.SPEAKER_DINNER_TICKET_DATE: %A %d %B}.",
 
            required=False,
 
            render_type=inv.Category.RENDER_TYPE_QUANTITY,
 
            limit_per_user=5,
 
            limit_per_user=1,
 
            order=30,
 
        )
 
        self.pdns_category = self.find_or_make(
 
            inv.Category,
 
            ("name",),
 
            name="Professional Delegates Networking Session Ticket",
 
            description="Tickets to our Professional Delegates Networking session. "
 
                        "This event will be held on the evening of "
 
                        f"{settings.PDNS_TICKET_DATE: %A %d %B} "
 
                        "and is restricted to Professional Ticket "
 
                        "holders, speakers, miniconf organisers, and invited "
 
                        "guests.",
 
            required=False,
 
            render_type=inv.Category.RENDER_TYPE_RADIO,
 
            limit_per_user=1,
 
            order=40,
 
        )
 
        self.t_shirt = self.find_or_make(
 
            inv.Category,
 
            ("name",),
 
            name="Shirt",
 
            description="Commemorative conference polo shirts, featuring the "
 
                        f"linux.conf.au {settings.LCA_START.year} artwork.",
 
            description="Commemorative conference shirts, featuring the "
 
                        f"linux.conf.au {settings.LCA_START.year} artwork. "
 
                        "View the <a href=\"https://linux.conf.au/attend/shirts\">"
 
                        "sizing guide</a>.",
 
            required=False,
 
            render_type=inv.Category.RENDER_TYPE_ITEM_QUANTITY,
 
            order=50,
 
        )
 
        # self.accommodation = self.find_or_make(
 
        #     inv.Category,
 
        #     ("name",),
 
        #     name="Accommodation at University of Tasmania",
 
        #     description="Accommodation at the University of Tasmania colleges "
 
        #                 "and apartments. You can come back and book your "
 
        #                 "accommodation at a later date, provided rooms remain "
 
        #                 "available. Rooms may only be booked from Sunday 15 "
 
        #                 "January--Saturday 21 January. If you wish to stay "
 
        #                 "for only a part of the 6-day period, you must book "
 
        #                 "accommodation for the full 6-day period. Rooms at "
 
        #                 "other hotels, including Wrest Point can be booked "
 
        #                 "elsewhere. For full details, see [LINK]our "
 
        #                 "accommodation page.[/LINK]",
 
        #     required=False,
 
        #     render_type=inv.Category.RENDER_TYPE_RADIO,
 
        #     limit_per_user=1,
 
        #     order=50,
 
        # )
 
        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_contributor = self.find_or_make(
 
            inv.Product,
 
            ("name", "category",),
 
            category=self.ticket,
 
            name=settings.CONTRIBUTOR.name,
 
            price=settings.CONTRIBUTOR.regular_price,
 
            reservation_duration=hours(24),
 
            order=1,
 
        )
 
        self.ticket_professional = self.find_or_make(
 
            inv.Product,
 
            ("name", "category",),
 
            category=self.ticket,
 
            name=settings.PROFESSIONAL.name,
 
            price=settings.PROFESSIONAL.regular_price,
 
            reservation_duration=hours(24),
 
            order=10,
 
        )
 
        self.ticket_hobbyist = self.find_or_make(
 
            inv.Product,
 
            ("name", "category",),
 
            category=self.ticket,
 
            name=settings.HOBBYIST.name,
 
            price=settings.HOBBYIST.regular_price,
 
            reservation_duration=hours(24),
 
            order=20,
 
        )
 
        self.ticket_student = self.find_or_make(
 
            inv.Product,
 
            ("name", "category",),
 
            category=self.ticket,
 
            name=settings.STUDENT.name,
 
            price=settings.STUDENT.regular_price,
 
            reservation_duration=hours(24),
 
            order=30,
 
        )
 
        self.ticket_miniconfs_mt = self.find_or_make(
 
            inv.Product,
 
            ("name", "category",),
 
            category=self.ticket,
 
            name=settings.MINICONF_MT.name,
 
            price=settings.MINICONF_MT.regular_price,
 
            reservation_duration=hours(24),
 
            order=40,
 
        )
 
        self.ticket_miniconfs_mon = self.find_or_make(
 
            inv.Product,
 
            ("name", "category",),
 
            category=self.ticket,
 
            name=settings.MINICONF_M.name,
 
            price=settings.MINICONF_M.regular_price,
 
            reservation_duration=hours(24),
 
            order=42,
 
        )
 
        self.ticket_miniconfs_tue = self.find_or_make(
 
            inv.Product,
 
            ("name", "category",),
 
            category=self.ticket,
 
            name=settings.MINICONF_MT.name,
 
            price=settings.MINICONF_MT.regular_price,
 
            reservation_duration=hours(24),
 
            order=44,
 
        )
 
        self.ticket_speaker = self.find_or_make(
 
            inv.Product,
 
            ("name", "category",),
 
            category=self.ticket,
 
            name=settings.SPEAKER.name,
 
            price=settings.SPEAKER.regular_price,
 
            reservation_duration=hours(24),
 
            order=50,
 
        )
 
        self.ticket_media = self.find_or_make(
 
            inv.Product,
 
            ("name", "category",),
 
            category=self.ticket,
 
            name=settings.MEDIA.name,
 
            price=settings.MEDIA.regular_price,
 
            reservation_duration=hours(24),
 
            order=60,
 
        )
 
        self.ticket_sponsor = self.find_or_make(
 
            inv.Product,
 
            ("name", "category",),
 
            category=self.ticket,
 
            name=settings.SPONSOR.name,
 
            price=settings.SPONSOR.regular_price,
 
            reservation_duration=hours(24),
 
            order=70,
 
        )
 
        self.ticket_team = self.find_or_make(
 
            inv.Product,
 
            ("name", "category",),
 
            category=self.ticket,
 
            name=settings.CONFERENCE_ORG.name,
 
            price=settings.CONFERENCE_ORG.regular_price,
 
            reservation_duration=hours(24),
 
            order=80,
 
        )
 
        self.ticket_volunteer = self.find_or_make(
 
            inv.Product,
 
            ("name", "category",),
 
            category=self.ticket,
 
            name=settings.CONFERENCE_VOL.name,
 
            price=settings.CONFERENCE_VOL.regular_price,
 
            reservation_duration=hours(24),
 
            order=90,
 
        )
 

	
 
        # Agreements
 
        self.accept_terms = self.find_or_make(
 
            inv.Product,
 
            ("name", "category",),
 
            category=self.terms,
 
            name="I Accept",
 
            price=Decimal("00.00"),
 
            reservation_duration=hours(24),
 
            order=10,
 
            limit_per_user=1,
 
        )
 

	
 
        for t in settings.PENGUIN_DINNER.tickets:
 
            self.find_or_make(
 
                inv.Product,
 
                ("name", "category",),
 
                category=self.penguin_dinner,
 
                name=t.name,
 
                description=t.description,
 
                price=t.price,
 
                reservation_duration=t.reservation,
 
                order=t.order()
 
            )
 

	
 
        for t in settings.SPEAKERS_DINNER.tickets:
 
            self.find_or_make(
 
                inv.Product,
 
                ("name", "category",),
 
                category=self.speakers_dinner_ticket,
 
                name=t.name,
 
                description=t.description,
 
                price=t.price,
 
                reservation_duration=t.reservation,
 
                order=t.order()
 
            )
 

	
 
        # PDNS
 

	
 
        self.pdns = self.find_or_make(
 
            inv.Product,
 
            ("name", "category",),
 
            category=self.pdns_category,
 
            name="Conference Attendee",
 
            price=Decimal("00.00"),
 
            reservation_duration=hours(1),
 
            limit_per_user=1,
 
            order=10,
 
        )
 

	
 
        # # Accommodation
 

	
 
        # self.accommodation_week = self.find_or_make(
 
        #     inv.Product,
 
        #     ("name", "category",),
 
        #     category=self.accommodation,
 
        #     name="Single Bedroom with Shared Bathrooms, includes full "
 
        #          "breakfast, Sunday 15 January 2017--Saturday 21 January 2017",
 
        #     price=Decimal("396.00"),
 
        #     reservation_duration=hours(24),
 
        #     limit_per_user=1,
 
        #     order=10,
 
        # )
 

	
 
        # Extras
 

	
 
        self.carbon_offset = self.find_or_make(
 
            inv.Product,
 
            ("name", "category",),
 
            category=self.extras,
 
            name="Offset the carbon pollution generated by your attendance, "
 
                 "thanks to fifteen trees.",
 
            price=Decimal("5.00"),
 
            reservation_duration=hours(1),
 
            order=10,
 
        )
 

	
 
        # Shirts
 
        ShirtGroup = namedtuple("ShirtGroup", ("prefix", "sizes"))
 
        shirt_names = {
 
            "mens": ShirtGroup(
 
                "Men's/Straight Cut",
 
                ("S", "M", "L", "XL", "2XL", "3XL", "4XL"),
 
            "straight": ShirtGroup(
 
                "Straight Cut",
 
                ("S", "M", "L", "XL", "2XL", "3XL", "4XL", "5XL"),
 
            ),
 
            "semi_fitted": ShirtGroup(
 
                "Semi-Fitted",
 
                ("XS", "S", "M", "L", "XL", "2XL"),
 
            ),
 
            "womens": ShirtGroup(
 
                "Women's Classic Fit",
 
                ("8", "10", "12", "14", "16", "18"),
 
            "fitted": ShirtGroup(
 
                "Fitted",
 
                ("XS", "S", "M", "L", "XL", "2XL"),
 
            ),
 
        }
 

	
 
        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=settings.TSHIRT_PRICE,
 
                    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,
 
            self.ticket_miniconfs_mt,
 
            self.ticket_miniconfs_mon,
 
            self.ticket_miniconfs_tue,
 
        ])
 

	
 
        hide_all_tickets = self.find_or_make(
 
            cond.GroupMemberFlag,
 
            ("description", ),
 
            description="Can pre-purchase tickets",
 
            condition=cond.FlagBase.ENABLE_IF_TRUE,
 
        )
 
        hide_all_tickets.group.set([self.group_prepurchase])
 
        hide_all_tickets.products.set([
 
            self.ticket_contributor,
 
            self.ticket_professional,
 
            self.ticket_hobbyist,
 
            self.ticket_student,
 
        ])
 

	
 
        # Set limits.
 
        public_ticket_cap = self.find_or_make(
 
            cond.TimeOrStockLimitFlag,
 
            ("description", ),
 
            description="Public ticket cap",
 
            condition=cond.FlagBase.DISABLE_IF_FALSE,
 
            limit=600,
 
        )
 
        public_ticket_cap.products.set([
 
            self.ticket_contributor,
 
            self.ticket_professional,
 
            self.ticket_hobbyist,
 
            self.ticket_student,
 
        ])
 

	
 
        student_ticket_cap = self.find_or_make(
 
            cond.TimeOrStockLimitFlag,
 
            ("description", ),
 
            description="Student ticket cap",
 
            condition=cond.FlagBase.DISABLE_IF_FALSE,
 
            limit=100,
 
        )
 

	
 
        student_ticket_cap.products.set([
 
            self.ticket_student,
 
        ])
 

	
 
        public_ticket_cap.products.set([
 
            self.ticket_contributor,
 
            self.ticket_professional,
 
            self.ticket_hobbyist,
 
            self.ticket_student,
 
        ])
 

	
 
        sponsor_ticket_cap = self.find_or_make(
 
            cond.TimeOrStockLimitFlag,
 
            ("description", ),
 
            description="Reserved for sponsors",
 
            condition=cond.FlagBase.DISABLE_IF_FALSE,
 
            limit=70,
 
        )
 
        sponsor_ticket_cap.products.set([
 
            self.ticket_sponsor,
 
        ])
 

	
 
        volunteer_ticket_cap = self.find_or_make(
 
            cond.TimeOrStockLimitFlag,
 
            ("description", ),
 
            description="Reserrved for volunteers and organizers",
 
            description="Reserved for volunteers and organisers",
 
            condition=cond.FlagBase.DISABLE_IF_FALSE,
 
            limit=62,
 
        )
 
        volunteer_ticket_cap.products.set([
 
            self.ticket_team,
 
            self.ticket_volunteer,
 
        ])
 

	
 
        media_ticket_cap = self.find_or_make(
 
            cond.TimeOrStockLimitFlag,
 
            ("description", ),
 
            description="Reserved for media",
 
            condition=cond.FlagBase.DISABLE_IF_FALSE,
 
            limit=10,
 
        )
 
        media_ticket_cap.products.set([
 
            self.ticket_media,
 
        ])
 

	
 
        speaker_ticket_cap = self.find_or_make(
 
            cond.TimeOrStockLimitFlag,
 
            ("description", ),
 
            description="Reserved for speakers (and miniconf organisers)",
 
            condition=cond.FlagBase.DISABLE_IF_FALSE,
 
            limit=104,
 
        )
 
        speaker_ticket_cap.products.set([
 
            self.ticket_speaker,
 
        ])
 

	
 
        penguin_dinner_cap = self.find_or_make(
 
            cond.TimeOrStockLimitFlag,
 
            ("description", ),
 
            description="Penguin dinner ticket cap",
 
            condition=cond.FlagBase.DISABLE_IF_FALSE,
 
            limit=900,
 
        )
 
        penguin_dinner_cap.categories.set([
 
            self.penguin_dinner,
 
        ])
 

	
 
        speakers_dinner_cap = self.find_or_make(
 
            cond.TimeOrStockLimitFlag,
 
            ("description", ),
 
            description="Speakers dinner ticket cap",
 
            condition=cond.FlagBase.DISABLE_IF_FALSE,
 
            limit=135,
 
        )
 
        speakers_dinner_cap.categories.set([
 
            self.speakers_dinner_ticket,
 
        ])
 

	
 
        pdns_cap = self.find_or_make(
 
            cond.TimeOrStockLimitFlag,
 
            ("description", ),
 
            description="PDNS ticket cap",
 
            condition=cond.FlagBase.DISABLE_IF_FALSE,
 
            limit=400,
 
        )
 
        pdns_cap.categories.set([
 
            self.pdns_category,
 
        ])
 

	
 
        # 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, ])
 

	
 
        # Speaker dinner tickets are for primary and secondary speakers
 
        # Speaker dinner tickets are for primary speakers only
 
        speaker_dinner_tickets = self.find_or_make(
 
            cond.SpeakerFlag,
 
            ("description", ),
 
            description="Speaker dinner tickets",
 
            condition=cond.FlagBase.ENABLE_IF_TRUE,
 
            is_presenter=True,
 
            is_copresenter=True,
 
            is_copresenter=False,
 
        )
 
        speaker_dinner_tickets.proposal_kind.set(self.main_conference_proposals)
 
        speaker_dinner_tickets.categories.set([self.speakers_dinner_ticket, ])
 

	
 
        # PDNS tickets are complicated.
 
        # They can be enabled by tickets
 
        pdns_by_ticket = self.find_or_make(
 
            cond.ProductFlag,
 
            ("description", ),
 
            description="PDNS available by ticket",
 
            condition=cond.FlagBase.ENABLE_IF_TRUE,
 
        )
 
        pdns_by_ticket.enabling_products.set([
 
            self.ticket_professional,
 
            self.ticket_contributor,
 
            self.ticket_media,
 
            self.ticket_sponsor,
 
        ])
 
        pdns_by_ticket.categories.set([self.pdns_category, ])
 

	
 
        # They are available to speakers
 
        pdns_by_speaker = self.find_or_make(
 
            cond.SpeakerFlag,
 
            ("description", ),
 
            description="PDNS available to speakers",
 
            condition=cond.FlagBase.ENABLE_IF_TRUE,
 
            is_presenter=True,
 
            is_copresenter=True,
 

	
 
        )
 
        pdns_by_speaker.proposal_kind.set(self.main_conference_proposals)
 
        pdns_by_speaker.categories.set([self.pdns_category, ])
 

	
 
        # They are available to staff
 
        pdns_by_staff = self.find_or_make(
 
            cond.GroupMemberFlag,
 
            ("description", ),
 
            description="PDNS available to staff",
 
            condition=cond.FlagBase.ENABLE_IF_TRUE,
 
        )
 
        pdns_by_staff.group.set([
 
            self.group_team,
 
        ])
 
        pdns_by_staff.categories.set([self.pdns_category, ])
 

	
 
        # Don't allow people to get anything if they don't have a ticket first
 
        needs_a_ticket = self.find_or_make(
 
            cond.CategoryFlag,
 
            ("description", ),
 
            description="GottaGettaTicketFirst",
 
            condition=cond.FlagBase.DISABLE_IF_FALSE,
 
            enabling_category=self.ticket
 
        )
 
        needs_a_ticket.categories.set([
 
            self.extras,
 
            self.t_shirt,
 
            self.penguin_dinner,
 
            self.pdns_category,
 
        ])
 
        # Require attendees to accept the T&Cs and Code of Conduct
 
        needs_agreement = self.find_or_make(
 
            cond.CategoryFlag,
 
            ("description", ),
 
            description="Must Accept Terms",
 
            condition=cond.FlagBase.DISABLE_IF_FALSE,
 
            enabling_category=self.terms,
 
        )
 
        needs_agreement.categories.set([
 
            self.extras,
 
            self.t_shirt,
 
            self.penguin_dinner,
 
            self.pdns_category,
 
            self.speakers_dinner_ticket,
 
        ])
 

	
 
    def populate_discounts(self):
 
        def add_early_birds(discount):
 
            self.find_or_make(
 
                cond.DiscountForProduct,
 
                ("discount", "product"),
 
                discount=discount,
 
                product=self.ticket_contributor,
 
                price=settings.CONTRIBUTOR.earlybird_discount(),
 
                quantity=1,  # Per user
 
            )
 
            self.find_or_make(
 
                cond.DiscountForProduct,
 
                ("discount", "product"),
 
                discount=discount,
 
                product=self.ticket_professional,
 
                price=settings.PROFESSIONAL.earlybird_discount(),
 
                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_hobbyist_discount = self.find_or_make(
 
            cond.TimeOrStockLimitDiscount,
 
            ("description", ),
 
            description="Early Bird Discount - Hobbyist",
 
            end_time=settings.EARLY_BIRD_DEADLINE,
 
            limit=150,  # Across all users
 
        )
 
        self.find_or_make(
 
                cond.DiscountForProduct,
 
                ("discount", "product"),
 
                discount=early_bird_hobbyist_discount,
 
                product=self.ticket_hobbyist,
 
                price=settings.HOBBYIST.earlybird_discount(),
 
                quantity=1,  # Per user
 
        )
 

	
 
        early_bird = self.find_or_make(
 
            cond.TimeOrStockLimitDiscount,
 
            ("description", ),
 
            description="Early Bird Discount - Professional",
 
            end_time=settings.EARLY_BIRD_DEADLINE,
 
            limit=200,  # Across professionals and fairy sponsors
 
        )
 
        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)
 

	
 
        # Primary speaker gets a free speaker dinner ticket
 
        primary_speaker = self.find_or_make(
 
            cond.SpeakerDiscount,
 
            ("description", ),
 
            description="Complimentary for primary proposer",
 
            is_presenter=True,
 
            is_copresenter=False,
 
        )
 
        primary_speaker.proposal_kind.set(self.main_conference_proposals)
 
        free_category(primary_speaker, self.speakers_dinner_ticket)
 

	
 
        # Professional-Like ticket inclusions
 
        ticket_prolike_inclusions = self.find_or_make(
 
            cond.IncludedProductDiscount,
 
            ("description", ),
 
            description="Complimentary for ticket holder (Professional-level)",
 
        )
 
        ticket_prolike_inclusions.enabling_products.set([
 
            self.ticket_contributor,
 
            self.ticket_professional,
 
            self.ticket_media,
 
            self.ticket_sponsor,
 
            self.ticket_speaker,
 
        ])
 
        free_category(ticket_prolike_inclusions, self.penguin_dinner)
 
        free_category(ticket_prolike_inclusions, self.t_shirt)
 

	
 
        # Hobbyist ticket inclusions
 
        ticket_hobbyist_inclusions = self.find_or_make(
 
            cond.IncludedProductDiscount,
 
            ("description", ),
 
            description="Complimentary for ticket holder (Hobbyist-level)",
 
        )
 
        ticket_hobbyist_inclusions.enabling_products.set([
 
            self.ticket_hobbyist,
 
        ])
 
        free_category(ticket_hobbyist_inclusions, self.t_shirt)
 

	
 
        # Student ticket inclusions
 
        ticket_student_inclusions = self.find_or_make(
 
            cond.IncludedProductDiscount,
 
            ("description", ),
 
            description="Complimentary for ticket holder (Student-level)",
 
        )
 
        ticket_student_inclusions.enabling_products.set([
 
            self.ticket_student,
 
        ])
 
        free_category(ticket_student_inclusions, self.t_shirt)
 

	
 
        # Team ticket inclusions
 
        ticket_staff_inclusions = self.find_or_make(
 
            cond.IncludedProductDiscount,
 
            ("description", ),
 
            description="Complimentary for ticket holder staff)",
 
            description="Complimentary for ticket holder (Staff)",
 
        )
 
        ticket_staff_inclusions.enabling_products.set([
 
            self.ticket_team,
 
        ])
 
        free_category(ticket_staff_inclusions, self.penguin_dinner)
 

	
 
        # Team & volunteer shirts, regardless of ticket type
 
        staff_t_shirts = self.find_or_make(
 
            cond.GroupMemberDiscount,
 
            ("description", ),
 
            description="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=5)
 

	
 
        print(f"{self.count} categories found/made")
 

	
 
    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.
 
        '''
 
        self.count += 1
 
        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)
pinaxcon/settings.py
Show inline comments
 
from decimal import Decimal
 
import os
 
import sys
 

	
 
import django
 
import dj_database_url
 
import saml2
 
import saml2.saml
 

	
 
from datetime import date, datetime, timedelta
 

	
 
from dataclasses import dataclass
 

	
 
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
 
PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__))
 
DJANGO_ROOT = os.path.abspath(os.path.dirname(django.__file__))
 
BASE_DIR = PACKAGE_ROOT
 
sys.path.append(os.path.join(PROJECT_ROOT, 'vendor'))
 

	
 

	
 
### USER SETTINGS
 
DEV_MODE = os.environ.get("SYMPOSION_DEV_MODE", None)
 
DEBUG = os.environ.get('SYMPOSION_APP_DEBUG', '0')
 
if isinstance(DEBUG, str):
 
    try:
 
        i = int(DEBUG)
 
        if not i in [0, 1]:
 
            raise ValueError("not 0 or 1")
 
        DEBUG = bool(i)
 
    except ValueError:
 
        sys.exit('DEBUG env var must be set to string value of a 0 or 1')
 
else:
 
    sys.exit('DEBUG env var is in unexpected format.  Should be a string'
 
             'containing either a 0 or a 1 - Got type %s' % type(DEBUG))
 

	
 
DATABASES = {}
 
DATABASES['default'] = dj_database_url.config(conn_max_age=600)
 
if DATABASES['default']['ENGINE'] == 'django.db.backends.mysql':
 
    DATABASES['default']['OPTIONS'] = {'charset': 'utf8mb4'}
 

	
 
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
 
EMAIL_HOST = os.environ.get('EMAIL_HOST', None)
 
EMAIL_PORT = os.environ.get('EMAIL_PORT', 25)
 
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER', None)
 
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD', None)
 
DEFAULT_FROM_EMAIL = os.environ.get('DEFAULT_FROM_EMAIL', 'webmaster@localhost')
 
EMAIL_USE_SSL = False
 
EMAIL_USE_TLS = False
 
_EMAIL_SSL_FLAVOR=os.environ.get('EMAIL_SSL_FLAVOR', None)
 
if _EMAIL_SSL_FLAVOR == "TLS":
 
    EMAIL_USE_TLS = True
 
elif _EMAIL_SSL_FLAVOR == "SSL":
 
    EMAIL_USE_SSL = True
 

	
 
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', None)
 

	
 
PINAX_STRIPE_PUBLIC_KEY = os.environ.get('STRIPE_PUBLIC_KEY', None)
 
PINAX_STRIPE_SECRET_KEY = os.environ.get('STRIPE_SECRET_KEY', None)
 
PINAX_STRIPE_SEND_EMAIL_RECEIPTS = False
 

	
 
ANALYTICS_KEY = os.environ.get('ANALYTICS_KEY', None)
 

	
 
saml2_entityid = os.environ.get('SAML2_ENTITYID', None)
 
saml2_sp_name = os.environ.get('SAML2_SP_NAME', None)
 
saml2_sp_assertion_service = os.environ.get('SAML2_SP_ASSERTION_SERVICE', None)
 
saml2_sp_slo_rdir = os.environ.get('SAML2_SP_SLO_RDIR', None)
 
saml2_sp_slo_post = os.environ.get('SAML2_SP_SLO_POST', None)
 

	
 
saml2_idp_metadata = {
 
    'local': [os.environ.get('SAML2_IDP_METADATA_FILE', None)],
 
    }
 
saml2_signing_key = os.environ.get('SAML2_SIGNING_KEY', None)
 
saml2_signing_crt = os.environ.get('SAML2_SIGNING_CRT', None)
 
saml2_encr_key = os.environ.get('SAML2_ENCRYPTION_KEY', None)
 
saml2_encr_crt = os.environ.get('SAML2_ENCRYPTION_CRT', None)
 
saml2_contact = {
 
    'given_name': os.environ.get("META_GIVEN_NAME", 'Bastard'),
 
    'sur_name': os.environ.get('META_FAM_NAME', 'Operator'),
 
    'company': os.environ.get('META_COMPANY', 'Corp1'),
 
    'email_address': os.environ.get('META_EMAIL', 'op@example.com'),
 
    'contact_type': 'technical'},
 

	
 
fail = False
 

	
 
BADGER_DEFAULT_SVG = 'registrasion/badge.svg'
 
BADGER_DEFAULT_FORM = "registrasion/badge_form.html"
 

	
 
if SECRET_KEY is None:
 
    print("FAILURE: You need to supply a DJANGO_SECRET_KEY "
 
          "environment variable")
 
    fail = True
 

	
 
if PINAX_STRIPE_PUBLIC_KEY is None:
 
    print("FAILURE: You need to supply a STRIPE_PUBLIC_KEY "
 
          "environment variable")
 
    fail = True
 

	
 
if PINAX_STRIPE_SECRET_KEY is None:
 
    print("FAILURE: You need to supply a STRIPE_SECRET_KEY "
 
          "environment variable")
 
    fail = True
 

	
 
if fail:
 
    sys.exit('FAILURE: Missing environment variables.')
 

	
 
### Standard settings
 

	
 
ADMIN_USERNAMES = []
 

	
 
CACHES = {
 
    'default': {
 
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
 
        'LOCATION': 'unique-snowflake',
 
    }
 
}
 

	
 

	
 
ALLOWED_HOSTS = ['127.0.0.1', 'localhost', '*']
 

	
 
TIME_ZONE = "Pacific/Auckland"
 
DATE_FORMAT = "j F Y"
 
LANGUAGE_CODE = "en-au"
 

	
 
SITE_ID = int(os.environ.get("SITE_ID", 1))
 
USE_I18N = True
 
USE_L10N = True
 
USE_TZ = True
 

	
 
MEDIA_ROOT = os.path.join(PACKAGE_ROOT, "site_media", "media")
 
MEDIA_URL = "/site_media/media/"
 

	
 
STATIC_ROOT = os.path.join(PROJECT_ROOT, 'static/build')
 
STATIC_URL = '/static/build/'
 

	
 
STATICFILES_DIRS = [
 
    os.path.join(PROJECT_ROOT, 'static/src'),
 
]
 

	
 
STATICFILES_FINDERS = [
 
    "django.contrib.staticfiles.finders.FileSystemFinder",
 
    "django.contrib.staticfiles.finders.AppDirectoriesFinder",
 
    "sass_processor.finders.CssFinder",
 
]
 

	
 
TEMPLATES = [
 
    {
 
        "BACKEND": "django.template.backends.django.DjangoTemplates",
 
        "DIRS": [
 
            os.path.join(PACKAGE_ROOT, "templates"),
 
            os.path.join(DJANGO_ROOT, 'forms/templates')
 
        ],
 
        "APP_DIRS": True,
 
        "OPTIONS": {
 
            "debug": DEBUG,
 
            "context_processors": [
 
                "django.contrib.auth.context_processors.auth",
 
                "django.template.context_processors.debug",
 
                "django.template.context_processors.i18n",
 
                "django.template.context_processors.media",
 
                "django.template.context_processors.static",
 
                "django.template.context_processors.tz",
 
                "django.template.context_processors.request",
 
                "django.contrib.messages.context_processors.messages",
 
                "pinax_theme_bootstrap.context_processors.theme",
 
                "symposion.reviews.context_processors.reviews",
 
                "django_settings_export.settings_export",
 
            ],
 
        },
 
    },
 
]
 

	
 
MIDDLEWARE_CLASSES = [
 
    "django.contrib.sessions.middleware.SessionMiddleware",
 
    "django.middleware.common.CommonMiddleware",
 
    "django.middleware.csrf.CsrfViewMiddleware",
 
    "django.contrib.auth.middleware.AuthenticationMiddleware",
 
    "django.contrib.auth.middleware.SessionAuthenticationMiddleware",
 
    "django.contrib.messages.middleware.MessageMiddleware",
 
    "debug_toolbar.middleware.DebugToolbarMiddleware",
 
    "reversion.middleware.RevisionMiddleware",
 
    "waffle.middleware.WaffleMiddleware",
 
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
 
    "django.contrib.flatpages.middleware.FlatpageFallbackMiddleware",
 
    'pinaxcon.monkey_patch.MonkeyPatchMiddleware',
 
]
 

	
 
if DEV_MODE and DEV_MODE == "LAPTOP":
 
    ROOT_URLCONF = "pinaxcon.devmode_urls"
 
else:
 
    ROOT_URLCONF = "pinaxcon.urls"
 

	
 
# Python dotted path to the WSGI application used by Django's runserver.
 
WSGI_APPLICATION = "pinaxcon.wsgi.application"
 

	
 
INSTALLED_APPS = [
 
    "django.contrib.admin",
 
    "django.contrib.auth",
 
    "django.contrib.contenttypes",
 
    "django.contrib.flatpages",
 
    "django.contrib.messages",
 
    "django.contrib.sessions",
 
    "django.contrib.sites",
 
    "django.contrib.staticfiles",
 
    "django.contrib.humanize",
 
    "debug_toolbar",
 

	
 
    'djangosaml2',
 

	
 
    # theme
 
    "bootstrapform",
 
    "pinax_theme_bootstrap",
 
    "sass_processor",
 

	
 
    # external
 
    "easy_thumbnails",
 
    "taggit",
 
    "reversion",
 
    "sitetree",
 
    "pinax.eventlog",
 

	
 
    # symposion
 
    "symposion",
 
    "symposion.conference",
 
    "symposion.proposals",
 
    "symposion.reviews",
 
    "symposion.schedule",
 
    "symposion.speakers",
 
    "symposion.teams",
 

	
 
    # Registrasion
 
    "registrasion",
 

	
 
    # Registrasion-stripe
 
    "pinax.stripe",
 
    "django_countries",
 
    "registripe",
 

	
 
    #registrasion-desk
 
    "regidesk",
 

	
 
    # admin - required by registrasion ??
 
    "nested_admin",
 

	
 
    # project
 
    "pinaxcon",
 
    "pinaxcon.proposals",
 
    "pinaxcon.registrasion",
 
    "pinaxcon.raffle",
 
    "jquery",
 
    "djangoformsetjs",
 

	
 
    # testing and rollout
 
    "django_nose",
 
    "waffle",
 

	
 
    "crispy_forms",
 
]
 

	
 
CRISPY_TEMPLATE_PACK = "bootstrap4"
 

	
 
DEBUG_TOOLBAR_PANELS = [
 
    'debug_toolbar.panels.versions.VersionsPanel',
 
    'debug_toolbar.panels.timer.TimerPanel',
 
    'debug_toolbar.panels.settings.SettingsPanel',
 
    'debug_toolbar.panels.headers.HeadersPanel',
 
    'debug_toolbar.panels.request.RequestPanel',
 
    'debug_toolbar.panels.sql.SQLPanel',
 
    'debug_toolbar.panels.staticfiles.StaticFilesPanel',
 
    'debug_toolbar.panels.cache.CachePanel',
 
    'debug_toolbar.panels.signals.SignalsPanel',
 
    'debug_toolbar.panels.logging.LoggingPanel',
 
    'debug_toolbar.panels.templates.TemplatesPanel',
 
    'debug_toolbar.panels.redirects.RedirectsPanel',
 
]
 

	
 
DEBUG_TOOLBAR_CONFIG = {
 
    'INTERCEPT_REDIRECTS': False,
 
    'SHOW_TOOLBAR_CALLBACK': lambda x: DEBUG,
 
}
 

	
 
LOGGING = {
 
    'version': 1,
 
    'disable_existing_loggers': False,
 
    'formatters': {
 
        'verbose': {
 
            'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
 
        },
 
        'simple': {
 
            'format': '%(asctime)s %(levelname)s $(module)s %(message)s'
 
        },
 
    },
 
    'filters': {
 
        'require_debug_false': {
 
            '()': 'django.utils.log.RequireDebugFalse'
 
        }
 
    },
 
    'handlers': {
 
        'console': {
 
            'level': 'DEBUG',
 
            'class': 'logging.StreamHandler',
 
            'formatter': 'simple'
 
        },
 
        'mail_admins': {
 
            'level': 'ERROR',
 
            'filters': ['require_debug_false'],
 
            'class': 'django.utils.log.AdminEmailHandler',
 
            'include_html': True,
 
        }
 
    },
 
    'loggers': {
 
        'django.request': {
 
            'handlers': ['mail_admins'],
 
            'level': 'DEBUG',
 
            'propagate': True,
 
        },
 
        'symposion.request': {
 
            'handlers': ['mail_admins'],
 
            'level': 'DEBUG',
 
            'propagate': True,
 
        },
 
    },
 
    'root': {
 
        'handlers': ['console'],
 
        'level': 'DEBUG'
 
    },
 
}
 
FIXTURE_DIRS = [
 
    os.path.join(PROJECT_ROOT, "fixtures"),
 
]
 

	
 
AUTHENTICATION_BACKENDS = [
 
    'symposion.teams.backends.TeamPermissionsBackend',
 
    'django.contrib.auth.backends.ModelBackend',
 
    'djangosaml2.backends.Saml2Backend',
 
]
 

	
 
LOGIN_URL = '/saml2/login/'
 
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
 

	
 
CONFERENCE_ID = 1
 
PROPOSAL_FORMS = {
 
    "talk": "pinaxcon.proposals.forms.TalkProposalForm",
 
    "tutorial": "pinaxcon.proposals.forms.TutorialProposalForm",
 
    "miniconf": "pinaxcon.proposals.forms.MiniconfProposalForm",
 
    ### LCA2020 Miniconfs
 
    "containers-miniconf": "pinaxcon.proposals.forms.ContainersProposalForm",
 
    "creative-arts-miniconf": "pinaxcon.proposals.forms.CreativeArtsProposalForm",
 
    "docs-miniconf": "pinaxcon.proposals.forms.DocsProposalForm",
 
    "freebsd-miniconf": "pinaxcon.proposals.forms.FreeBsdProposalForm",
 
    "games-miniconf": "pinaxcon.proposals.forms.GamesProposalForm",
 
    "glam-miniconf": "pinaxcon.proposals.forms.GlamProposalForm",
 
    "kernel-miniconf": "pinaxcon.proposals.forms.KernelProposalForm",
 
    "open-education-miniconf": "pinaxcon.proposals.forms.OpenEducationProposalForm",
 
    "open-hardware-miniconf": "pinaxcon.proposals.forms.OpenHardwareProposalForm",
 
    "open-isa-miniconf": "pinaxcon.proposals.forms.OpenIsaProposalForm",
 
    "security-miniconf": "pinaxcon.proposals.forms.SecurityProposalForm",
 
    "sysadmin-miniconf": "pinaxcon.proposals.forms.SysAdminProposalForm",
 
}
 

	
 
# Registrasion bits:
 
ATTENDEE_PROFILE_MODEL = "pinaxcon.registrasion.models.AttendeeProfile"
 
ATTENDEE_PROFILE_FORM = "pinaxcon.registrasion.forms.ProfileForm"
 
INVOICE_CURRENCY = "NZD"
 
INVOICE_CURRENCY = "AUD"
 
TICKET_PRODUCT_CATEGORY = 1
 
TERMS_PRODUCT_CATEGORY = 2
 
ATTENDEE_PROFILE_FORM = "pinaxcon.registrasion.forms.ProfileForm"
 

	
 
#REGIDESK
 
REGIDESK_BOARDING_GROUP = "Ready For Boarding"
 

	
 
# CSRF custom error screen
 
CSRF_FAILURE_VIEW = "pinaxcon.csrf_view.csrf_failure"
 

	
 
# Use nose to run all tests
 
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
 

	
 
# Tell nose to measure coverage on the 'foo' and 'bar' apps
 
NOSE_ARGS = [
 
    '--with-coverage',
 
    '--cover-package=registrasion.controllers,registrasion.models',
 
]
 

	
 
SASS_PROCESSOR_INCLUDE_DIRS = [
 
    os.path.join(PROJECT_ROOT, 'static/src/bootstrap/scss'),
 
    os.path.join(PROJECT_ROOT, 'static/src/scss'),
 
]
 

	
 
xmlsec_binary = '/usr/bin/xmlsec1'
 
if not os.path.isfile(xmlsec_binary):
 
        sys.exit('ERROR: xmlsec1 binary missing, EXITING')
 

	
 
SAML_ATTRIBUTE_MAPPING = {
 
    'uid': ('username', ),
 
    'mail': ('email', ),
 
    'givenName': ('first_name', ),
 
    'sn': ('last_name', ),
 
}
 
SAML_CONFIG = {
 
    'xmlsec_binary': xmlsec_binary,
 
    'entityid': saml2_entityid,
 
    'attribute_map_dir': os.path.join(PACKAGE_ROOT, 'saml2/attribute-maps'),
 
    'service': {
 
        'sp': {
 
            'name': saml2_sp_name,
 
            'endpoints': {
 
                'assertion_consumer_service': [
 
                    saml2_sp_assertion_service,
 
                    ],
 
                'single_logout_service': [
 
                    (saml2_sp_slo_rdir, saml2.BINDING_HTTP_REDIRECT),
 
                    (saml2_sp_slo_post, saml2.BINDING_HTTP_POST),
 
                    ],
 
                },
 
            'logout_requests_signed': True,
 
            'required_attributes': ['uid', 'mail', 'givenName', 'sn'],
 
            },
 
        },
 
    'metadata': saml2_idp_metadata,
 
    'debug': 0,
 
    'key_file': saml2_signing_key,
 
    'cert_file': saml2_signing_crt,
 
    'encryption_keypairs': [{
 
        'key_file': saml2_encr_key,
 
        'cert_file': saml2_encr_crt,
 
    }],
 
    'contact_person': saml2_contact,
 
    'valid_for': 10,
 
}
 

	
 
if 'SAML_CONFIG_LOADER' in os.environ:
 
    SAML_CONFIG_LOADER = os.environ.get('SAML_CONFIG_LOADER')
 

	
 
DEFAULT_FILE_STORAGE = 'gapc_storage.storage.GoogleCloudStorage'
 
GAPC_STORAGE = {
 
    'num_retries': 2,
 
}
 

	
 
SETTINGS_EXPORT = [
 
    'DEBUG',
 
    'ANALYTICS_KEY',
 
]
 

	
 
if DEV_MODE and DEV_MODE == "LAPTOP":
 
    print("ENABLING LAPTOP MODE")
 
    from .devmode_settings import *
 

	
 

	
 
class Category(object):
 
    tickets = []
 

	
 
    @classmethod
 
    def order(cls, ticket) -> int:
 
        return (cls.tickets.index(ticket) + 1) * 10
 

	
 

	
 
@dataclass(frozen=True)
 
class Ticket:
 
    name: str
 
    regular_price: Decimal
 
    earlybird_price: Decimal
 

	
 
    def earlybird_discount(self):
 
        return self.regular_price - self.earlybird_price
 

	
 

	
 
@dataclass(frozen=True)
 
class DinnerTicket:
 
    name: str
 
    price: Decimal
 
    description: str
 
    reservation: timedelta
 
    cat: Category
 

	
 
    def order(self):
 
        return self.cat.order(self)
 

	
 

	
 
class PenguinDinnerTicket(DinnerTicket):
 
    pass
 

	
 

	
 
class SpeakersDinnerTicket(DinnerTicket):
 
    pass
 

	
 

	
 
class SpeakersDinnerCat(Category):
 
    @classmethod
 
    def create(cls, name: str, price: Decimal, description: str, reservation: timedelta) -> SpeakersDinnerTicket:
 
        t = SpeakersDinnerTicket(name, price, description, reservation, cls)
 
        cls.tickets.append(t)
 
        return t
 

	
 

	
 
class PenguinDinnerCat(Category):
 
    @classmethod
 
    def create(cls, name: str, price: Decimal, description: str, reservation: timedelta) -> PenguinDinnerTicket:
 
        t = PenguinDinnerTicket(name, price, description, reservation, cls)
 
        cls.tickets.append(t)
 
        return t
 

	
 

	
 
LCA_START = datetime(2020, 1, 13)
 
LCA_END = datetime(2020, 1, 17)
 
EARLY_BIRD_DEADLINE = datetime(2019, 11, 1)
 
PENGUIN_DINNER_TICKET_DATE = date(2020, 1, 15)
 
SPEAKER_DINNER_TICKET_DATE = date(2020, 1, 14)
 
PDNS_TICKET_DATE = date(2020, 1, 16)
 

	
 
TSHIRT_PRICE = Decimal("25.00")
 

	
 
CONTRIBUTOR = Ticket("Contributor", Decimal("1999.00"), Decimal("1849.00"))
 
PROFESSIONAL = Ticket("Professional", Decimal("1099.00"), Decimal("949.00"))
 
HOBBYIST = Ticket("Hobbyist", Decimal("549.00"), Decimal("399.00"))
 
STUDENT = Ticket("Student", Decimal("199.00"), None)
 

	
 
MINICONF_MT = Ticket("Monday and Tuesday Only", Decimal("198.00"), None)
 
MINICONF_M = Ticket("Monday Only", Decimal("99.00"), None)
 
MINICONF_T = Ticket("Tuesday Only", Decimal("99.00"), None)
 

	
 
MEDIA = Ticket("Media", Decimal("0.0"), None)
 
SPEAKER = Ticket("Speaker", Decimal("0.0"), None)
 
SPONSOR = Ticket("Sponsor", Decimal("0.0"), None)
 

	
 
CONFERENCE_ORG = Ticket("Conference Organiser", Decimal("0.0"), None)
 
CONFERENCE_VOL = Ticket("Conference Volunteer", Decimal("0.0"), None)
 

	
 
PENGUIN_DINNER = PenguinDinnerCat
 
PENGUIN_DINNER_ADULT = PenguinDinnerCat.create(
 
    "Adult", Decimal("95.00"),
 
    "Includes an adult's meal and full beverage service.",
 
    timedelta(hours=1))
 
PENGUIN_DINNER_CHILD = PenguinDinnerCat.create(
 
    "Child", Decimal("50.00"),
 
    "Children 14 and under. "
 
    "Includes a child's meal and soft drink service.",
 
    timedelta(hours=1))
 
PENGUIN_DINNER_INFANT = PenguinDinnerCat.create(
 
    "Infant", Decimal("0.0"),
 
    "Includes no food or beverage service.",
 
    timedelta(hours=1))
 

	
 
SPEAKERS_DINNER = SpeakersDinnerCat
 

	
 
SPEAKERS_DINNER_ADULT = SpeakersDinnerCat.create(
 
    "Adult", Decimal("100.00"),
 
    "Includes an adult's meal and full beverage service.",
 
    timedelta(hours=1))
 

	
 
SPEAKERS_DINNER_CHILD = SpeakersDinnerCat.create(
 
    "Child", Decimal("60.00"),
 
    "Children 14 and under. "
 
    "Includes a child's meal and soft drink service.",
 
    timedelta(hours=1))
 

	
 
SPEAKERS_DINNER_INFANT = SpeakersDinnerCat.create(
 
    "Infant", Decimal("00.00"),
 
    "Infant must be seated in an adult's lap. "
 
    "No food or beverage service.",
 
    timedelta(hours=1))
 
# SPEAKERS_DINNER_CHILD = SpeakersDinnerCat.create(
 
#     "Child", Decimal("60.00"),
 
#     "Children 14 and under. "
 
#     "Includes a child's meal and soft drink service.",
 
#     timedelta(hours=1))
 

	
 
# SPEAKERS_DINNER_INFANT = SpeakersDinnerCat.create(
 
#     "Infant", Decimal("00.00"),
 
#     "Infant must be seated in an adult's lap. "
 
#     "No food or beverage service.",
 
#     timedelta(hours=1))
pinaxcon/templates/_form_snippet.html
Show inline comments
 
{% load lca2018_tags %}
 
{% load lca2019_tags %}
 
{% load bootstrap %}
 
{% load crispy_forms_tags %}
 

	
 
{% if form.non_field_errors %}
 
  <div class="has-errors">
 
    {{ form.non_field_errors }}
 
  </div>
 
  <br/>
 
{% endif %}
 

	
 
<p>
 
{% if form|has_required_fields %}
 
<blockquote>
 
  Fields marked with a * are required
 
</blockquote>
 
Fields marked with a * are required.
 
{% endif %}
 
{% if form|has_price_fields %}
 
<strong>Item prices are before any discount.</strong>
 
{% endif %}
 
</p>
 

	
 
{{ form|crispy }}
pinaxcon/templates/registrasion/_invoice_details.html
Show inline comments
 
{% load registrasion_tags %}
 
{% load lca2018_tags %}
 

	
 
<h2>Tax Invoice/Statement</h2>
 
<h3>Linux Australia</h3>
 
<h4>GST #90-792-369</h4>
 
<h4>ABN 56 987 117 479</h4>
 

	
 
<p>
 
  Enquiries: please e-mail <a href="mailto:contact@lca2019.org">contact@lca2019.org</a>
 
  Enquiries: please e-mail <a href="mailto:contact@lca2020.linux.org.au">contact@lca2020.linux.org.au</a>
 
</p>
 

	
 
<ul class="list-unstyled">
 
  <li><strong>Invoice number:</strong> {{ invoice.id }}
 
  <li><strong>Invoice status:</strong> {{ invoice.get_status_display }}</li>
 
  <li><strong>Issue date:</strong> {{ invoice.issue_time|date:"DATE_FORMAT" }}
 
  {% if not invoice.is_void %}
 
    <li><strong>Due:</strong> {{ invoice.due_time|date:"DATETIME_FORMAT"}}</li>
 
  {% endif %}
 
</ul>
 

	
 
<div>
 
<h4>Attention:</h4>
 
  {{ invoice.recipient|linebreaksbr}}
 
</div>
 

	
 
<p>This invoice has been issued as a result of an application to attend {% conference_name %}. All amounts are in New Zealand Dollars (NZD).</p>
 
<p>This invoice has been issued as a result of an application to attend {% conference_name %}. All amounts are in Australian Dollars (AUD).</p>
 

	
 
<table class="table table-striped my-4">
 
  <thead>
 
    <tr>
 
      <th>Description</th>
 
      <th class="text-right">Quantity</th>
 
      <th class="text-right">Price/Unit</th>
 
      <th class="text-right">Total</th>
 
    </tr>
 
  </thead>
 
  <tbody>
 
    {% for line_item in invoice.lineitem_set.all %}
 
      <tr>
 
        <td>{{ line_item.description }}</td>
 
        <td class="text-right">{{ line_item.quantity }}</td>
 
        <td class="text-right">${{ line_item.price }}</td>
 
        <td class="text-right">${{ line_item.total_price }}</td>
 
      </tr>
 
    {% endfor %}
 

	
 
    <tr><th colspan="4"></th></tr>
 

	
 
    <tr>
 
      <th colspan="3">Includes 15% New Zealand Goods and Services Tax</th>
 
      <th colspan="3">Includes 10% Australian Goods and Services Tax</th>
 
      <td class="text-right">${{ invoice.value|gst}}</td>
 
    </tr>
 

	
 
    <tr>
 
      <th colspan="3">Total</th>
 
      <td class="text-right">${{ invoice.value }}</td>
 
    </tr>
 

	
 
    <tr><th colspan="4"></th></tr>
 

	
 
    <tr>
 
      <th colspan="3">Total payments received:</th>
 
      <td class="text-right">${{ invoice.total_payments }}</td>
 
    </tr>
 
    {% if invoice.is_unpaid or invoice.is_paid %}
 
      <tr>
 
        <th colspan="3">Balance due:</th>
 
        <td class="text-right">${{ invoice.balance_due }}</td>
 
      </tr>
 
    {% endif %}
 
  </tbody>
 
</table>
 

	
 
{% if invoice.paymentbase_set.all %}
 
  <div class="page-break"></div>
 
  <h3 class="pt-4">Payments received</h3>
 
  {% include "registrasion/payment_list.html" with payments=invoice.paymentbase_set.all %}
 
{% endif %}
 

	
 

	
 
<hr />
 

	
 
<p>{% conference_name %} is a project of Linux Australia, Inc.</p>
 

	
 
<p>
 
  GPO Box 4788 <br />
 
  Sydney NSW 2001 <br />
 
  Australia <br />
 
  ABN 56 987 117 479 <br />
 
  NZ GST #90-792-369
 
<p>
pinaxcon/templates/registrasion/base.html
Show inline comments
 
{% extends "utility_page.html" %}
 
{% extends "site_base.html" %}
 
{% load staticfiles %}
 
{% load lca2018_tags %}
 
{% load i18n %}
 

	
 
{% block head_title %}{% block page_title %}{% endblock %}{% endblock %}
 

	
 
{% block utility_body %}
 
  {% block proposals_body %}
 
  {% endblock %}
 
{% block content_base %}
 
{% block content %}
 
  <div class="jumbotron rego-content">
 
    {% block proposals_body %}
 
    {% endblock %}
 
  </div>
 
{% endblock content %}
 
{% endblock %}
 

	
pinaxcon/templates/registrasion/discount_list.html
Show inline comments
 
{% if discounts %}
 
<div class="my-4 py-4 px-4">
 
  <h4>Discounts and Complimentary Items</h4>
 
  <p>The following discounts and complimentary items are available to you. If you wish to take advantage of this offer, you must choose your items below. The discounts will be applied automatically when you check out.</p>
 
<div class="alert alert-danger my-4 pb-4 text-center">
 
  <h4 class="alert-heading">Discounts and Complimentary Items</h4>
 
  <p>The following discounts and complimentary items are available to you:</p>
 
  {% regroup discounts by discount.description as discounts_grouped %}
 
  <ul class="d-inline-block text-left">
 
  {% for discount_type in discounts_grouped %}
 
  <strong>{{ discount_type.grouper }}</strong>
 
  <ul>
 
    {% for discount in discount_type.list %}
 
    <li>{{ discount.quantity }} &times; {{ discount.clause }}</li>
 
    {% endfor %}
 
  </ul>
 
    <li>
 
      <strong>{{ discount_type.grouper }}</strong>
 
      <ul>
 
        {% for discount in discount_type.list %}
 
        <li>{{ discount.quantity }} &times; {{ discount.clause }}</li>
 
        {% endfor %}
 
      </ul>
 
    </li>
 
  {% endfor %}
 
  </ul>
 
  <p>
 
    <strong>
 
    Please ensure you enter a value in the form below to take advantage of this discount.<br>
 
    The discounts will be applied automatically when you check out.
 
    </strong>
 
  </p>
 
  <p class="mb-0">
 
    <strong>
 
    If you do not enter anything, you will not receive anything from this category.
 
    </strong>
 
  </p>
 
</div>
 

	
 
{% endif %}
pinaxcon/templates/registrasion/guided_registration.html
Show inline comments
 
{% extends "registrasion/base.html" %}
 
{% load lca2018_tags %}
 

	
 
{% block header_title %}Buy Your Ticket{% endblock %}
 
{% block header_paragraph %}Step {{ current_step }} of {{ total_steps|add:1 }} &ndash; {{ title }} {% endblock %}
 
{% block page_title %}Buy Your Ticket{% endblock %}
 
{% block page_lead %}Step {{ current_step }} of {{ total_steps|add:1 }} &ndash; {{ title }} {% endblock %}
 

	
 
{% block scripts_extra %}
 
  {% for section in sections %}
 
    {{ section.form.media.js }}
 
  {% endfor %}
 
<script type="text/javascript">
 
  <script type="text/javascript">
 
    postcode_label = $("label[for='id_profile-state']");
 
    postcode_help = $("#id_profile-state + p");
 
      $('#id_profile-country').change(function () {
 
        if ($(this).val() == 'AU' )  {
 
          postcode_label.addClass('label-required');
 
          postcode_help.show();
 
        } else {
 
          postcode_label.removeClass('label-required');
 
          postcode_help.hide();
 
        } });
 
        $("#id_profile-country").change();
 

	
 
      </script>
 

	
 
  </script>
 
{% endblock %}
 

	
 
{% block proposals_body %}
 

	
 
  <form class="form-horizontal" method="POST" action="" enctype="multipart/form-data">
 
    {% csrf_token %}
 

	
 
    {% for section in sections %}
 
    <h2>{{ section.title }}</h2>
 

	
 
    {% if section.description %}
 
    <blockquote>{{ section.description|safe }}</blockquote>
 
    {% endif %}
 

	
 
    <fieldset>
 
        {% if section.discounts %}
 
          {% include "registrasion/discount_list.html" with discounts=section.discounts %}
 
        {% endif %}
 
      {% if section.discounts %}
 
        {% include "registrasion/discount_list.html" with discounts=section.discounts %}
 
      {% endif %}
 

	
 
        {% include "_form_snippet.html" with form=section.form %}
 
      {% include "_form_snippet.html" with form=section.form %}
 

	
 
        <br />
 
      </fieldset>
 
        {% endfor %}
 
      <br />
 
    </fieldset>
 
    {% endfor %}
 

	
 
      {% if current_step > 1 %}
 
      <a class="btn btn-primary" role="button" href="{{ previous_step }}">Back</a>
 
      {% endif %}
 
      <input class="btn btn-primary" type="submit" value="Next Step" />
 
    {% if current_step > 1 %}
 
    <a class="btn btn-secondary" role="button" href="{{ previous_step }}">Back</a>
 
    {% endif %}
 
    <input class="btn btn-primary" type="submit" value="Next Step" />
 
  </form>
 

	
 

	
 
{% endblock %}
pinaxcon/templates/registrasion/invoice.html
Show inline comments
 
{% extends "registrasion/base.html" %}
 
{% load registrasion_tags %}
 
{% load lca2018_tags %}
 
{% load staticfiles %}
 

	
 
{% block header_title %}{% conference_name %}{% endblock %}
 
{% block head_title %}Tax Invoice/Statement #{{ invoice.id }}{% endblock %}
 
{% block page_title %}{% conference_name %}{% endblock %}
 

	
 
{% block proposals_body %}
 
{% include "registrasion/_invoice_details.html" %}
 
<div class="hidden-print mb-4 pb-4">
 
<div class="d-print-none mb-4 pb-4">
 
  {% if invoice.is_unpaid %}
 
  <p>
 
    <strong>NOTICE:</strong> The above invoice is automatically generated, and
 
    will be voided if you amend your selections before payment, or if discounts
 
    or products contained in the invoice become unavailable. The products and
 
    discounts are only reserved until the invoice due time, please pay before then
 
    to guarantee your selection. Late payments are accepted only if the products
 
    and discounts are still available.</p>
 

	
 
  {% url "invoice_access" invoice.user.attendee.access_code as access_url %}
 
  <p>Your most recent unpaid invoice will be available at
 
    <a href="{{ access_url }}">{{ request.scheme }}://{{ request.get_host }}{{ access_url }}</a>
 
    You can print that page for your records, or give this URL to your accounts department to pay for this invoice
 
  </p>
 

	
 
  <a class="btn btn-primary" href='{% url "registripe_card" invoice.id invoice.user.attendee.access_code %}'>Pay this invoice by card</a>
 

	
 
  {% if user.is_staff %}
 
  <a class="btn btn-primary" href="{% url "manual_payment" invoice.id %}">Apply manual payment</a>
 
  <a class="btn btn-secondary" href="{% url "manual_payment" invoice.id %}">Apply manual payment</a>
 
  {% endif %}
 

	
 
  {% elif invoice.is_paid %}
 
  {% if user.is_staff %}
 
  <a class="btn btn-default" href="{% url "manual_payment" invoice.id %}">Apply manual payment/refund</a>
 
  <a class="btn btn-default" href="{% url "refund" invoice.id %}">Refund by issuing credit note</a>
 
  {% endif %}
 
{% endif %}
 
</div>
 

	
 

	
 

	
 
{% endblock %}
pinaxcon/templates/registrasion/product_category.html
Show inline comments
 
{% extends "registrasion/base.html" %}
 
{% load registrasion_tags %}
 
{% load lca2018_tags %}
 

	
 
{% block header_title %}Product Category: {{ category.name }}{% endblock %}
 
{% block header_inset_image %}{% illustration "lavender.svg" %}{% endblock %}
 
{% block page_title %}Product Category: {{ category.name }}{% endblock %}
 

	
 
{% block scripts_extra %}
 
  {{ voucher_form.media.js }}
 
  {{ form.media.js }}
 

	
 

	
 
{% endblock %}
 

	
 
{% block proposals_body %}
 

	
 

	
 
  <form class="form-horizontal my-4" method="post" action="">
 
    {% csrf_token %}
 

	
 

	
 
    <div class="vertical-bigger"></div>
 

	
 
    {% items_purchased category as items %}
 
    {% if items %}
 
      <h3>Paid items</h3>
 
      <p>You have already paid for the following items:</p>
 
      {% include "registrasion/_items_list.html" with items=items %}
 

	
 
    {% endif %}
 

	
 

	
 
    <h1>{{ category.name }}</h1>
 
    <h2>{{ category.name }}</h2>
 
    <blockquote>{{ category.description|safe }}</blockquote>
 

	
 
    <fieldset>
 
      {% if discounts %}
 
        {% include "registrasion/discount_list.html" with discounts=discounts %}
 
      {% endif %}
 

	
 
      <h2>Make a selection</h2>
 
      <h3>Make a selection</h3>
 
      {% include "_form_snippet.html" with form=form %}
 

	
 
      <br />
 
      <div class="btn-group">
 
          <input class="btn btn-primary" type="submit" value="Add to cart" />
 
          <a href="{% url "dashboard" %}" class="btn btn-default">Return to dashboard</a>
 
      </div>
 
      <input class="btn btn-primary" type="submit" value="Add to cart" />
 
      <a href="{% url "dashboard" %}" class="btn btn-link">Return to dashboard</a>
 
    </fieldset>
 
  </form>
 

	
 

	
 
{% endblock %}
pinaxcon/templates/registrasion/review.html
Show inline comments
 
{% extends "registrasion/base.html" %}
 
{% load registrasion_tags %}
 
{% load lca2018_tags %}
 

	
 
{% block header_title %}Review your selection{% endblock %}
 
{% block header_inset_image %}{% illustration "wineglass.svg" %}{% endblock %}
 
{% block header_paragraph %}
 
{% block page_title %}Review your selection{% endblock %}
 
{% block page_lead %}
 
  Please ensure that you have selected all of the products you require, including
 
  t-shirts and social event tickets.
 
{% endblock %}
 

	
 
{% block scripts_extra %}
 
  {{ voucher_form.media.js }}
 
  {{ form.media.js }}
 
{% endblock %}
 

	
 

	
 
{% block proposals_body %}
 
  <h1 class="mb-4">Order Review</h1>
 

	
 
  {% items_pending as pending %}
 
  {% if pending %}
 
  <div class="my-4">
 
    <h2>Current selection</h2>
 
    <p>You've selected the following items, which will be in your invoice when
 
      you check out:<p>
 
    <p>You've selected the following items, which will be in your invoice when you check out:<p>
 
    {% include "registrasion/_items_list.html" with items=pending %}
 
  </div>
 
  {% endif %}
 

	
 
  {% items_purchased as purchased %}
 
  {% if purchased %}
 
  <div class="my-4">
 
    <h2>Previously purchased</h2>
 
    <p>You've already paid for the following items:</p>
 
    {% include "registrasion/_items_list.html" with items=purchased suffix="<em>(PAID)</em>" %}
 
  </div>
 
  {% endif %}
 

	
 
  <div class="my-4">
 
    <h2>Modify your selection</h2>
 
    <p>
 

	
 
    {% missing_categories as missing %}
 
    {% if missing %}
 
        <strong>You have <em>not</em> selected anything from the following
 
          categories. If your ticket includes any of these, you still need to
 
          make a selection:
 
        </strong>
 
    <div class="alert alert-warning my-4 pb-4">
 
      <h4 class="alert-heading">You have empty categories</h4>
 
      <p>You have <em>not</em> selected anything from the following
 
        categories. If your ticket includes any of these, you still need to
 
        make a selection:
 
      </p>
 

	
 
      {% include "registrasion/_category_list.html" with categories=missing %}
 
      {% endif %}
 
    </p>
 
    </div>
 
    {% endif %}
 

	
 
    <p>
 
      <strong>You can also change your selection from these categories:</strong>
 
      <strong>You can change your selection from these categories:</strong>
 
      {% available_categories as available %}
 
      {% include "registrasion/_category_list.html" with categories=available exclude=missing %}
 
    </p>
 
  </div>
 

	
 
  <div class="my-4">
 
    <h2>Voucher</h2>
 
    <p>If you have been given a voucher, please <a id="voucher-form-button" href="{% url "voucher_code" %}">enter your voucher code</a> now.
 
    </p>
 
  </div>
 

	
 

	
 
  <div class="my-4">
 
    <h2>What next?</h2>
 
    {% if pending %}
 
    <p>You can either check out an invoice and pay for your selections, or return to
 
        the dashboard.</p>
 

	
 
    <a class="btn btn-primary" href="{% url "checkout" %}">
 
      <i class="fa fa-credit-card"></i> Check out and pay
 
    </a>
 

	
 
    <a class="btn btn-light" href="{% url "dashboard" %}">Return to dashboard</a>
 

	
 
    {% else %}
 

	
 
    <p>You have no items that need to be paid.</p>
 

	
 
    <div class="form-actions">
 
      <a class="btn btn-light" href="{% url "dashboard" %}">Return to dashboard</a>
 
    </div>
 

	
 
    {% endif %}
 
  </div>
 

	
 

	
 

	
 
{% endblock %}
pinaxcon/templates/registrasion/stripe/credit_card_payment.html
Show inline comments
 
{% extends "registrasion/base.html" %}
 
{% load registrasion_tags %}
 
{% load lca2018_tags %}
 

	
 

	
 
{% block scripts %}
 
  {{ block.super }}
 

	
 
  {{ form.media }}
 

	
 
  <script type="text/javascript">
 
    $(function() {
 
      var $form = $('#payment-form');
 
      $form.submit(function(event) {
 

	
 
        if ($form.find("input[name='stripe_token']").length) {
 
          // If we've added the stripe token, then we're good to go.
 
          return true;
 
        }
 

	
 
        // Disable the submit button to prevent repeated clicks:
 

	
 
        $form.find('input[type=submit]').prop('disabled', true);
 

	
 
        console.log($form.number);
 

	
 
        // Request a token from Stripe:
 
        Stripe.card.createToken($form, stripeResponseHandler);
 

	
 
        // Prevent the form from being submitted:
 
        return false;
 
      });
 
    });
 

	
 
    function stripeResponseHandler(status, response) {
 
      // Grab the form:
 
      var $form = $('#payment-form');
 
      var $submit = $form.find('input[type=submit]')
 
      if (response.error) { // Problem!
 
        console.log(response.error.message);
 

	
 
        // Show the errors on the form:
 
        $form.find('#payment-errors').text(response.error.message);
 
        $form.find('#payment-errors-outer').show();
 
        $submit.prop('disabled', false); // Re-enable submission
 

	
 
      } else { // Token was created!
 
        console.log(response);
 

	
 
        // Get the token ID:
 
        var token = response.id;
 

	
 
        // Insert the token ID into the form so it gets submitted to the server:
 
        $form = $form.append($('<input type="hidden" name="stripe_token" />').val(token));
 

	
 
        // Submit the form:
 

	
 
        $form.get(0).submit();
 
        $form.append($('<p>').text("Processing your payment. Please do not refresh."));
 
      }
 
    };
 
  </script>
 

	
 
{% endblock %}
 

	
 
{% block header_title %}Credit card payment for invoice #{{ invoice.id}}{% endblock %}
 
{% block page_title %}Credit card payment for invoice #{{ invoice.id}}{% endblock %}
 

	
 

	
 
{% block proposals_body %}
 
<p>Pay for your linux.conf.au attendance with your Visa, Mastercard, or American Express credit or debit card. Card payments are processed by <a href="https://stripe.com">Stripe</a>.</p>
 

	
 
  <p>
 
    No data on this form is retained by {% conference_name %}, rather it is
 
    sent to Stripe. In particular, credit card details are not sent
 
    to linux.conf.au. You must allow JavaScript from <code>js.stripe.com</code> and <code>stripe.network</code> to complete payment.
 
  </p>
 

	
 
  <p>You have <strong>${{ invoice.balance_due }}</strong> remaining to pay on this invoice.</p>
 

	
 
  <p></p>
 

	
 
  <h3>Card details</h3>
 

	
 
  <form class="form-horizontal" id="payment-form" method="post">
 
    <fieldset>
 

	
 
      <div class="has-errors" id="payment-errors-outer" style="display: none;">
 
        <span class="errorlist" id="payment-errors"></span>
 
      </div>
 

	
 
      {% csrf_token %}
 
      {% include "_form_snippet.html" with form=form %}
 
      <br />
 
      <input id="pay" class="btn btn-primary" type="submit" value="Pay ${{ invoice.balance_due }}" />
 
    </fieldset>
 
  </form>
 
{% endblock %}
pinaxcon/templates/site_base.html
Show inline comments
 
{% load staticfiles %}
 
{% load i18n %}
 
{% load sitetree %}
 
{% load sass_tags %}
 

	
 
<!DOCTYPE html>
 

	
 
<html lang="en">
 
<head>
 
  <meta charset="utf-8">
 
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
 
  <meta name="description" content="">
 
  <meta name="viewport" content="width=device-width, initial-scale=1">
 

	
 
  <title>{% block head_title_base %}{% if SITE_NAME %}{{ SITE_NAME }} | {% endif %}{% block head_title %}{% endblock %}{% endblock %}</title>
 

	
 
  <meta property="og:type" content="website" />
 
  <meta property="og:title" content="linux.conf.au 2020" />
 
  <meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{% static 'assets/social.png' %}" />
 
  <meta property="og:image:alt" content="linux.conf.au 2020 - Jan 13-17 2020, Gold Coast, Australia" />
 

	
 
  {% block styles %}
 
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.3/css/all.css" integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/" crossorigin="anonymous">
 
  <link href="{% sass_src 'scss/app.scss' %}" rel="stylesheet" type="text/css" />
 
  {% block extra_style %}{% endblock %}
 
  {% endblock %}
 

	
 
  {% block extra_head_base %}
 
  {% block extra_head %}{% endblock %}
 
  {% endblock %}
 
</head>
 
<body class="{% block body_class %}{% endblock %}">
 
  {% block template_overrides %}{% endblock %}
 
  <header class="clearfix hidden-print">
 
  <header class="clearfix d-print-none">
 
    {% block alert %}{% endblock %}
 
    {% block navbar %}{% include 'nav.html' %}{% endblock %}
 
  </header>
 

	
 
  {% if messages %}
 
  <div class="container my-5 alert alert-primary">
 
    <ul class="messagelist hidden-print list-unstyled">
 
    <ul class="messagelist d-print-none list-unstyled">
 
      {% for message in messages %}
 
      <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
 
      {% endfor %}
 
    </ul>
 
  </div>
 
  {% endif %}
 

	
 
  <main role="main" class="{% block main_class %}container{% endblock %}">
 
    <h1 class="page-title mb-5">{% block page_title %}{% endblock %}</h1>
 
    <div class="page-header mb-5">
 
      <h1 class="page-title">{% block page_title %}{% endblock %}</h1>
 
      <p class="lead">{% block page_lead %}{% endblock %}</p>
 
    </div>
 

	
 
    {% block body_base %}
 
    {% block body_out %}
 

	
 
    <div class="row">
 
      <div class="col-md-12">
 
        {% block body_outer %}
 
        {% endblock %}
 
      </div>
 
    </div>
 
    {% block content %}
 
    {% endblock %}
 
    {% endblock %}
 
    {% endblock %}
 

	
 
    {% block footer_base %}
 
    {% block footer %}
 
    {% endblock %}
 
    {% endblock %}
 

	
 
    {% block scripts %}
 
    <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
 
    <script src="{% static 'js/app.js' %}" type="text/javascript"></script>
 
    <script src="{% static 'js/jquery.formset.js' %}"></script>
 
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
 
    <script src="{% static 'bootstrap/js/bootstrap.bundle.min.js' %}"></script>
 
    {% if settings.ANALYTICS_KEY %}
 
    <!-- Global site tag (gtag.js) - Google Analytics -->
 
    <script async src="https://www.googletagmanager.com/gtag/js?id={{ settings.ANALYTICS_KEY }}"></script>
 
    <script>
 
      window.dataLayer = window.dataLayer || [];
 
      function gtag(){dataLayer.push(arguments);}
 
      gtag('js', new Date());
 

	
 
      gtag('config', '{{ settings.ANALYTICS_KEY }}');
 
    </script>
 
    {% else %}
 
    <!--no-analytics-->
 
    {% endif %}
 
    {% block extra_script %}
 
    {% endblock %}
 
    {% block scripts_extra %}{% endblock %}
 
    {% endblock %}
 

	
 
    {% block extra_body_base %}
 
    {% block extra_body %}
 
    {% endblock %}
 
    {% endblock %}
 
  </main>
 

	
 
  <footer class="footer mt-4">
 
  <footer class="footer mt-4 d-print-none">
 
    <div class="container py-4">
 
      <div class="row">
 
        <div class="col-md-4 pb-4">
 
          <strong>linux.conf.au 2020</strong> <br>
 
          Jan 13-17 2020 <br>
 
          Gold Coast, Australia <br>
 
          <a href="mailto:contact@lca2020.linux.org.au" alt="Email"><i class="far fa-envelope"></i></a>&nbsp;&nbsp;
 
          <a href="https://twitter.com/linuxconfau" alt="Twitter"><i class="fab fa-twitter"></i></a>&nbsp;&nbsp;
 
          <a href="https://www.facebook.com/linuxconferenceaustralia/" alt="Facebook"><i class="fab fa-facebook"></i></a>
 
        </div>
 
        <div class="col-md-4 pb-4 text-center">
 
          <img src="{% static 'lca/lca_horiz_colour.svg' %}" alt="linux.conf.au logo" class="footer-logo">
 
          <a href="https://linux.org.au"><img src="{% static 'lca/la_logo.svg' %}" alt="Linux Australia logo" class="footer-image"></a>
 
        </div>
 
        <div class="col-md-4 pb-4 text-right">
 
          <small>
 
            <a href="#">Back to top</a><br>
 
            &copy; 2019 linux.conf.au and <a href="http://linux.org.au/">Linux Australia</a><br>
 
            Linux is a registered trademark of Linus Torvalds <br>
 
            <a href="/colophon/">Colophon</a>
 
          </small>
 
        </div>
 
      </div>
 
    </div>
 
  </footer>
 

	
 
</body>
 
</html>
pinaxcon/templates/symposion/dashboard/_categories.html
Show inline comments
 
{% load i18n %}
 
{% load proposal_tags %}
 
{% load review_tags %}
 
{% load teams_tags %}
 
{% load registrasion_tags %}
 
{% load lca2018_tags %}
 
{% load lca2019_tags %}
 
{% load staticfiles %}
 

	
 
{% if user.is_staff %}
 
<div class="mb-4">
 
  <div class="row">
 
    <div class="col-12">
 
      <h2>{% trans "Administration" %}</h2>
 
      <p>The following administrative tools are available to you:
 
        <ul class="list-unstyled">
 
          <li><a href="{% url "reports_list" %}">Reports</a></li>
 
        </ul>
 
      </p>
 
    </div>
 
  </div>
 
</div>
 
{% endif %}
 

	
 
<div class="mb-4">
 
  <div class="row">
 
    <div class="col-12">
 
      <h2>{% trans "Attend" %} {% conference_name %}</h2>
 
    </div>
 
  </div>
 
</div>
 

	
 
{% if not user.attendee.completed_registration %}
 
<div class="mb-4">
 
  <div class="row">
 
    {% if not user.attendee.completed_registration %}
 
    <div class="col-12">
 
      <h3>Register</h3>
 
      <p>To attend the conference, you must create an attendee profile and purchase your ticket</p>
 
      <div class="mt-auto">
 
        <a class="btn btn-primary" role="button" href="{% url "guided_registration" %}">Get your ticket</a>
 
        <a class="btn btn-lg btn-primary" role="button" href="{% url "guided_registration" %}">Get your ticket</a>
 
      </div>
 
    </div>
 
    </div>
 
  </div>
 
</div>
 
    {% else %}
 
{% else %}
 
<div class="mb-4">
 
  <div class="row">
 
    <div class="col-md-6 my-3 d-flex flex-column">
 
      <h3>Attendee Profile</h3>
 
      <p>If you would like to change the details on your badge or your attendee statistics, you may edit your attendee profile here.</p>
 
      <div class="mt-auto">
 
        <a class="btn btn-lg btn-primary" role="button" href="{% url "attendee_edit" %}">Edit attendee profile</a>
 
      </div>
 
    </div>
 
    <div class="col-md-6 my-3 d-flex flex-column">
 
      <h3>Account Management</h3>
 
      <p>If you would like to change your registered email address or password, you can use our self-service account management portal</p>
 
      <div class="mt-auto">
 
        <a class="btn btn-lg btn-primary" role="button" href="https://login.linux.conf.au/manage/">Account Management</a>
 
      </div>
 
    </div>
 
  </div>
 
</div>
 

	
 
<div class="my-4 py-4">
 
  <div class="row">
 
    {% items_pending as pending %}
 

	
 
    <div class="col-12">
 
      <h2>Account</h2>
 
    </div>
 

	
 
    {% if pending %}
 
    <div class="col-6 my-3 d-flex flex-column">
 
      <h4>Items pending payment</h4>
 
      {% include "registrasion/_items_list.html" with items=pending %}
 
      <a class="btn btn-lg btn-primary" role="button" href="{% url "checkout" %}">Check out and pay</a>
 
    </div>
 
    {% endif %}
 

	
 
    {% items_purchased as purchased %}
 
    {% if purchased %}
 
    <div class="col-md-6 my-3 d-flex flex-column">
 
      <h4>Paid Items</h4>
 
      {% include "registrasion/_items_list.html" with items=purchased %}
 
    </div>
 
    {% endif %}
 

	
 
    <div class="col-md-6 my-3 d-flex flex-column">
 
      <h4>Add/Update Items</h4>
 
      {% include "registrasion/_category_list.html" with categories=categories %}
 
    </div>
 

	
 
    {% invoices as invoices %}
 
    {% if invoices %}
 
    <div class="col-md-6 my-3 d-flex flex-column">
 
      <h4>Invoices</h4>
 
      <ul>
 
        {% for invoice in invoices %}
 

	
 
        <li{% if invoice.is_void %} class="void-invoice" style="display: none;"{% endif %}>
 
          <a href="{% url "invoice" invoice.id %}" >Invoice {{ invoice.id }}</a> - ${{ invoice.value }} ({{ invoice.get_status_display }})
 
        </li>
 
        {% endfor %}
 
      </ul>
 
      {% if invoices|any_is_void %}
 
      <div class="mt-auto">
 
        <button id="toggle-void-invoices" onclick="toggleVoidInvoices();" class="btn btn-lg btn-default">Show void invoices</button>
 
      </div>
 
      {% endif %}
 
    </div>
 
    {% endif %}
 

	
 
    {% if false %}
 
    <div class="col-md-6 my-3 d-flex flex-column">
 
      <h4>Raffle Tickets</h4>
 

	
 
      <p><a href="/raffle/tickets/">View all my raffle tickets</a></p>
 
      {# REMOVE HARDCODED CATEGORY NUMBER!!!! #}
 
      <p><a href="/tickets/category/8">Buy raffle tickets</a></p>
 
    </div>
 
    {% endif %}
 

	
 
    {% available_credit as credit %}
 
    {% if credit %}
 
    <div class="col-md-6 my-3 d-flex flex-column">
 
      <h4>Credit</h4>
 
      <p>You have ${{ credit }} leftover from refunded invoices. This credit will be automatically applied to new invoices. Contact the conference organisers to for a refund to your original payment source.</p>
 
    </div>
 
    {% endif %}
 
  </div>
 
</div>
 
{% endif %} {# user.attendee.completed_registration #}
pinaxcon/templates/utility_page.html
Show inline comments
 
{% extends "content_page.html" %}
 
{% load staticfiles %}
 

	
 
{% block header_title %}{% block page_title %}{% endblock %}{% endblock %}
 
{% block head_title %}{% block page_title %}{% endblock %}{% endblock %}
 

	
 
{% block content_base %}
 
  {% block utility_body_outer %}
 
    <div class="l-content-page">
 
      <div class="l-content-page--richtext">
 
        {% block content %}
 
          <div class="jumbotron">
 
            {% block utility_body %}
 
            {% endblock %}
 
          </div>
 
        {% endblock content %}
 
      </div>
 
    </div>
 
  {% endblock %}
 
{% endblock %}
pinaxcon/templatetags/lca2019_tags.py
Show inline comments
 
from django import template
 
from django.forms import Form
 
import re
 

	
 

	
 
register = template.Library()
 

	
 

	
 
@register.filter
 
def has_required_fields(form):
 
    for field in form:
 
        if isinstance(field, Form):
 
            if has_required_fields(field):
 
                return True
 
        if field.field.required:
 
            return True
 
    return False
 

	
 

	
 
@register.filter
 
def has_price_fields(form):
 
    for field in form:
 
        if isinstance(field, Form):
 
            return has_price_fields(field)
 

	
 
        if '$' in field.field.help_text:
 
            return True
 

	
 
        if '$' in field.field.label:
 
            return True
 

	
 
        choices = getattr(field.field, 'choices', [])
 
        if choices:
 
            for choice_id, choice_text in choices:
 
                if '$' in choice_text:
 
                    return True
 

	
 
    return False
 

	
 

	
 
@register.filter
 
def any_is_void(invoices):
 
    for invoice in invoices:
 
        if invoice.is_void:
 
            return True
 
    return False
 

	
 

	
 
@register.simple_tag
 
def listlookup(lookup, target):
 
    try:
 
        return lookup[target]
 
    except IndexError:
 
        return ''
 

	
 

	
 
@register.filter
 
def clean_text(txt):
 
    # Remove double/triple/+ spaces from `txt` and replace with single space
 
    return re.sub(r' {2,}' , ' ', txt)
 

	
 
@register.filter
 
def twitter_handle(txt):
 
    # Add @ to twitter handle if not present
 
    return txt if txt.startswith('@') else '@{}'.format(txt)
...
 
\ No newline at end of file
 
    return txt if txt.startswith('@') else '@{}'.format(txt)
static/src/scss/app.scss
Show inline comments
 
@import url('https://fonts.googleapis.com/css?family=Montserrat:300|Source+Sans+Pro:400,700&display=swap&subset=latin-ext');
 
@import "open-iconic-bootstrap.scss";
 
@import "bootstrap.scss";
 

	
 
/* START LCA2020 */
 

	
 
body {
 
   font-family: 'Source Sans Pro', sans-serif;
 
}
 

	
 
h1, .h1,
 
h2, .h2,
 
h3, .h3 {
 
    font-family: 'Montserrat', sans-serif;
 
    font-weight: 300;
 
}
 

	
 
.bg-dawn-sea {
 
    background-color: rgb(0, 177, 197);
 
}
 

	
 
.bg-blaze {
 
    background-color: rgb(250, 166, 26);
 
}
 

	
 
.bg-dusk-sea {
 
    background-color: rgb(0, 113, 144);
 
}
 

	
 
.bg-noon-sea {
 
    background-color: rgb(0, 141, 162);
 
}
 

	
 
.banner-subscribe {
 
    color: #fff;
 
}
 

	
 
.page-title {
 
    font-size: 3rem;
 
    line-height: 5rem;
 
}
 

	
 
.navbar-brand {
 
    margin-top: 0.4rem;
 
    margin-bottom: 0.4rem;
 
}
 

	
 
.navbar-nav .nav-link {
 
    font-size: 1.2rem;
 
}
 

	
 
.footer {
 
    border-top: 1px rgb(0, 177, 197) solid;
 
}
 

	
 
.footer-logo {
 
    height: 70px;
 
}
 

	
 
.footer-image {
 
    height: 50px;
 
}
 

	
 
.rego-content {
 
    border: 1px solid #000;
 
    background-color: #fff;
 
}
 

	
 
/* END LCA2020 */
 

	
 
.messagelist {
 
    margin-bottom: 0;
 
}
 

	
 
.asteriskField:before { content: ' '; }
 

	
 
label.label-required:after { content: ' *'; }
 

	
 
.abstract, .bio, .monospace-text {
 
    font-family: Hack, monospace;
 
    white-space: pre-wrap;
 
}
...
 
\ No newline at end of file
 
}
vendor/registrasion/registrasion/views.py
Show inline comments
 
from collections import defaultdict
 
import datetime
 
import zipfile
 
import os
 
import logging
 
import subprocess
 

	
 
from django.contrib import messages
 

	
 
from . import forms
 
from . import util
 
from .models import commerce
 
from .models import inventory
 
from .models import people
 
from .controllers.batch import BatchController
 
from .controllers.cart import CartController
 
from .controllers.category import CategoryController
 
from .controllers.credit_note import CreditNoteController
 
from .controllers.discount import DiscountController
 
from .controllers.invoice import InvoiceController
 
from .controllers.item import ItemController
 
from .controllers.product import ProductController
 
from .exceptions import CartValidationError
 

	
 
from collections import namedtuple
 

	
 
from django import forms as django_forms
 
from django.conf import settings
 
from django.contrib.auth.decorators import login_required
 
from django.contrib.auth.decorators import user_passes_test
 
from django.contrib.auth.models import User
 
from django.contrib import messages
 
from django.core.exceptions import ObjectDoesNotExist
 
from django.core.exceptions import ValidationError
 
from django.core.mail import send_mass_mail
 
from django.http import Http404, HttpResponse
 
from django.shortcuts import redirect
 
from django.shortcuts import render
 
from django.template import Context, Template, loader
 
from django.urls import reverse
 
import waffle
 

	
 
from lxml import etree
 
from copy import deepcopy
 

	
 
from registrasion.forms import BadgeForm, ticket_selection
 
from registrasion.contrib.badger import (
 
                                         collate,
 
                                         svg_badge,
 
                                         InvalidTicketChoiceError
 
                                         )
 

	
 
_GuidedRegistrationSection = namedtuple(
 
    "GuidedRegistrationSection",
 
    (
 
        "title",
 
        "discounts",
 
        "description",
 
        "form",
 
    )
 
)
 

	
 
@util.all_arguments_optional
 
class GuidedRegistrationSection(_GuidedRegistrationSection):
 
    ''' Represents a section of a guided registration page.
 

	
 
    Attributes:
 
       title (str): The title of the section.
 

	
 
       discounts ([registrasion.contollers.discount.DiscountAndQuantity, ...]):
 
            A list of discount objects that are available in the section. You
 
            can display ``.clause`` to show what the discount applies to, and
 
            ``.quantity`` to display the number of times that discount can be
 
            applied.
 

	
 
       description (str): A description of the section.
 

	
 
       form (forms.Form): A form to display.
 
    '''
 
    pass
 

	
 

	
 
@login_required
 
def guided_registration(request, page_number=None):
 
    ''' Goes through the registration process in order, making sure user sees
 
    all valid categories.
 

	
 
    The user must be logged in to see this view.
 

	
 
    Parameter:
 
        page_number:
 
            1) Profile form (and e-mail address?)
 
            2) Ticket type
 
            3) T&C Consent
 
            4) Remaining products
 
            5) Mark registration as complete
 

	
 
    Returns:
 
        render: Renders ``registrasion/guided_registration.html``,
 
            with the following data::
 

	
 
                {
 
                    "previous_step": int(),  # Previous step
 
                    "current_step": int(),  # The current step in the
 
                                            # registration
 
                    "sections": sections,   # A list of
 
                                            # GuidedRegistrationSections
 
                    "title": str(),         # The title of the page
 
                    "total_steps": int(),   # The total number of steps
 
                }
 

	
 
    '''
 

	
 
    PAGE_PROFILE = 1
 
    PAGE_TICKET = 2
 
    PAGE_TERMS = 3
 
    PAGE_PRODUCTS = 4
 
    PAGE_PRODUCTS_MAX = 5
 
    TOTAL_PAGES = 5
 

	
 
    ticket_category = inventory.Category.objects.get(
 
        id=settings.TICKET_PRODUCT_CATEGORY
 
    )
 
    cart = CartController.for_user(request.user)
 

	
 
    attendee = people.Attendee.get_instance(request.user)
 

	
 
    # This guided registration process is only for people who have
 
    # not completed registration (and has confusing behaviour if you go
 
    # back to it.)
 
    if attendee.completed_registration:
 
        return redirect(review)
 

	
 
    # Calculate the current maximum page number for this user.
 
    has_profile = hasattr(attendee, "attendeeprofilebase")
 
    if not has_profile:
 
        # If there's no profile, they have to go to the profile page.
 
        max_page = PAGE_PROFILE
 
        redirect_page = PAGE_PROFILE
 
    else:
 
        # We have a profile.
 
        # Do they have a ticket?
 
        products = inventory.Product.objects.filter(
 
            productitem__cart=cart.cart
 
        )
 
        tickets = products.filter(category=ticket_category)
 

	
 
        if tickets.count() == 0:
 
            # If no ticket, they can only see the profile or ticket page.
 
            max_page = PAGE_TICKET
 
            redirect_page = PAGE_TICKET
 
        elif products.count() == 1:
 
            # They have a ticket, now to accept terms and conditions
 
            max_page = PAGE_TERMS
 
            redirect_page = PAGE_TERMS
 
        else:
 
            # If there's a ticket, they should *see* the general products page#
 
            # but be able to go to the overflow page if needs be.
 
            max_page = PAGE_PRODUCTS_MAX
 
            redirect_page = PAGE_PRODUCTS
 

	
 
    if page_number is None or int(page_number) > max_page:
 
        return redirect("guided_registration", redirect_page)
 

	
 
    page_number = int(page_number)
 

	
 
    prev_step = page_number - 1
 

	
 
    next_step = redirect("guided_registration", page_number + 1)
 

	
 
    with BatchController.batch(request.user):
 

	
 
        # This view doesn't work if the conference has sold out.
 
        available = ProductController.available_products(
 
            request.user, category=ticket_category
 
        )
 
        if not available:
 
            messages.error(request, "There are no more tickets available.")
 
            return redirect("dashboard")
 

	
 
        sections = []
 

	
 
        # Build up the list of sections
 
        if page_number == PAGE_PROFILE:
 
            # Profile bit
 
            title = "Attendee information"
 
            sections = _guided_registration_profile_and_voucher(request)
 
        elif page_number == PAGE_TICKET:
 
            # Select ticket
 
            title = "Select ticket type"
 
            sections = _guided_registration_products(
 
                request, GUIDED_MODE_TICKETS_ONLY
 
            )
 
        elif page_number == PAGE_TERMS:
 
            # Accept terms
 
            title = "Terms and Conditions"
 
            sections = _guided_registration_products(
 
                request, GUIDED_MODE_TERMS
 
            )
 
        elif page_number == PAGE_PRODUCTS:
 
            # Select additional items
 
            title = "Additional items"
 
            sections = _guided_registration_products(
 
                request, GUIDED_MODE_ALL_ADDITIONAL
 
            )
 
        elif page_number == PAGE_PRODUCTS_MAX:
 
            # Items enabled by things on page 3 -- only shows things
 
            # that have not been marked as complete.
 
            title = "More additional items"
 
            sections = _guided_registration_products(
 
                request, GUIDED_MODE_EXCLUDE_COMPLETE
 
            )
 

	
 
        if not sections:
 
            # We've filled in every category
 
            attendee.completed_registration = True
 
            attendee.save()
 
            return redirect("review")
 

	
 
        if sections and request.method == "POST":
 
            for section in sections:
 
                if section.form.errors:
 
                    break
 
            else:
 
                # We've successfully processed everything
 
                return next_step
 

	
 
    data = {
 
        "previous_step": prev_step,
 
        "current_step": page_number,
 
        "sections": sections,
 
        "title": title,
 
        "total_steps": TOTAL_PAGES,
 
    }
 
    return render(request, "registrasion/guided_registration.html", data)
 

	
 

	
 
GUIDED_MODE_TICKETS_ONLY = 2
 
GUIDED_MODE_TERMS = 3
 
GUIDED_MODE_ALL_ADDITIONAL = 4
 
GUIDED_MODE_EXCLUDE_COMPLETE =5
 

	
 

	
 
@login_required
 
def _guided_registration_products(request, mode):
 
    sections = []
 

	
 
    SESSION_KEY = "guided_registration"
 
    MODE_KEY = "mode"
 
    CATS_KEY = "cats"
 

	
 
    attendee = people.Attendee.get_instance(request.user)
 

	
 
    # Get the next category
 
    cats = inventory.Category.objects.order_by("order")  # TODO: default order?
 

	
 
    # Fun story: If _any_ of the category forms result in an error, but other
 
    # new products get enabled with a flag, those new products will appear.
 
    # We need to make sure that we only display the products that were valid
 
    # in the first place. So we track them in a session, and refresh only if
 
    # the page number does not change. Cheap!
 

	
 
    if SESSION_KEY in request.session:
 
        session_struct = request.session[SESSION_KEY]
 
        old_mode = session_struct[MODE_KEY]
 
        old_cats = session_struct[CATS_KEY]
 
    else:
 
        old_mode = None
 
        old_cats = []
 

	
 
    if mode == old_mode:
 
        cats = cats.filter(id__in=old_cats)
 
    elif mode == GUIDED_MODE_TICKETS_ONLY:
 
        cats = cats.filter(id=settings.TICKET_PRODUCT_CATEGORY)
 
    elif mode == GUIDED_MODE_TERMS:
 
        cats = cats.filter(id=settings.TERMS_PRODUCT_CATEGORY)
 
    elif mode == GUIDED_MODE_ALL_ADDITIONAL:
 
        cats = cats.exclude(id=settings.TICKET_PRODUCT_CATEGORY)
 
        cats = cats.exclude(id=settings.TERMS_PRODUCT_CATEGORY)
 
    elif mode == GUIDED_MODE_EXCLUDE_COMPLETE:
 
        cats = cats.exclude(id=settings.TICKET_PRODUCT_CATEGORY)
 
        cats = cats.exclude(id=settings.TERMS_PRODUCT_CATEGORY)
 
        cats = cats.exclude(id__in=old_cats)
 

	
 
    # We update the session key at the end of this method
 
    # once we've found all the categories that have available products
 

	
 
    all_products = inventory.Product.objects.filter(
 
        category__in=cats,
 
    ).select_related("category")
 

	
 
    seen_categories = []
 

	
 
    with BatchController.batch(request.user):
 
        available_products = ProductController.available_products(
 
            request.user,
 
            products=all_products,
 
        )
 

	
 
        if len(available_products) == 0:
 
            return []
 

	
 
        available_by_category = defaultdict(list)
 
        for product in available_products:
 
            available_by_category[product.category].append(product)
 

	
 
        has_errors = False
 

	
 
        for category in cats:
 
            products = available_by_category[category]
 

	
 
            prefix = "category_" + str(category.id)
 
            p = _handle_products(request, category, products, prefix)
 
            products_form, discounts, products_handled = p
 

	
 
            section = GuidedRegistrationSection(
 
                title=category.name,
 
                description=category.description,
 
                discounts=discounts,
 
                form=products_form,
 
            )
 

	
 
            if products:
 
                # This product category has items to show.
 
                sections.append(section)
 
                seen_categories.append(category)
 

	
 
    # Update the cache with the newly calculated values
 
    cat_ids = [cat.id for cat in seen_categories]
 
    request.session[SESSION_KEY] = {MODE_KEY: mode, CATS_KEY: cat_ids}
 

	
 
    return sections
 

	
 

	
 
@login_required
 
def _guided_registration_profile_and_voucher(request):
 
    voucher_form, voucher_handled = _handle_voucher(request, "voucher")
 
    profile_form, profile_handled = _handle_profile(request, "profile")
 

	
 
    voucher_section = GuidedRegistrationSection(
 
        title="Voucher Code",
 
        form=voucher_form,
 
    )
 

	
 
    profile_section = GuidedRegistrationSection(
 
        title="Profile and Personal Information",
 
        form=profile_form,
 
        description=("<div class=\"text-info\"><em>You can come back and edit these details any time before January 15, "
 
                     "2018.</em></div>"),
 
        description=("<div class=\"text-info\"><em>You can come back and edit these details any time before "
 
                     "January 6 2020.</em></div>"),
 
    )
 

	
 
    return [voucher_section, profile_section]
 

	
 

	
 
@login_required
 
def review(request):
 
    ''' View for the review page. '''
 

	
 
    return render(
 
        request,
 
        "registrasion/review.html",
 
        {},
 
    )
 

	
 

	
 
@login_required
 
def edit_profile(request):
 
    ''' View for editing an attendee's profile
 

	
 
    The user must be logged in to edit their profile.
 

	
 
    Returns:
 
        redirect or render:
 
            In the case of a ``POST`` request, it'll redirect to ``dashboard``,
 
            or otherwise, it will render ``registrasion/profile_form.html``
 
            with data::
 

	
 
                {
 
                    "form": form,  # Instance of ATTENDEE_PROFILE_FORM.
 
                }
 

	
 
    '''
 

	
 
    if hasattr(request.user, "checkin"):
 
        if request.user.checkin.checked_in_bool:
 
            messages.add_message(
 
                request, messages.ERROR,
 
                'Profile cannot be edited. Please email contact@lca2020.linux.org.au '
 
                'if you need any changes to your profile.')
 
            return redirect(reverse('dashboard'))
 

	
 
    form, handled = _handle_profile(request, "profile")
 

	
 
    if handled and not form.errors:
 
        messages.success(
 
            request,
 
            "Your attendee profile was updated.",
 
        )
 
        return redirect("dashboard")
 

	
 
    data = {
 
        "form": form,
 
    }
 
    return render(request, "registrasion/profile_form.html", data)
 

	
 

	
 
# Define the attendee profile form, or get a default.
 
try:
 
    ProfileForm = util.get_object_from_name(settings.ATTENDEE_PROFILE_FORM)
 
except:
 
    class ProfileForm(django_forms.ModelForm):
 
        class Meta:
 
            model = util.get_object_from_name(settings.ATTENDEE_PROFILE_MODEL)
 
            exclude = ["attendee"]
 

	
 

	
 
def _handle_profile(request, prefix):
 
    ''' Returns a profile form instance, and a boolean which is true if the
 
    form was handled. '''
 
    attendee = people.Attendee.get_instance(request.user)
 

	
 
    try:
 
        profile = attendee.attendeeprofilebase
 
        profile = people.AttendeeProfileBase.objects.get_subclass(
 
            pk=profile.id,
 
        )
 
    except ObjectDoesNotExist:
 
        profile = None
 

	
 
    # Load a pre-entered name from the speaker's profile,
 
    # if they have one.
 
    try:
 
        speaker_profile = request.user.speaker_profile
 
        speaker_name = speaker_profile.name
 
    except ObjectDoesNotExist:
 
        speaker_name = None
 

	
 
    name_field = ProfileForm.Meta.model.name_field()
 
    initial = {}
 
    if profile is None and name_field is not None:
 
        initial[name_field] = speaker_name
 

	
 
    form = ProfileForm(
 
        request.POST or None,
 
        initial=initial,
 
        instance=profile,
 
        prefix=prefix
 
    )
 

	
 
    handled = True if request.POST else False
 

	
 
    if request.POST and form.is_valid():
 
        form.instance.attendee = attendee
 
        form.save()
 

	
 
    return form, handled
 

	
 

	
 
@login_required
 
def product_category(request, category_id):
 
    ''' Form for selecting products from an individual product category.
 

	
 
    Arguments:
 
        category_id (castable to int): The id of the category to display.
 

	
 
    Returns:
 
        redirect or render:
 
            If the form has been sucessfully submitted, redirect to
 
            ``dashboard``. Otherwise, render
 
            ``registrasion/product_category.html`` with data::
 

	
 
                {
 
                    "category": category,         # An inventory.Category for
 
                                                  # category_id
 
                    "discounts": discounts,       # A list of
 
                                                  # DiscountAndQuantity
 
                    "form": products_form,        # A form for selecting
 
                                                  # products
 
                    "voucher_form": voucher_form, # A form for entering a
 
                                                  # voucher code
 
                }
 

	
 
    '''
 

	
 
    PRODUCTS_FORM_PREFIX = "products"
 
    VOUCHERS_FORM_PREFIX = "vouchers"
 

	
 
    # Handle the voucher form *before* listing products.
 
    # Products can change as vouchers are entered.
 
    v = _handle_voucher(request, VOUCHERS_FORM_PREFIX)
 
    voucher_form, voucher_handled = v
 

	
 
    category_id = int(category_id)  # Routing is [0-9]+
 
    category = inventory.Category.objects.get(pk=category_id)
 

	
 
    with BatchController.batch(request.user):
 
        products = ProductController.available_products(
 
            request.user,
 
            category=category,
 
        )
 

	
 
        if not products:
 
            messages.warning(
 
                request,
 
                (
 
                    "There are no products available from category: " +
 
                    category.name
 
                ),
 
            )
 
            return redirect("dashboard")
 

	
 
        p = _handle_products(request, category, products, PRODUCTS_FORM_PREFIX)
 
        products_form, discounts, products_handled = p
 

	
 
    if request.POST and not voucher_handled and not products_form.errors:
 
        # Only return to the dashboard if we didn't add a voucher code
 
        # and if there's no errors in the products form
 
        if products_form.has_changed():
 
            messages.success(
 
                request,
 
                "Your reservations have been updated.",
 
            )
 
        return redirect(review)
 

	
 
    data = {
 
        "category": category,
 
        "discounts": discounts,
 
        "form": products_form,
 
        "voucher_form": voucher_form,
 
    }
 

	
 
    return render(request, "registrasion/product_category.html", data)
 

	
 

	
 
def voucher_code(request):
 
    ''' A view *just* for entering a voucher form. '''
 

	
 
    VOUCHERS_FORM_PREFIX = "vouchers"
 

	
 
    # Handle the voucher form *before* listing products.
 
    # Products can change as vouchers are entered.
 
    v = _handle_voucher(request, VOUCHERS_FORM_PREFIX)
 
    voucher_form, voucher_handled = v
 

	
 
    if voucher_handled:
 
        messages.success(request, "Your voucher code was accepted.")
 
        return redirect("dashboard")
 

	
 
    data = {
 
        "voucher_form": voucher_form,
 
    }
 

	
 
    return render(request, "registrasion/voucher_code.html", data)
 

	
 

	
 

	
 
def _handle_products(request, category, products, prefix):
 
    ''' Handles a products list form in the given request. Returns the
 
    form instance, the discounts applicable to this form, and whether the
 
    contents were handled. '''
 

	
 
    current_cart = CartController.for_user(request.user)
 

	
 
    ProductsForm = forms.ProductsForm(category, products)
 

	
 
    # Create initial data for each of products in category
 
    items = commerce.ProductItem.objects.filter(
 
        product__in=products,
 
        cart=current_cart.cart,
 
    ).select_related("product")
 
    quantities = []
 
    seen = set()
 

	
 
    for item in items:
 
        quantities.append((item.product, item.quantity))
 
        seen.add(item.product)
 

	
 
    zeros = set(products) - seen
 
    for product in zeros:
 
        quantities.append((product, 0))
 

	
 
    products_form = ProductsForm(
 
        request.POST or None,
 
        product_quantities=quantities,
 
        prefix=prefix,
 
    )
 

	
 
    if request.method == "POST" and products_form.is_valid():
 
        if products_form.has_changed():
 
            _set_quantities_from_products_form(products_form, current_cart)
 

	
 
        # If category is required, the user must have at least one
 
        # in an active+valid cart
 
        if category.required:
 
            carts = commerce.Cart.objects.filter(user=request.user,
 
                                                 status=commerce.Cart.STATUS_ACTIVE)
 
            items = commerce.ProductItem.objects.filter(
 
                product__category=category,
 
                cart=current_cart.cart,
 
            )
 

	
 
            if len(items) == 0:
 
                products_form.add_error(
 
                    None,
 
                    "You must have at least one item from this category",
 
                )
 
    handled = False if products_form.errors else True
 

	
 
    # Making this a function to lazily evaluate when it's displayed
 
    # in templates.
 

	
 
    discounts = util.lazy(
 
        DiscountController.available_discounts,
 
        request.user,
 
        [],
 
        products,
 
    )
 

	
 
    return products_form, discounts, handled
 

	
 

	
 
def _set_quantities_from_products_form(products_form, current_cart):
 

	
 
    # Makes id_to_quantity, a dictionary from product ID to its quantity
 
    quantities = list(products_form.product_quantities())
 
    id_to_quantity = dict(quantities)
 

	
 
    # Get the actual product objects
 
    pks = [i[0] for i in quantities]
 
    products = inventory.Product.objects.filter(
 
        id__in=pks,
 
    ).select_related("category").order_by("id")
 

	
 
    quantities.sort(key=lambda i: i[0])
 

	
 
    # Match the product objects to their quantities
 
    product_quantities = [
 
        (product, id_to_quantity[product.id]) for product in products
 
    ]
 

	
 
    try:
 
        current_cart.set_quantities(product_quantities)
 
    except CartValidationError as ve:
 
        for ve_field in ve.error_list:
 
            product, message = ve_field.message
 
            products_form.add_product_error(product, message)
 

	
 

	
 
def _handle_voucher(request, prefix):
 
    ''' Handles a voucher form in the given request. Returns the voucher
 
    form instance, and whether the voucher code was handled. '''
 

	
 
    voucher_form = forms.VoucherForm(request.POST or None, prefix=prefix)
 
    current_cart = CartController.for_user(request.user)
 

	
 
    if (voucher_form.is_valid() and
 
            voucher_form.cleaned_data["voucher"].strip()):
 

	
 
        voucher = voucher_form.cleaned_data["voucher"]
 
        voucher = inventory.Voucher.normalise_code(voucher)
 

	
 
        if len(current_cart.cart.vouchers.filter(code=voucher)) > 0:
 
            # This voucher has already been applied to this cart.
 
            # Do not apply code
 
            handled = False
 
        else:
 
            try:
 
                current_cart.apply_voucher(voucher)
 
            except Exception as e:
 
                voucher_form.add_error("voucher", e)
 
            handled = True
 
    else:
 
        handled = False
 

	
 
    return (voucher_form, handled)
 

	
 

	
 
@login_required
 
def checkout(request, user_id=None):
 
    ''' Runs the checkout process for the current cart.
 

	
 
    If the query string contains ``fix_errors=true``, Registrasion will attempt
 
    to fix errors preventing the system from checking out, including by
 
    cancelling expired discounts and vouchers, and removing any unavailable
 
    products.
 

	
 
    Arguments:
 
        user_id (castable to int):
 
            If the requesting user is staff, then the user ID can be used to
 
            run checkout for another user.
 
    Returns:
 
        render or redirect:
 
            If the invoice is generated successfully, or there's already a
 
            valid invoice for the current cart, redirect to ``invoice``.
 
            If there are errors when generating the invoice, render
 
            ``registrasion/checkout_errors.html`` with the following data::
 

	
 
                {
 
                    "error_list", [str, ...]  # The errors to display.
 
                }
 

	
 
    '''
 

	
 
    if user_id is not None:
 
        if request.user.is_staff:
 
            user = User.objects.get(id=int(user_id))
 
        else:
 
            raise Http404()
 
    else:
 
        user = request.user
 

	
 
    current_cart = CartController.for_user(user)
 

	
 
    if "fix_errors" in request.GET and request.GET["fix_errors"] == "true":
 
        current_cart.fix_simple_errors()
 

	
 
    try:
 
        current_invoice = InvoiceController.for_cart(current_cart.cart)
 
    except ValidationError as ve:
 
        return _checkout_errors(request, ve)
 

	
 
    return redirect("invoice", current_invoice.invoice.id)
 

	
 

	
 
def _checkout_errors(request, errors):
 

	
 
    error_list = []
 
    for error in errors.error_list:
 
        if isinstance(error, tuple):
 
            error = error[1]
 
        error_list.append(error)
 

	
 
    data = {
0 comments (0 inline, 0 general)