Adds registration to the website (#69)

* Updates settings and requirements

* First pass at attendee profile

* Imports the registration templates; defines attendee profile models etc.

* First pass at themeing the registration form.

* First page of the registration form: done!

* Makes form validation nicer

* Adds populate_inventory

* Improves the additional items page

* Allows for rendering of formsets.

* Adds support for formset extending.

* Removes formset delete buttons

* Review page is LCA-ified

* Fixes some formset behaviour

* Fixes urls.py

* LCA-ifies product_category.html

* Invoices

* Credit card payments

* s/register/tickets/

* Show registration features only whilst products are available (think about this better, later)

* Updates the attendee profile form page

* Form tidy-up

* Makes it so that address info is copied from attendee profile to the address details are autofilled in Stripe.

* Adds feature to offer Australians a dropdown list of states rather than free text.

* Allow toggling of void invoices.

* Adds backgrounds to the headers in the registration process

* Improves the review page

* Adds “Linux Australia” to invoice details.

* Do not show balance due on void/refunded invoices.

* More thumbing

* Adds a link back to reports on each report.

* Tokenisation language.

* Another bug in credit card processing.

* Adds stripe refunds to options

* Removes spurious dashboard button.

* Tidies up the presentation of discounts.

* Tidies up presentation of voucher form.

* Fixes sponsor logo appearance with adblock.

* Front page tweaks

* Lets us specify alternative URLs in homepage panels

* more

* Updates discount amounts.

* More website fixes

* Changes language on pay invoice button

* Adds contact details to the invoice template.

* Updates the currency message in the invoice template.

* Explicitly includes e-mail address, because theme_contact_email doesn’t propagate

* Changes payment text.

* s/registration/selections/

* Removes final face palm

* Fixes lack of speaker dinner tickets for actual presenters.

* Adjusts wording in invoice e-mails

* Invoice wording.

* (FIX)

* Fixes margins on lists and tables

* Improvements arising from those CSS fixes.

* Changes description tags.
This commit is contained in:
Christopher Neugebauer 2016-09-30 03:46:05 -07:00 committed by Scott Bragg
parent 882902d7a3
commit a3474fd9cd
52 changed files with 2159 additions and 292 deletions

View file

@ -1,12 +1,12 @@
from __future__ import unicode_literals
from django import forms
from django.http import Http404
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models.signals import pre_delete
from django.dispatch import receiver
from django.forms.utils import ErrorList
from django.http import Http404
from django.shortcuts import render
from django.utils.encoding import python_2_unicode_compatible
@ -84,6 +84,19 @@ class ExternalLinksBlock(blocks.StructBlock):
url = blocks.URLBlock(required=True)
class BasicContentLink(blocks.StructBlock):
page = blocks.PageChooserBlock(
required=False,
help_text="You must specify either this, or the URL.",
)
url = blocks.CharBlock(
required=False,
help_text="You must specify either this, or the URL.",
)
title = blocks.CharBlock(required=True)
class BasicContentBlock(blocks.StructBlock):
class Meta:
@ -111,10 +124,7 @@ class BasicContentBlock(blocks.StructBlock):
"blue-left block. It's not used for white-right."
)
body = blocks.RichTextBlock(required=True)
link = blocks.StructBlock([
("page", blocks.PageChooserBlock()),
("title", blocks.CharBlock(required=True)),
])
link = BasicContentLink()
external_links = blocks.ListBlock(ExternalLinksBlock)
compact = blocks.BooleanBlock(
required=False,
@ -126,6 +136,13 @@ class PresentationChooserBlock(blocks.ChooserBlock):
target_model = schedule.models.Presentation
widget = forms.Select
# Return the key value for the select field
def value_for_form(self, value):
if isinstance(value, self.target_model):
return value.pk
else:
return value
class KeynoteSpeakerBlock(blocks.StructBlock):

View file

@ -15,6 +15,7 @@ def do_monkey_patch():
patch_mail_to_send_bcc()
fix_sitetree_check_access_500s()
never_cache_login_page()
patch_stripe_payment_form()
# Remove this function from existence
global do_monkey_patch
@ -103,7 +104,7 @@ def patch_mail_to_send_bcc():
return k
to_wrap = message.EmailMessage.__init__
@wraps(to_wrap)
def email_message__init__(*a, **k):
@ -142,3 +143,62 @@ def never_cache_login_page():
from django.views.decorators.cache import never_cache
from account.views import LoginView
LoginView.get = never_cache(LoginView.get)
def patch_stripe_payment_form():
import inspect # Oh no.
from django.http.request import HttpRequest
from registripe.forms import CreditCardForm
from pinaxcon.registrasion import models
old_init = CreditCardForm.__init__
@wraps(old_init)
def new_init(self, *a, **k):
# Map the names from our attendee profile model
# To the values expected in the Stripe card model
mappings = (
("address_line_1", "address_line1"),
("address_line_2", "address_line2"),
("address_suburb", "address_city"),
("address_postcode", "address_zip"),
("state", "address_state"),
("country", "address_country"),
)
initial = "initial"
if initial not in k:
k[initial] = {}
initial = k[initial]
# Find request context maybe?
frame = inspect.currentframe()
attendee_profile = None
if frame:
context = frame.f_back.f_locals
for name, value in (context.items() or {}):
if not isinstance(value, HttpRequest):
continue
user = value.user
if not user.is_authenticated():
break
try:
attendee_profile = models.AttendeeProfile.objects.get(
attendee__user=user
)
except models.AttendeeProfile.DoesNotExist:
# Profile is still none.
pass
break
if attendee_profile:
for us, stripe in mappings:
i = getattr(attendee_profile, us, None)
if i:
initial[stripe] = i
old_init(self, *a, **k)
CreditCardForm.__init__ = new_init

View file

@ -8,3 +8,9 @@ class ProfileForm(forms.ModelForm):
class Meta:
model = models.AttendeeProfile
exclude = ['attendee']
widgets = {
"past_lca" : forms.widgets.CheckboxSelectMultiple(),
}
class Media:
js = ("lca2017/js/profile_form.js", )

View file

@ -0,0 +1,723 @@
from collections import namedtuple
from datetime import datetime
from datetime import timedelta
from decimal import Decimal
from django.contrib.auth.models import Group
from django.core.exceptions import ObjectDoesNotExist
from django.core.management.base import BaseCommand, CommandError
from registrasion.models import inventory as inv
from registrasion.models import conditions as cond
from symposion import proposals
class Command(BaseCommand):
help = 'Populates the inventory with the LCA2017 inventory model'
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",
)
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 "
"[LINK]registration details page.[/LINK]",
required = True,
render_type=inv.Category.RENDER_TYPE_RADIO,
limit_per_user=1,
order=1,
)
self.penguin_dinner = self.find_or_make(
inv.Category,
("name",),
name="Penguin Dinner Ticket",
description="Tickets to our conference dinner on the evening of "
"Wednesday 18 January. 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=10,
)
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 Tuesday 17 January. You may purchase up "
"to 5 tickets in total, for significant others, and "
"family members.",
required = False,
render_type=inv.Category.RENDER_TYPE_QUANTITY,
limit_per_user=5,
order=20,
)
self.pdns_breakfast = self.find_or_make(
inv.Category,
("name",),
name="Opening Reception Breakfast Ticket",
description="Tickets to our Opening Reception Breakfast. This "
"event will be held on the morning of Monday 16 "
"January, 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=30,
)
self.t_shirt = self.find_or_make(
inv.Category,
("name",),
name="T-Shirt",
description="Commemorative conference t-shirts, featuring secret "
"linux.conf.au 2017 artwork.",
required = False,
render_type=inv.Category.RENDER_TYPE_ITEM_QUANTITY,
order=40,
)
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_fairy = self.find_or_make(
inv.Product,
("name", "category",),
category=self.ticket,
name="Fairy Penguin Sponsor",
price=Decimal("1999.00"),
reservation_duration=hours(24),
order=1,
)
self.ticket_professional = self.find_or_make(
inv.Product,
("name", "category",),
category=self.ticket,
name="Professional",
price=Decimal("999.00"),
reservation_duration=hours(24),
order=10,
)
self.ticket_hobbyist = self.find_or_make(
inv.Product,
("name", "category",),
category=self.ticket,
name="Hobbyist",
price=Decimal("449.00"),
reservation_duration=hours(24),
order=20,
)
self.ticket_student = self.find_or_make(
inv.Product,
("name", "category",),
category=self.ticket,
name="Student",
price=Decimal("160.00"),
reservation_duration=hours(24),
order=30,
)
self.ticket_miniconfs_mt = self.find_or_make(
inv.Product,
("name", "category",),
category=self.ticket,
name="Monday and Tuesday Only",
price=Decimal("198.00"),
reservation_duration=hours(24),
order=40,
)
self.ticket_miniconfs_mon = self.find_or_make(
inv.Product,
("name", "category",),
category=self.ticket,
name="Monday Only",
price=Decimal("99.00"),
reservation_duration=hours(24),
order=42,
)
self.ticket_miniconfs_tue = self.find_or_make(
inv.Product,
("name", "category",),
category=self.ticket,
name="Tuesday Only",
price=Decimal("99.00"),
reservation_duration=hours(24),
order=44,
)
self.ticket_speaker = self.find_or_make(
inv.Product,
("name", "category",),
category=self.ticket,
name="Speaker",
price=Decimal("00.00"),
reservation_duration=hours(24),
order=50,
)
self.ticket_media = self.find_or_make(
inv.Product,
("name", "category",),
category=self.ticket,
name="Media",
price=Decimal("00.00"),
reservation_duration=hours(24),
order=60,
)
self.ticket_sponsor = self.find_or_make(
inv.Product,
("name", "category",),
category=self.ticket,
name="Sponsor",
price=Decimal("00.00"),
reservation_duration=hours(24),
order=70,
)
self.ticket_team = self.find_or_make(
inv.Product,
("name", "category",),
category=self.ticket,
name="Conference Organiser",
price=Decimal("00.00"),
reservation_duration=hours(24),
order=80,
)
self.ticket_volunteer = self.find_or_make(
inv.Product,
("name", "category",),
category=self.ticket,
name="Conference Volunteer",
price=Decimal("00.00"),
reservation_duration=hours(24),
order=90,
)
# Penguin dinner
self.penguin_adult = self.find_or_make(
inv.Product,
("name", "category",),
category=self.penguin_dinner,
name="Adult",
description="Includes an adult's meal and full beverage service.",
price=Decimal("95.00"),
reservation_duration=hours(1),
order=10,
)
self.penguin_child = self.find_or_make(
inv.Product,
("name", "category",),
category=self.penguin_dinner,
name="Child",
description="Includes a child's meal and soft drink service.",
price=Decimal("50.00"),
reservation_duration=hours(1),
order=20,
)
self.penguin_infant = self.find_or_make(
inv.Product,
("name", "category",),
category=self.penguin_dinner,
name="Infant",
description="Includes no food or beverage service.",
price=Decimal("00.00"),
reservation_duration=hours(1),
order=30,
)
# Speakers' dinner
self.speakers_adult = self.find_or_make(
inv.Product,
("name", "category",),
category=self.speakers_dinner_ticket,
name="Adult",
description="Includes an adult's meal and full beverage service.",
price=Decimal("100.00"),
reservation_duration=hours(1),
order=10,
)
self.speakers_child = self.find_or_make(
inv.Product,
("name", "category",),
category=self.speakers_dinner_ticket,
name="Child",
description="Includes a child's meal and soft drink service.",
price=Decimal("60.00"),
reservation_duration=hours(1),
order=20,
)
self.speaker_infant = self.find_or_make(
inv.Product,
("name", "category",),
category=self.speakers_dinner_ticket,
name="Infant",
description="Infant must be seated in an adult's lap. No food or "
"beverage service.",
price=Decimal("00.00"),
reservation_duration=hours(1),
order=30,
)
# PDNS breakfast
self.pdns = self.find_or_make(
inv.Product,
("name", "category",),
category=self.pdns_breakfast,
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 polution 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 Size",
("S", "M", "L", "XL", "2XL", "3XL", "5XL"),
),
"womens_classic": ShirtGroup(
"Women's Classic Fit",
("XS", "S", "M", "L", "XL", "2XL"),
),
"womens_semi": ShirtGroup(
"Women's Semi-Fitted",
("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=Decimal("25.00"),
reservation_duration=hours(1),
order=order,
)
def populate_restrictions(self):
# Hide the products that will eventually need a voucher
hide_voucher_products = self.find_or_make(
cond.GroupMemberFlag,
("description", ),
description="Can see hidden products",
condition=cond.FlagBase.ENABLE_IF_TRUE,
)
hide_voucher_products.group.set([self.group_unpublish])
hide_voucher_products.products.set([
self.ticket_media,
self.ticket_sponsor,
])
# Set limits.
public_ticket_cap = self.find_or_make(
cond.TimeOrStockLimitFlag,
("description", ),
description="Public ticket cap",
condition=cond.FlagBase.DISABLE_IF_FALSE,
limit=450,
)
public_ticket_cap.products.set([
self.ticket_fairy,
self.ticket_professional,
self.ticket_hobbyist,
self.ticket_student,
])
non_public_ticket_cap = self.find_or_make(
cond.TimeOrStockLimitFlag,
("description", ),
description="Non-public ticket cap",
condition=cond.FlagBase.DISABLE_IF_FALSE,
limit=450,
)
non_public_ticket_cap.products.set([
self.ticket_speaker,
self.ticket_sponsor,
self.ticket_media,
self.ticket_team,
self.ticket_volunteer,
])
penguin_dinner_cap = self.find_or_make(
cond.TimeOrStockLimitFlag,
("description", ),
description="Penguin dinner ticket cap",
condition=cond.FlagBase.DISABLE_IF_FALSE,
limit=450,
)
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=150,
)
speakers_dinner_cap.categories.set([
self.speakers_dinner_ticket,
])
pdns_cap = self.find_or_make(
cond.TimeOrStockLimitFlag,
("description", ),
description="PDNS breakfast ticket cap",
condition=cond.FlagBase.DISABLE_IF_FALSE,
limit=400,
)
pdns_cap.categories.set([
self.pdns_breakfast,
])
# 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 = self.find_or_make(
cond.SpeakerFlag,
("description", ),
description="Speaker dinner tickets",
condition=cond.FlagBase.ENABLE_IF_TRUE,
is_presenter=True,
is_copresenter=True,
)
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_fairy,
self.ticket_media,
self.ticket_sponsor,
])
pdns_by_ticket.categories.set([self.pdns_breakfast, ])
# 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_breakfast, ])
# 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,
self.group_volunteers,
])
pdns_by_staff.categories.set([self.pdns_breakfast, ])
def populate_discounts(self):
def add_early_birds(discount):
self.find_or_make(
cond.DiscountForProduct,
("discount", "product"),
discount=discount,
product=self.ticket_fairy,
price=Decimal("150.00"),
quantity=1, # Per user
)
self.find_or_make(
cond.DiscountForProduct,
("discount", "product"),
discount=discount,
product=self.ticket_professional,
price=Decimal("150.00"),
quantity=1, # Per user
)
self.find_or_make(
cond.DiscountForProduct,
("discount", "product"),
discount=discount,
product=self.ticket_hobbyist,
price=Decimal("100.00"),
quantity=1, # Per user
)
def free_category(parent_discount, category, quantity=1):
self.find_or_make(
cond.DiscountForCategory,
("discount", "category",),
discount=parent_discount,
category=category,
percentage=Decimal("100.00"),
quantity=quantity,
)
# Early Bird Discount (general public)
early_bird = self.find_or_make(
cond.TimeOrStockLimitDiscount,
("description", ),
description="Early Bird",
end_time=datetime(year=2016, month=11, day=1),
limit=165, # Across all users
)
add_early_birds(early_bird)
# Early bird rates for speakers
speaker_ticket_discounts = self.find_or_make(
cond.SpeakerDiscount,
("description", ),
description="Speaker Ticket Discount",
is_presenter=True,
is_copresenter=True,
)
speaker_ticket_discounts.proposal_kind.set(
self.main_conference_proposals,
)
add_early_birds(speaker_ticket_discounts)
# 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_fairy,
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 & volunteer ticket inclusions
ticket_staff_inclusions = self.find_or_make(
cond.IncludedProductDiscount,
("description", ),
description="Complimentary for ticket holder (staff/volunteer)",
)
ticket_staff_inclusions.enabling_products.set([
self.ticket_team,
self.ticket_volunteer,
])
free_category(ticket_staff_inclusions, self.penguin_dinner)
# Team & volunteer t-shirts, regardless of ticket type
staff_t_shirts = self.find_or_make(
cond.GroupMemberDiscount,
("description", ),
description="T-shirts complimentary for staff and volunteers",
)
staff_t_shirts.group.set([
self.group_team,
self.group_volunteers,
])
free_category(staff_t_shirts, self.t_shirt, quantity=5)
def find_or_make(self, model, search_keys, **k):
''' Either makes or finds an object of type _model_, with the given
kwargs.
Arguments:
search_keys ([str, ...]): A sequence of keys that are used to search
for an existing version in the database. The remaining arguments are
only used when creating a new object.
'''
try:
keys = dict((key, k[key]) for key in search_keys)
a = model.objects.get(**keys)
self.stdout.write("FOUND : " + str(keys))
model.objects.filter(id=a.id).update(**k)
a.refresh_from_db()
return a
except ObjectDoesNotExist:
a = model.objects.create(**k)
self.stdout.write("CREATED: " + str(k))
return a
def hours(n):
return timedelta(hours=n)

View file

@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-09-22 06:25
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import django_countries.fields
_PAST_EVENTS = (
(1999, "1999 Melbourne (CALU)"),
(2001, "2001 Sydney"),
(2002, "2002 Brisbane"),
(2003, "2003 Perth"),
(2004, "2004 Adelaide"),
(2005, "2005 Canberra"),
(2006, "2006 Dunedin"),
(2007, "2007 Sydney"),
(2008, "2008 Melbourne"),
(2009, "2009 Hobart"),
(2010, "2010 Wellington"),
(2011, "2011 Brisbane"),
(2012, "2012 Ballarat"),
(2013, "2013 Canberra"),
(2014, "2014 Perth"),
(2015, "2015 Auckland"),
(2016, "2016 Geelong"),
)
def populate(apps, schema_editor):
PastEvent = apps.get_model("pinaxcon_registrasion","PastEvent")
all_such = PastEvent.objects.all()
by_year = dict((event.year, event) for event in all_such)
events = []
for past_event in _PAST_EVENTS:
if past_event[0] in by_year:
continue
events.append(PastEvent(
year=past_event[0],
name=past_event[1],
))
PastEvent.objects.bulk_create(events)
class Migration(migrations.Migration):
initial = True
dependencies = [
('registrasion', '0005_auto_20160905_0945'),
]
operations = [
migrations.CreateModel(
name='AttendeeProfile',
fields=[
('attendeeprofilebase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registrasion.AttendeeProfileBase')),
('name', models.CharField(help_text=b"Your name, as you'd like it to appear on your badge. ", max_length=64, verbose_name=b'Your name (for your conference nametag)')),
('company', models.CharField(blank=True, help_text=b"The name of your company, as you'd like it on your badge", max_length=64)),
('state', models.CharField(choices=[(b'', b'Not in Australia'), (b'TAS', b'Tasmania'), (b'ACT', b'Australian Capital Territory'), (b'NSW', b'New South Wales'), (b'NT', b'Northern Territory'), (b'QLD', b'Queensland'), (b'SA', b'South Australia'), (b'VIC', b'Victoria'), (b'WA', b'Western Australia')], max_length=4, verbose_name=b'Australian State/Territory')),
('country', django_countries.fields.CountryField(max_length=2)),
('free_text_1', models.CharField(blank=True, help_text=b"A line of free text that will appear on your badge. Use this for your Twitter handle, IRC nick, your preferred pronouns or anything else you'd like people to see on your badge.", max_length=64, verbose_name=b'Free text line 1')),
('free_text_2', models.CharField(blank=True, max_length=64, verbose_name=b'Free text line 2')),
('name_per_invoice', models.CharField(blank=True, help_text=b"If your legal name is different to the name on your badge, fill this in, and we'll put it on your invoice. Otherwise, leave it blank.", max_length=256, verbose_name=b'Your legal name (for invoicing purposes)')),
('address', models.TextField(blank=True, help_text=b'This address, if provided, will appear on your invoices.', verbose_name=b'Invoicing address')),
('of_legal_age', models.BooleanField(default=False, help_text=b'Being under 18 will not stop you from attending the conference. We need to know whether you are over 18 to allow us to cater for you at venues that serve alcohol.', verbose_name=b'Are you over 18?')),
('dietary_restrictions', models.TextField(blank=True, max_length=256, verbose_name=b'Food allergies, intolerances, or dietary restrictions')),
('accessibility_requirements', models.TextField(blank=True, verbose_name=b'Accessibility-related requirements')),
('gender', models.CharField(blank=True, help_text=b'Gender data will only be used for demographic purposes.', max_length=64)),
('linux_australia', models.BooleanField(help_text=b"Select this field to register for free <a href='http://www.linux.org.au/'>Linux Australia</a> membership.", verbose_name=b'Linux Australia membership')),
('lca_announce', models.BooleanField(help_text=b'Select to be subscribed to the low-traffic lca-announce mailing list', verbose_name=b'Subscribe to lca-announce list')),
('lca_chat', models.BooleanField(help_text=b'lca2017-chat is a high-traffic mailing list used by attendees during the week of the conference for general discussion.', verbose_name=b'Subscribe to the lca2017-chat list')),
],
bases=('registrasion.attendeeprofilebase',),
),
migrations.CreateModel(
name='PastEvent',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('year', models.IntegerField(unique=True)),
('name', models.CharField(max_length=255, unique=True)),
],
),
migrations.AddField(
model_name='attendeeprofile',
name='past_lca',
field=models.ManyToManyField(blank=True, to='pinaxcon_registrasion.PastEvent', verbose_name=b'Which past linux.conf.au events have you attended?'),
),
migrations.RunPython(populate),
]

View file

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-09-22 06:38
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pinaxcon_registrasion', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='attendeeprofile',
name='accessibility_requirements',
field=models.CharField(blank=True, max_length=256, verbose_name=b'Accessibility-related requirements'),
),
migrations.AlterField(
model_name='attendeeprofile',
name='dietary_restrictions',
field=models.CharField(blank=True, max_length=256, verbose_name=b'Food allergies, intolerances, or dietary restrictions'),
),
]

View file

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-09-22 23:37
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pinaxcon_registrasion', '0002_auto_20160922_0638'),
]
operations = [
migrations.RemoveField(
model_name='attendeeprofile',
name='address',
),
migrations.AddField(
model_name='attendeeprofile',
name='address_line_1',
field=models.CharField(blank=True, help_text=b'This address, if provided, will appear on your invoices.', max_length=1024, verbose_name=b'Address line 1'),
),
migrations.AddField(
model_name='attendeeprofile',
name='address_line_2',
field=models.CharField(blank=True, max_length=1024, verbose_name=b'Address line 2'),
),
migrations.AddField(
model_name='attendeeprofile',
name='address_postcode',
field=models.CharField(blank=True, max_length=1024, verbose_name=b'Postal/Zip code'),
),
migrations.AddField(
model_name='attendeeprofile',
name='address_suburb',
field=models.CharField(blank=True, max_length=1024, verbose_name=b'City/Town/Suburb'),
),
migrations.AlterField(
model_name='attendeeprofile',
name='state',
field=models.CharField(blank=True, max_length=256, verbose_name=b'State/Territory/Province'),
),
]

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.7 on 2016-09-23 01:33
from __future__ import unicode_literals
from django.db import migrations
import django_countries.fields
class Migration(migrations.Migration):
dependencies = [
('pinaxcon_registrasion', '0003_auto_20160922_2337'),
]
operations = [
migrations.AlterField(
model_name='attendeeprofile',
name='country',
field=django_countries.fields.CountryField(default=b'AU', max_length=2),
),
]

View file

@ -1,7 +1,52 @@
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django_countries.fields import CountryField
from registrasion import models as rego
@python_2_unicode_compatible
class PastEvent(models.Model):
''' This is populated in 0001_initial.py '''
def __str__(self):
return self.name
year = models.IntegerField(unique=True,)
name = models.CharField(max_length=255, unique=True,)
class AttendeeProfile(rego.AttendeeProfileBase):
'''
Have you attended linux.conf.au before?
1999 (CALU, Melbourne)
2001 (Sydney)
2002 (Brisbane)
2003 (Perth)
2004 (Adelaide)
2005 (Canberra)
2006 (Dunedin)
2007 (Sydney)
2008 (Melbourne)
2009 (Hobart)
2010 (Wellington)
2011 (Brisbane)
2012 (Ballarat)
2013 (Canberra)
2014 (Perth)
2015 (Auckland)
Do you want?
Membership of Linux Australia. (read more)
The low traffic linux.conf.au 2016 announcement mailing list
The linux.conf.au 2016 attendees mailing listName
'''
@classmethod
def name_field(cls):
@ -10,11 +55,49 @@ class AttendeeProfile(rego.AttendeeProfileBase):
return "name"
def invoice_recipient(self):
lines = [
self.name_per_invoice,
]
if self.company:
base = "\n%(company)s\nAttention: %(name_per_invoice)s"
else:
base = "%(name_per_invoice)s"
return base % self.__dict__
lines.append("C/- " + self.company)
if self.address_line_1:
lines.append(self.address_line_1)
if self.address_line_2:
lines.append(self.address_line_2)
if self.address_suburb or self.address_postcode:
lines.append("%s %s" % (
self.address_suburb or "",
self.address_postcode or "",
))
if self.state:
lines.append(self.state)
if self.country:
lines.append(self.country.name)
return "\n".join(unicode(line) for line in lines)
def clean(self):
errors = []
if self.country == "AU" and not self.state:
errors.append(
("state", "Australians must list their state"),
)
if self.address_line_2 and not self.address_line_1:
errors.append((
"address_line_1",
"Please fill in line 1 before filling line 2",
))
if errors:
raise ValidationError(dict(errors))
def save(self):
if not self.name_per_invoice:
@ -27,12 +110,12 @@ class AttendeeProfile(rego.AttendeeProfileBase):
max_length=64,
help_text="Your name, as you'd like it to appear on your badge. ",
)
company = models.CharField(
max_length=64,
help_text="The name of your company, as you'd like it on your badge",
blank=True,
)
free_text_1 = models.CharField(
max_length=64,
verbose_name="Free text line 1",
@ -51,32 +134,92 @@ class AttendeeProfile(rego.AttendeeProfileBase):
# Other important Information
name_per_invoice = models.CharField(
verbose_name="Your legal name (for invoicing purposes)",
max_length=64,
max_length=256,
help_text="If your legal name is different to the name on your badge, "
"fill this in, and we'll put it on your invoice. Otherwise, "
"leave it blank.",
blank=True,
)
of_legal_age = models.BooleanField(
default=False,
verbose_name="18+?",
address_line_1 = models.CharField(
verbose_name="Address line 1",
help_text="This address, if provided, will appear on your invoices.",
max_length=1024,
blank=True,
)
dietary_requirements = models.CharField(
address_line_2 = models.CharField(
verbose_name="Address line 2",
max_length=1024,
blank=True,
)
address_suburb = models.CharField(
verbose_name="City/Town/Suburb",
max_length=1024,
blank=True,
)
address_postcode = models.CharField(
verbose_name="Postal/Zip code",
max_length=1024,
blank=True,
)
country = CountryField(
default="AU",
)
state = models.CharField(
max_length=256,
verbose_name="State/Territory/Province",
blank=True,
)
of_legal_age = models.BooleanField(
default=False,
verbose_name="Are you over 18?",
blank=True,
help_text="Being under 18 will not stop you from attending the "
"conference. We need to know whether you are over 18 to "
"allow us to cater for you at venues that serve alcohol.",
)
dietary_restrictions = models.CharField(
verbose_name="Food allergies, intolerances, or dietary restrictions",
max_length=256,
blank=True,
)
accessibility_requirements = models.CharField(
verbose_name="Accessibility-related requirements",
max_length=256,
blank=True,
)
gender = models.CharField(
help_text="Gender data will only be used for demographic purposes.",
max_length=64,
blank=True,
)
linux_australia = models.BooleanField(
verbose_name="Linux Australia membership",
help_text="Select this field to register for free "
"<a href='http://www.linux.org.au/'>Linux Australia</a> "
"membership.",
blank=True,
)
class DemoPayment(rego.PaymentBase):
''' A subclass of PaymentBase for use in our demo payments function. '''
lca_announce = models.BooleanField(
verbose_name="Subscribe to lca-announce list",
help_text="Select to be subscribed to the low-traffic lca-announce "
"mailing list",
blank=True,
)
pass # No custom features here, but yours could be here.
lca_chat = models.BooleanField(
verbose_name="Subscribe to the lca2017-chat list",
help_text="lca2017-chat is a high-traffic mailing list used by "
"attendees during the week of the conference for general "
"discussion.",
blank=True,
)
past_lca = models.ManyToManyField(
PastEvent,
verbose_name="Which past linux.conf.au events have you attended?",
blank=True,
)

View file

@ -31,7 +31,7 @@ ALLOWED_HOSTS = []
# timezone as the operating system.
# If running in a Windows environment this must be set to the same as your
# system time zone.
TIME_ZONE = "UTC"
TIME_ZONE = "Australia/Hobart"
# The date format for this installation
DATE_FORMAT = "j F Y"
@ -182,16 +182,23 @@ INSTALLED_APPS = [
"pinax.boxes",
# Registrasion
#"registrasion",
"registrasion",
# Registrasion-stipe
"pinax.stripe",
"django_countries",
"registripe",
#admin - required by registrasion ??
#"nested_admin",
"nested_admin",
# project
"cms_pages",
"pinaxcon",
"pinaxcon.proposals",
#"pinaxcon.registrasion",
"pinaxcon.registrasion",
"jquery",
"djangoformsetjs",
#testing
"django_nose",
@ -268,6 +275,14 @@ PROPOSAL_FORMS = {
#PINAX_PAGES_HOOKSET = "pinaxcon.hooks.PinaxPagesHookSet"
#PINAX_BOXES_HOOKSET = "pinaxcon.hooks.PinaxBoxesHookSet"
# Registrasion bits:
ATTENDEE_PROFILE_MODEL = "pinaxcon.registrasion.models.AttendeeProfile"
ATTENDEE_PROFILE_FORM = "pinaxcon.registrasion.forms.ProfileForm"
INVOICE_CURRENCY = "AUD"
PINAX_STRIPE_PUBLIC_KEY = os.environ.get("STRIPE_PUBLIC_KEY", "your test public key")
PINAX_STRIPE_SECRET_KEY = os.environ.get("STRIPE_SECRET_KEY", "your test secret key")
PINAX_STRIPE_SEND_EMAIL_RECEIPTS = False
# Wagtail config
WAGTAIL_SITE_NAME = 'linux.conf.au 2017'
WAGTAIL_APPEND_SLASH = True

View file

@ -1,22 +1,13 @@
{% load lca2017_tags %}
{% if form.non_field_errors %}
{{ form.non_field_errors }}
<div class="has-errors">
{{ form.non_field_errors }}
</div>
<br/>
{% endif %}
<p>Fields marked with <strong>(*)</strong> are required.</p>
<blockquote><small>
Fields marked with <strong>(*)</strong> are required.
</small></blockquote>
{% for field in form %}
{% if not field.is_hidden %}
<div class="fieldWrapper">
<div>
{% include "forms/widget.html" %}
</div>
{% if field.help_text %}
<span class="help_text">{{ field.help_text|safe }}</span>
{% endif %}
<p></p>
</div>
{% endif %}
{% endfor %}
{% include "forms/render.html" with form=form %}

View file

@ -1,11 +1,19 @@
{% load lca2017_tags %}
{% if value.link.page %}
{% define value.link.page.url as url %}
{% else %}
{% define value.link.url as url %}
{% endif %}
<div class="panel--2-3">
<h2>{{ value.heading }}</h2>
<p class="lede">{{ value.body }}</p>
<div class="btn-group">
{% if value.panel_type == "blue_left" %}
<a href="{{ value.link.page.url }}" class="btn btn__white">{{ value.link.title }}</a>
<a href="{{ url }}" class="btn btn__white">{{ value.link.title }}</a>
{% elif value.panel_type == "white_right" %}
<a href="{{ value.link.page.url }}" class="btn">{{ value.link.title }}</a>
<a href="{{ url }}" class="btn">{{ value.link.title }}</a>
{% endif %}
{% for link in value.external_links %}
{{ link }}

View file

@ -2,7 +2,7 @@
{% load wagtailimages_tags %}
{% image value.profile_image width-800 as profile_image %}
<div class="panel panel__compact">
<div class="panel panel__compact {% if forloop.first %}panel__first{% elif forloop.last %}panel__last{% endif %}">
<div class="panel--content">
<div class="panel--1-3">
<div class="portrait">

View file

@ -1,4 +1,4 @@
<div class="panel panel__compact">
<div class="panel panel__compact panel__first">
<div class="panel--content">
<div class="panel--1-3"></div>
<div class="panel--2-3">
@ -8,5 +8,5 @@
</div>
{% for speaker in value.speakers %}
{{ speaker }}
{% include "cms_pages/home_page_blocks/keynote_speaker.html" with value=speaker %}
{% endfor %}

View file

@ -4,7 +4,8 @@
{% load proposal_tags %}
{% load review_tags %}
{% load teams_tags %}
{# {% load registrasion_tags %} #}
{% load registrasion_tags %}
{% load lca2017_tags %}
{% block head_title %}Dashboard{% endblock %}
@ -19,7 +20,101 @@
{% load staticfiles %}
{% load wagtailimages_tags %}
<div class="panel panel__compact panel__bg">
{% available_categories as categories %}
{% if categories %}
<div class="panel panel__compact panel__bg panel__last">
<div style="background-image: url('{% static "lca2017/images/wp_bg_optimised.jpg" %}');" class="panel--bg"></div>
<div class="panel--content">
<div >
<h2>{% trans "Attend" %} {% conference_name %}</h2>
<div class="vertical-bigger"></div>
<div class="btn-group">
{% if not user.attendee.completed_registration %}
<a href="{% url "guided_registration" %}" class="btn btn__white">
Get your ticket
</a>
{% else %}
<a href="{% url "attendee_edit" %}" class="btn btn__white">
Edit attendee profile
</a>
{% items_pending as pending %}
{% if pending %}
<a href="{% url "checkout" %}" class="btn btn__white">
Check out and pay
</a>
{% endif %}
{% endif %}
{% if user.is_staff %}
<a class="btn btn__white" href="{% url "reports_list" %}">Reports</a>
{% endif %}
</div>
</div>
</div>
</div>
<div class="l-content-page">
<div class="l-content-page--richtext">
{% if not user.attendee.completed_registration %}
<p>To attend the conference, you must purchase a ticket. <a href="{% url "guided_registration" %}">Use our registration form to purchase your ticket</a>.
{% else %}
<h3>Your items</h3>
{% items_pending as pending %}
{% if pending %}
<h4>Items pending payment</h4>
{% include "registrasion/_items_list.html" with items=pending %}
<p><a href="{% url "checkout" %}" class="btn btn-xs btn-default">
<i class="fa fa-credit-card"></i>
Check out and pay for these items.</a>
</p>
{% endif %}
{% items_purchased as purchased %}
{% if purchased %}
<h4>Paid items</h4>
{% include "registrasion/_items_list.html" with items=purchased %}
{% endif %}
<h4>Add/Update items</h4>
{% include "registrasion/_category_list.html" with categories=categories %}
{% invoices as invoices %}
{% if invoices %}
<h4>Invoices</h4>
<ul>
{% for invoice in invoices %}
{% if invoice.is_void %}
<li class="void-invoice" style="display: none;">
{% else %}
<li>
{% endif %}
<a href="{% url "invoice" invoice.id %}" >Invoice {{ invoice.id }}</a>
- ${{ invoice.value }} ({{ invoice.get_status_display }})
</li>
{% endfor %}
</ul>
<p>
<button class="btn" id="toggle-void-invoices" href="" onclick="toggleVoidInvoices();">Show void invoices</button>
</p>
{% endif %}
{% available_credit as credit %}
{% if credit %}
<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>
{% endif %}
{% endif %}
<p></p>
</div>
</div>
{% endif %}
<div class="panel panel__compact panel__bg panel__last">
<div style="background-image: url('{% static "lca2017/images/hobart_bg_optimised.jpg" %}');" class="panel--bg"></div>
<div class="panel--content">
<div class="panel--2-3">
@ -39,7 +134,6 @@
</a>
{% endif %}
</div>
<br />
</div>
</div>
</div>
@ -109,12 +203,11 @@
{% if review_sections %}
<div class="panel panel__compact panel__bg">
<div class="panel panel__compact panel__bg panel__last">
<div style="background-image: url('{% static "lca2017/images/conference_bg_optimised.jpg" %}');" class="panel--bg"></div>
<div class="panel--content">
<div class="panel--2-3">
<h2>{% trans "Reviews" %}</h2>
<br />
</div>
</div>
</div>
@ -195,3 +288,29 @@
{% endif %}
{% endblock %}
{% block scripts_extra %}
<script type="text/javascript">
function _toggleVoidInvoices() {
var visible = false;
function toggleVoidInvoices() {
$btn = $("#toggle-void-invoices");
$invoices = $(".void-invoice")
if (visible) {
$invoices.hide();
btnText = "Show void invoices";
} else {
$invoices.show();
btnText = "Hide void invoices";
}
$btn.text(btnText);
visible = !visible;
return true;
}
return toggleVoidInvoices;
}
var toggleVoidInvoices = _toggleVoidInvoices();
_toggleVoidInvoices() = undefined;
</script>
{% endblock %}

View file

@ -0,0 +1,58 @@
{% load formset_tags %}
{% if form.total_form_count and form.empty_form %}
{% comment %}This is a formset.{% endcomment %}
<div class="formset" data-formset-prefix="{{ form.prefix }}">
{{ form.management_form }}
<div data-formset-body>
{% for inner_form in form %}
<div data-formset-form>
<fieldset>
{% include "forms/render_fields.html" with form=inner_form %}
</fieldset>
</div>
{% if not forloop.last %}
<div class="vertical-small"></div>
{% endif %}
{% endfor %}
</div>
<script type="form-template" data-formset-empty-form>
{% escapescript %}
<div class="vertical-small"></div>
<div data-formset-form>
<fieldset>
{% include "forms/render_fields.html" with form=form.empty_form %}
</fieldset>
</div>
{% endescapescript %}
</script>
<div class="vertical-small"></div>
<input type="button" class="btn" value="Add another" data-formset-add>
<script type="text/javascript">
function defer() {
if (window.jQuery) {
jQuery(function($) {
$(".formset").formset({
animateForms: true
});
});
} else {
setTimeout(function() { defer() }, 50);
}
}
defer();
</script>
</div>
{% else %}
{% include "forms/render_fields.html" %}
{% endif %}

View file

@ -0,0 +1,13 @@
{% for field in form %}
{% if not field.is_hidden %}
<div class="fieldWrapper {% if field.errors %}has-errors{% endif %}">
<div>
{% include "forms/widget.html" %}
</div>
{% if field.help_text %}
<span class="help_text">{{ field.help_text|safe }}</span>
{% endif %}
</div>
{% endif %}
{% endfor %}

View file

@ -3,9 +3,9 @@
{% classname field.field.widget as widget %}
{% if widget == "CheckboxInput" %}
{% include "forms/widget_boolean_single.html" %}
{% elif widget == "RadioSelect" %}
{% elif widget == "RadioSelect" or widget == "CheckboxSelectMultiple" %}
{% include "forms/widget_boolean_multiple.html" %}
{% elif widget == "Select" or widget == "SelectMultiple" %}
{% elif widget == "Select" or widget == "SelectMultiple" or widget == "LazySelect" or widget == "CountrySelectWidget" %}
{% include "forms/widget_basic.html" with widget_class="select" %}
{% else %}
{% include "forms/widget_basic.html" %}

View file

@ -1,13 +1,17 @@
<h4>
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
{{ field.label }}
{% if field.field.required %}
(*)
{% endif %}
</h4>
<ul class="form-field boolean-group">
{% for item in field %}
LA
<li class="boolean-group--row">
{% if True or widget == "CheckboxSelectMultiple" %}
<li>
{% else %}
<!-- This is broken for everything. Going to need to look at Django. -->
<li class="boolean-group--row">
{% endif %}
{{ item }}
<label for="{{ item.id_for_label }}"><strong>{{ item.label }}</strong></label>
</li>

View file

@ -0,0 +1,7 @@
<ul>
{% for category in categories %}
{% if not category in exclude %}
<li><a href="{% url "product_category" category.id %}">{{ category.name }}</a></li>
{% endif %}
{% endfor %}
</ul>

View file

@ -0,0 +1,86 @@
{% load registrasion_tags %}
{% load lca2017_tags %}
<h2>Tax Invoice/Statement</h2>
<h3>Linux Australia</h3>
<h4>ABN 56 987 117 479</h4>
<p>
Enquiries: please e-mail <a href="mailto:team@hobart.lca2017.org">team@hobart.lca2017.org</a>
</p>
<ul>
<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 Australian Dollars (AUD).</p>
<table class="table table-striped">
<tr>
<th>Description</th>
<th class="text-right">Quantity</th>
<th class="text-right">Price/Unit</th>
<th class="text-right">Total</th>
</tr>
{% 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 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 %}
</table>
{% if invoice.paymentbase_set.all %}
<hr />
<h3>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 />
<p>

View file

@ -1,7 +1,7 @@
{% if items %}
<ul>
{% for item in items %}
<li>{{ item.quantity }} &times; {{ item.product }}</li>
<li>{{ item.quantity }} &times; {{ item.product }} {{ suffix }}</li>
{% endfor %}
</ul>
{% endif %}

View file

@ -0,0 +1,54 @@
{% extends "registrasion/base.html" %}
{% load bootstrap %}
{% load registrasion_tags %}
{% block content %}
<h2>Item summary for {{ user.attendee.attendeeprofilebase.attendee_name }}
(id={{user.id}})</h2>
<h3>Paid Items</h3>
<p>You cannot remove paid items from someone's registration. You must first
cancel the invoice that added those items. You will need to re-add the items
from that invoice for the user to have them available again.</p>
{% include "registrasion/_items_list.html" with items=paid %}
<br />
<h3>Cancelled Items</h3>
{% include "registrasion/_items_list.html" with items=cancelled %}
<br />
<h3>Amend pending items</h3>
<form method="POST">
{% csrf_token %}
{% include "_form_snippet.html" with form=form %}
<br/>
<input class="btn" type="submit">
</form>
<br />
<h3>Generate invoice</h3>
<div class="btn-group">
<a class="btn" href="{% url "checkout" user.id %}">Check out cart and view invoice</a>
</div>
<br />
<h3>Apply voucher</h3>
<form method="POST">
{% csrf_token %}
{% include "_form_snippet.html" with form=voucher_form %}
<br/>
<input class="btn" type="submit">
</form>
{% endblock %}

View file

@ -1,5 +1,4 @@
{% extends "site_base.html" %}
{% extends "lca2017/content_page.html" %}
{% load staticfiles %}
{% block body_outer %}
{% block body %}{% endblock %}
{% endblock %}
{% block header_background_image %}{% static "lca2017/images/hobart_bg_optimised.jpg" %}{% endblock %}

View file

@ -8,7 +8,7 @@
{% with note_user=credit_note.invoice.user %}
<ul>
<li><strong>Number:</strong> {{ credit_note.id }}
<li><strong>Attention:</strong> {{ credit_note.invoice.user.attendee.attendeeprofilebase.invoice_recipient }}</li>
<li><strong>Attention:</strong> {{ credit_note.invoice.recipient }}</li>
<li><strong>Value:</strong> {{ credit_note.value }}</li>
<li><strong>Status:</strong> {{ credit_note.status }}</li>
</ul>
@ -26,6 +26,20 @@
<div class="form-actions">
<input class="btn btn-primary" type="submit" value="Apply to invoice" />
</div>
<h3>Generate cancellation fee</h3>
<p>You can generate an invoice for a cancellation fee, resulting in an invoice
and a new credit note.
</p>
{{ cancellation_fee_form|bootstrap }}
<div class="form-actions">
<input class="btn btn-primary" type="submit" value="Generate fee" />
</div>
<h3>Stripe Refund</h3>
<p><a href="{% url 'registripe_refund' credit_note.id %}">View Stripe refund options</a></p>
<h3>Manual refund</h3>
<p>You can mark this credit note as refunded, and handle the refund manually.
</p>

View file

@ -1,72 +0,0 @@
<div class="panel panel-default">
<div class="panel-heading">
<div class="pull-right">
{% if not user.attendee.completed_registration %}
<a href="{% url "guided_registration" %}" class="btn btn-xs btn-default">
<i class="fa fa-plus-sign"></i> Register for the conference
</a>
{% else %}
<a href="{% url "attendee_edit" %}" class="btn btn-xs btn-default">
<i class="fa fa-pencil"></i> Edit your attendee profile
</a>
{% items_pending as pending %}
{% if pending %}
<a href="{% url "checkout" %}" class="btn btn-xs btn-default">
<i class="fa fa-credit-card"></i> Pay your registration
</a>
{% endif %}
{% endif %}
</div>
<h3 class="panel-title">
<i class="fa fa-ticket"></i>
{% trans "Registration" %}
</h3>
</div>
<div class="panel-body">
{% if not user.attendee.completed_registration %}
<p>To attend the conference, you must purchase a ticket. <a href="{% url "guided_registration" %}">Use our registration form to purchase your ticket</a>.
{% else %}
<h4>Your registration</h4>
{% items_pending as pending %}
{% if pending %}
<h5>Items pending payment</h5>
{% include "registrasion/items_list.html" with items=pending %}
{% endif %}
{% items_purchased as purchased %}
{% if purchased %}
<h5>Paid items</h5>
{% include "registrasion/items_list.html" with items=purchased %}
{% endif %}
<h5>Add/Update items</h5>
{% available_categories as categories %}
{% for category in categories %}
<li><a href="{% url "product_category" category.id %}">{{ category.name }}</a></li>
{% endfor %}
</ul>
{% invoices as invoices %}
{% if invoices %}
<h5>Invoices</h5>
<ul>
{% for invoice in invoices %}
{% if not invoice.is_void %}
<li>
<a href="{% url "invoice" invoice.id %}">Invoice {{ invoice.id }}</a>
- ${{ invoice.value }} ({{ invoice.get_status_display }})
</li>
{% endif %}
{% endfor %}
</ul>
{% endif %}
{% available_credit as credit %}
{% if credit %}
<p>You have ${{ credit }} leftover from refunded invoices. Contact the conference organisers
to put this toward other purchases, or to refund it.</p>
{% endif %}
{% endif %}
</div>
</div>

View file

@ -1,7 +1,12 @@
{% if discounts %}
{% regroup discounts by discount.description as discounts_grouped %}
{% for discount_type in discounts_grouped %}
<h4>{{ discount_type.grouper }}</h4>
<ul>
{% for discount in discounts %}
{% for discount in discount_type.list %}
<li>{{ discount.quantity }} &times; {{ discount.clause }}</li>
{% endfor %}
</ul>
{% endfor %}
{% endif %}

View file

@ -0,0 +1,26 @@
{% load i18n %}
{% if invoice.is_unpaid %}
<p>
<strong>NOTICE:</strong> The below 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>
<p>Please ensure this invoice is paid by the due date.</p>
{% endif %}
{% url "invoice" invoice.id invoice.user.attendee.access_code as access_url %}
<p>This invoice is available at <a href="http://{{ current_site }}{{ access_url }}">http://{{ current_site }}{{ access_url }}</a>
{% if invoice.is_unpaid %}
-- You can give this URL to your accounts department to pay for your selections.
{% endif %}
</p>
<hr />
{% include "registrasion/_invoice_details.html" %}

View file

@ -0,0 +1 @@
{% load i18n %}Invoice {{ invoice.id }} from Linux Australia

View file

@ -0,0 +1 @@
{% include "registrasion/emails/invoice_created/message.html" %}

View file

@ -0,0 +1 @@
{% load i18n %}{{ invoice.get_status_display }} -- Invoice {{ invoice.id }} from Linux Australia

View file

@ -1,34 +1,53 @@
{% extends "registrasion/base.html" %}
{% load bootstrap %}
{% load lca2017_tags %}
{% block body %}
{% block header_title %}Buy Your Ticket{% endblock %}
{% block header_paragraph %}Step {{ current_step }} of {{ total_steps|add:1 }} &ndash; {{ title }} {% endblock %}
{% block header_inset_image %}{% illustration "tuz.svg" %}{% endblock %}
<h1>Conference Registration {{ title }}</h1>
{% block scripts_extra %}
{% for section in sections %}
{{ section.form.media.js }}
{% endfor %}
{% endblock %}
<p><em>Step {{ current_step }} of {{ total_steps|add:1 }}</em></p>
{% block content %}
<form method="post" action="">
{% csrf_token %}
{% for section in sections %}
<h3>{{ section.title }}</h3>
<h2>{{ section.title }}</h2>
{% if section.description %}
<p>{{ section.description }}</p>
{% endif %}
{% if section.discounts %}
<p>You have the following discounts available to you:</p>
{% include "registrasion/discount_list.html" with discounts=section.discounts %}
<blockquote>{{ section.description }}</blockquote>
{% endif %}
<fieldset>
{{ section.form|bootstrap }}
{% if section.discounts %}
{% include "registrasion/discount_list.html" with discounts=section.discounts %}
<blockquote><small>
You must select a product to receive any discounts.<br/>
Applicable discounts will be applied automatically when you check out.
</small></blockquote>
<hr />
{% endif %}
<h3>Available options</h3>
{% include "_form_snippet.html" with form=section.form %}
</fieldset>
<br />
{% endfor %}
<div class="form-actions">
<input class="btn btn-primary" type="submit" value="Next" />
<div class="btn-group">
<input class="btn btn-primary" type="submit" value="Next Step" />
</div>
</form>

View file

@ -1,35 +0,0 @@
{% extends "registrasion/base.html" %}
{% load bootstrap %}
{% load registrasion_tags %}
{% block body %}
<h1>Conference Registration Review</h1>
{% items_pending as pending %}
{% if pending %}
<p><em>Step 4 of 4</em></p>
<p>You're almost done! You've selected the following items:<p>
{% include "registrasion/items_list.html" with items=pending %}
<p>You can either generate an invoice and pay for your registration, or return to
the dashboard to make amendments.</p>
<div class="form-actions">
<a class="btn btn-default" href="{% url "checkout" %}">Check out and pay</a>
<a class="btn btn-default" href="{% url "dashboard" %}">Return to dashboard</a>
</div>
{% else %}
<p>You have no items that need to be paid.</p>
<div class="form-actions">
<a class="btn btn-default" href="{% url "dashboard" %}">Return to dashboard</a>
</div>
{% endif %}
{% endblock %}

View file

@ -1,29 +1,42 @@
{% extends "site_base.html" %}
{% extends "registrasion/base.html" %}
{% load bootstrap %}
{% load registrasion_tags %}
{% block body %}
{% load lca2017_tags %}
{% load staticfiles %}
{% block header_title %}{% conference_name %}{% endblock %}
{% block header_paragraph %}
<p>Monday 16 January&ndash;Friday 20 January 2017.</p>
<p>Wrest Point Convention Centre, Hobart, Tasmania, Australia.</p>
{% endblock %}
{% block header_inset_image %}{% illustration "tuz.svg" %}{% endblock %}
{% block header_background_image %}{% static "lca2017/images/wp_bg_optimised.jpg" %}{% endblock %}
{% block content %}
{% if invoice.is_unpaid %}
<p><strong>NOTICE:</strong> The below invoice is automatically generated, and will be voided
if you amend your registration before payment, or if discounts or products contained in the
invoice become unavailable. The items and discounts are only reserved until
the invoice due time.</p>
<p>
<strong>NOTICE:</strong> The below 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 give this URL to your accounts department to pay your registration.</p>
You can give this URL to your accounts department to pay for this invoice.</p>
<div>
<a class="btn btn-default" href="{% url "demopay" invoice.id invoice.user.attendee.access_code %}">Pay this invoice</a>
<div class="btn-group">
<a class="btn btn-default" 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-default" href="{% url "manual_payment" invoice.id %}">Apply manual payment</a>
{% endif %}
</div>
{% elif invoice.is_paid %}
{% if user.is_staff %}
<div>
<div class="btn_group">
{% 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>
@ -34,49 +47,6 @@
<hr />
<h2>Invoice</h2>
{% with invoice_user=invoice.cart.user %}
<ul>
<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 %}
<li><strong>Recipient:</strong> {{ invoice_user.attendee.attendeeprofilebase.invoice_recipient|linebreaksbr}}</li>
</ul>
{% endwith %}
<p>This invoice has been issued as a result of an application to attend (conference name).</p>
<table class="table table-striped">
<tr>
<th>Description</th>
<th class="text-right">Quantity</th>
<th class="text-right">Price/Unit</th>
<th class="text-right">Total</th>
</tr>
{% 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.price|multiply:line_item.quantity }}</td>
</tr>
{% endfor %}
<tr>
<th>TOTAL</th>
<td></td>
<td></td>
<td class="text-right">${{ invoice.value }}</td>
</tr>
</table>
{% if invoice.paymentbase_set.all %}
<h4>Payments received</h4>
{% include "registrasion/payment_list.html" with payments=invoice.paymentbase_set.all %}
{% endif %}
{% include "registrasion/_invoice_details.html" %}
{% endblock %}

View file

@ -1,44 +1,74 @@
{% extends "registrasion/base.html" %}
{% load bootstrap %}
{% load registrasion_tags %}
{% block body %}
{% load lca2017_tags %}
<h1>Product Category: {{ category.name }}</h1>
{% block header_title %}Product Category: {{ category.name }}{% endblock %}
{% block header_inset_image %}{% illustration "lavender.svg" %}{% endblock %}
{% block scripts_extra %}
{{ voucher_form.media.js }}
{{ form.media.js }}
<script type="text/javascript">
function showVoucherForm() {
$("#voucher-form").show();
$("#voucher-form-button").hide();
}
</script>
{% endblock %}
{% block content %}
<button id="voucher-form-button" class="btn" onclick="showVoucherForm()">Enter voucher code</button>
<form method="post" action="">
{% csrf_token %}
<table>
{{ voucher_form | bootstrap }}
</table>
<div class="form-actions">
<fieldset id="voucher-form" style="display: none;">
{% include "_form_snippet.html" with form=voucher_form %}
<div class="btn-group" />
<input class="btn btn-primary" type="submit" value="Add voucher" />
</div>
</div>
</fieldset>
<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 %}
{% include "registrasion/_items_list.html" with items=items %}
{% endif %}
{% if discounts %}
<h3>Available Discounts</h3>
{% include "registrasion/discount_list.html" with discounts=discounts %}
{% endif %}
<h2>{{ category.name }}</h2>
<blockquote>{{ category.description }}</blockquote>
<h3>Available Products</h3>
<p>{{ category.description }}</p>
<table>
{{ form | bootstrap }}
</table>
<fieldset>
<div class="form-actions">
<input class="btn btn-primary" type="submit" value="Add to cart" />
</div>
{% if discounts %}
<h3>Discounts and Complimentary Items</h3>
<div class="vertical-small"></div>
{% include "registrasion/discount_list.html" with discounts=discounts %}
<blockquote><small>Any applicable discounts will be applied automatically when you check out.</small></blockquote>
<hr />
{% endif %}
<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>
</fieldset>
</form>

View file

@ -1,24 +1,33 @@
{% extends "registrasion/base.html" %}
{% load bootstrap %}
{% load lca2017_tags %}
{% block body %}
{% block header_title %}Your profile{% endblock %}
{% block header_inset_image %}{% illustration "antarctica.svg" %}{% endblock %}
{% block header_paragraph %}
These details will appear on your badge, your invoices, and will be used
to order catered food at the conference.
{% endblock %}
<h1>Your profile</h1>
{% block scripts_extra %}
{{ form.media.js }}
<script type="text/javascript">
</script>
{% endblock %}
<p>These details will appear on your badge, your invoices, and will be used
to order catered food at the conference.</p>
{% block content %}
<form method="post" action="">
{% csrf_token %}
<table>
{{ form|bootstrap }}
</table>
<fieldset>
{% include "_form_snippet.html" with form=form %}
<br />
<div class="btn-group">
<input class="btn" type="submit" value="Save Profile" />
<a class="btn btn-primary" href="{% url "dashboard" %}">Return to dashboard</a>
</div>
</fieldset>
<div class="form-actions">
<input class="btn btn-primary" type="submit" value="Save" />
</div>
</form>
{% endblock %}

View file

@ -0,0 +1,40 @@
{% extends "site_base.html" %}
{% load bootstrap %}
{% load registrasion_tags %}
{% block body %}
<h2>{{ title }}</h2>
<p><a href="{% url 'reports_list' %}">Return to reports list</a></p>
{% if form %}
<form method="GET">
{{ form | bootstrap}}
<br/>
<input class="btn btn-primary" type="submit">
</form>
{% endif %}
<hr />
{% for report in reports %}
<h3>{{ report.title }}</h3>
<table class="table table-striped">
<tr>
{% for heading in report.headings %}
<th>{{ heading }}</th>
{% endfor %}
</tr>
{% for line in report.rows %}
<tr>
{% for item in line %}
<td>
{{ item|safe }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
{% endfor %}
{% endblock %}

View file

@ -0,0 +1,21 @@
{% extends "site_base.html" %}
{% load bootstrap %}
{% load registrasion_tags %}
{% block body %}
<h2>Registration reports</h2>
<table class="table table-striped">
{% for report in reports %}
<tr>
<td>
<a href="{{ report.url }}">{{ report.name }}</a>
</td>
<td>
{{ report.description }}
</td>
</tr>
{% endfor %}
</table>
{% endblock %}

View file

@ -0,0 +1,78 @@
{% extends "registrasion/base.html" %}
{% load bootstrap %}
{% load registrasion_tags %}
{% load lca2017_tags %}
{% block header_title %}Review your selection{% endblock %}
{% block header_inset_image %}{% illustration "wineglass.svg" %}{% endblock %}
{% block header_paragraph %}
Please ensure that you have selected all of the products you require, including
t-shirts and social event tickets.
{% endblock %}
{% block content %}
{% items_pending as pending %}
{% if pending %}
<h3>Current selection</h3>
<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 %}
{% items_purchased as purchased %}
{% if purchased %}
<p>You've already paid for the following items:</p>
{% include "registrasion/_items_list.html" with items=purchased suffix="<em>(PAID)</em>" %}
{% endif %}
<h3>Add to your selection</h3>
<p>You can add these items now, or you can come back and add them in a
later purchase.</p>
{% missing_categories as missing %}
{% if missing %}
<p>
<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>
</p>
{% include "registrasion/_category_list.html" with categories=missing %}
{% endif %}
<p>
<strong>You can also change your selection from these categories:</strong>
</p>
{% available_categories as available %}
{% include "registrasion/_category_list.html" with categories=available exclude=missing %}
<h3>What next?</h3>
<p>You can either check out an invoice and pay for your selections, or return to
the dashboard.</p>
<div class="btn-group">
<a class="btn" href="{% url "checkout" %}">
<i class="fa fa-credit-card"></i> Check out and pay
</a>
<a class="btn btn-primary" href="{% url "dashboard" %}">Return to dashboard</a>
</div>
{% else %}
<p>You have no items that need to be paid.</p>
<div class="form-actions">
<a class="btn btn-default" href="{% url "dashboard" %}">Return to dashboard</a>
</div>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,104 @@
{% extends "registrasion/base.html" %}
{% load bootstrap %}
{% load registrasion_tags %}
{% load lca2017_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:
$submit.prop('disabled', false);
$submit.click();
$submit.prop('disabled', true);
$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 header_inset_image %}{% illustration "casino.svg" %}{% endblock %}
{% block header_paragraph %}
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>.
{% endblock %}
{% block content %}
<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> to complete payment.
</p>
<p>You have ${{ invoice.balance_due }} remaining to pay on this invoice.</p>
<p></p>
<h3>Card details</h3>
<form 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 />
<div class="btn-group">
<input id="submit" class="btn btn-primary" type="submit" value="Pay ${{ invoice.balance_due }}" />
</div>
</fieldset>
</form>
{% endblock %}

View file

@ -0,0 +1,22 @@
{% extends "site_base.html" %}
{% load bootstrap %}
{% load registrasion_tags %}
{% block body %}
<h2>Stripe refunds for credit note #{{ credit_note.id}}</h2>
<p>This credit note is valued at ${{ credit_note.value }}.</p>
<h3>Available refunds</h3>
<p>Currently credit notes can only be cashed out in full in a single
transaction. If you need to cash out to multiple small payments, you will
need to manually invoke the refunds from the Stripe Dashboard.</p>
<form method="post">
{% csrf_token %}
{{form|bootstrap}}
<input id="submit" class="btn btn-primary" type="submit" value="Refund {{ credit_note.value }} to payment source" />
</form>
{% endblock %}

View file

@ -29,13 +29,13 @@
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@linuxconfau">
<meta name="twitter:title" content="linux.conf.au 16-20 January 2017 Hobart, Tasmania"
<meta name="twitter:description"content="Share the Future of Open Source by submitting a talk, tutorial, or miniconf proposal: our CFP closes on 5 August.">
<meta name="twitter:description"content="Discover the future of Open Source at linux.conf.au 2017. Tickets on sale from October.">
<meta name="twitter:image" content="{{ request.scheme }}://{{ request.get_host }}{% static 'lca2017/images/socmed/twitter-tuz.png' %}">
<meta name="twitter:image:alt" content="Tuz">
<!--Open Graph-->
<meta property="og:url" content="https://linux.conf.au/">
<meta property="og:title" content="linux.conf.au 16-20 January 2017 Hobart, Tasmania">
<meta property="og:description" content="Share the Future of Open Source by submitting a talk, tutorial, or miniconf proposal: our CFP closes on 5 August.">
<meta property="og:description" content="Discover the future of Open Source at linux.conf.au 2017. Tickets on sale from October.">
<meta property="og:type" content="website">
<meta property="fb:admins" content="729577430">
<meta property="fb:profile_id" content="293907417664731">
@ -99,12 +99,15 @@
<h3>Sponsors</h3>
<p class="lede">We thank our Emperor Penguin sponsors for their
generous contribution to linux.conf.au 2017.</p>
<a href="#" class="btn btn__white">Other Sponsors</a>
<a href="/sponsors" class="btn btn__white">Other Sponsors</a>
</div>
<div class="l-footer--logos">
<img src="{% static 'lca2017/images/HPE.svg' %}" class="sponsor-logo" role="presentation">
<br />
<img src="{% static 'lca2017/images/IBM.svg' %}" class="sponsor-logo" role="presentation">
<div class="l-footer--logo">
<img src="{% static 'lca2017/images/HPE.svg' %}">
</div>
<div class="l-footer--logo">
<img src="{% static 'lca2017/images/IBM.svg' %}">
</div>
<br />
</div>
@ -115,9 +118,11 @@
{% endblock %}
</main>
{% block scripts %}
{% comment%}
<script src="{% static 'lca2017/js/app.js' %}"></script>
{% include "_scripts.html" %}
<script src="//cdnjs.cloudflare.com/ajax/libs/ace/1.2.0/ace.js"></script>
{% endcomment %}
{% block extra_script %}{% endblock %}
{% endblock %}
{% block extra_body_base %}

View file

@ -16,4 +16,9 @@
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>
<script src="{% static 'lca2017/js/load_editors.js' %}" type="text/javascript"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/ace/1.2.3/ace.js"></script>
<script src="{{ STATIC_URL }}js/jquery.formset.js"></script>
{% block scripts_extra %}
{% endblock %}
{% endblock %}

View file

@ -2,10 +2,14 @@ import cms_pages
import hashlib
import urllib
from decimal import Decimal
from django import template
from django.conf import settings
from django.contrib.staticfiles.templatetags import staticfiles
from easy_thumbnails.files import get_thumbnailer
from symposion.conference import models as conference_models
CONFERENCE_ID = settings.CONFERENCE_ID
register = template.Library()
@ -77,3 +81,14 @@ def header_paragraph(name):
@register.simple_tag()
def all_images():
return cms_pages.models.CustomImage.objects.all().order_by("title")
@register.filter()
def gst(amount):
two_places = Decimal(10) ** -2
return Decimal(amount / 11).quantize(two_places)
@register.simple_tag()
def conference_name():
return conference_models.Conference.objects.get(id=CONFERENCE_ID).title

View file

@ -31,18 +31,17 @@ urlpatterns = [
url(r'^cms/', include(wagtailadmin_urls)),
# Required by registrasion
url(r'^tickets/payments/', include('registripe.urls')),
url(r'^tickets/', include('registrasion.urls')),
url(r'^nested_admin/', include('nested_admin.urls')),
# Default catch-all for wagtail pages.
url(r'^', include(wagtail_urls)),
# Matches *NOTHING* -- remove once site_tree is fixed
url(r"^$", TemplateView.as_view(template_name="homepage.html"), name="home"),
# Required by registrasion
# url(r'^register/', include('registrasion.urls')),
# url(r'^nested_admin/', include('nested_admin.urls')),
# Demo payment gateway and related features
# url(r"^register/pinaxcon/", include("pinaxcon.registrasion.urls")),
]

View file

@ -4,6 +4,7 @@ pinax-theme-bootstrap==7.3.0
django-user-accounts==1.3.1
metron==1.3.7
pinax-eventlog==1.1.1
django-formset-js==0.5.0
dj-static==0.0.6
dj-database-url==0.4.0
#pinax-pages==0.4.2
@ -19,4 +20,6 @@ coverage==4.0.3
# Registrasion + Symposion
# Install via pip repo cloned locally
#registrasion==0.1.1
#https://github.com/lca2017/symposion/tarball/master#egg=symposion
-e git+https://github.com/chrisjrn/registrasion.git@master#egg=registrasion
-e git+https://github.com/chrisjrn/registrasion-stripe.git@master#egg=registrasion-stripe

View file

@ -513,6 +513,8 @@ textarea {
table {
border-collapse: collapse;
border-spacing: 0;
margin-top: 1em;
margin-bottom: 1em;
}
/* ==========================================================================
@ -656,7 +658,7 @@ a:hover, a:focus, a:active {
ul,
ol {
padding: 0;
margin: 0;
/* margin: 0; */
}
ul > li,
@ -869,6 +871,7 @@ table.alt tr:not(:last-of-type) {
.boolean-group {
border: 0;
list-style: none;
margin-bottom: 0;
}
.boolean-group--row {
@ -1017,6 +1020,11 @@ table.alt tr:not(:last-of-type) {
padding-top: 0.625rem 0;
}
.panel__compact.panel__first {
padding-top: 0 0;
padding-top: 0 0;
}
.panel__compact.panel__last {
padding-bottom: 10px;
padding-bottom: 0.625rem;
@ -1058,6 +1066,11 @@ table.alt tr:not(:last-of-type) {
.panel__compact {
padding-top: 5rem;
}
.panel__compact.panel__first {
padding-top: 0rem;
}
.panel__compact.panel__last {
padding-bottom: 2.5rem;
}
@ -1136,12 +1149,6 @@ table.alt tr:not(:last-of-type) {
}
}
.sponsor-logo {
width: 8em;
height: 4em;
margin: 0.5em;
}
.portrait {
position: relative;
overflow: hidden;
@ -1466,14 +1473,17 @@ table.alt tr:not(:last-of-type) {
max-width: 100%;
-ms-flex-preferred-size: 45%;
flex-basis: 45%;
margin-top: 1em;
margin-bottom: 1em;
}
.l-footer--logo > svg {
.l-footer--logo > svg, .l-footer--logo > img {
display: block;
width: 100%;
height: 100%;
}
.l-footer__alt {
background-color: #0c486c;
color: white;
@ -1566,6 +1576,36 @@ table.alt tr:not(:last-of-type) {
padding: 20px;
}
.fieldWrapper {
margin-top: 1em;
margin-bottom: 1em;
}
.has-errors {
margin-bottom: 0.5em;
padding: 0.5em;
background-color: #ebe0e0;
}
.errorlist {
margin-top: 0.1em;
list-style-type: none;
color: #6c0c0c;
}
.text-right {
text-align: right;
}
.vertical-small {
margin-top: 1em;
}
.vertical-bigger {
margin-top: 2em;
}
/* ------------------------------------------------------------------------------------------------
Shame

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 KiB

View file

@ -0,0 +1,79 @@
AU_STATES = {
"TAS": "Tasmania",
"ACT": "Australian Capital Territory",
"NSW": "New South Wales",
"NT": "Northern Territory",
"QLD": "Queensland",
"VIC": "Victoria",
"SA": "South Australia",
"WA": "Western Australia",
}
function profile_form() {
selects = $("select");
for (var i=0; i<selects.length; i++) {
select = selects[i];
id = select.id;
if (!id.endsWith("country")) {
continue;
}
parts = id.split("-");
_id = parts[parts.length - 1];
prefix = parts.slice(0, parts.length - 1).join("-");
mutate_state(select, prefix);
$(select).change(function() { mutate_state(select, prefix) })
}
}
function mutate_state(country_select, prefix) {
id = prefix + "-state";
var $state = $("#" + id);
var $select = $(country_select);
var $parent = $state.parent();
state = $state.val();
state_field_name = $state.attr("name");
country = $select.val()
$state.remove();
console.log($parent);
if (country == "AU") {
$p = $("<select>");
$p.append($("<option>").attr({
"value": "",
}).text("Select a state..."));
for (var state_key in AU_STATES) {
$opt = $("<option>").attr({
"value": state_key,
});
$opt.text(AU_STATES[state_key]);
$p.append($opt);
}
$parent.attr("class", "form-field select");
} else {
// state textfield
$p = $("<input>").attr({
"type": "text",
"maxlength": 256,
});
$parent.attr("class", "form-field");
}
$p.attr({
"id": id,
"name": state_field_name,
});
$p.val(state);
$parent.append($p);
}
function __defer_profile_form() {
if (window.jQuery) {
profile_form();
} else {
setTimeout(__defer_profile_form(), 50);
}
}
__defer_profile_form();