From 54fceededa3f66e8f45f5b3e9af878c2cad379ca Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sun, 24 Sep 2017 20:40:19 +1000
Subject: [PATCH 01/40] Just one void invoices button is sufficient thanks

And yes, this is changing it back to a button; having it as a link
isn't quite what we want.
---
 pinaxcon/templates/dashboard.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pinaxcon/templates/dashboard.html b/pinaxcon/templates/dashboard.html
index f681707a..70d0084b 100644
--- a/pinaxcon/templates/dashboard.html
+++ b/pinaxcon/templates/dashboard.html
@@ -125,9 +125,9 @@
                                 <a href="{% url "invoice" invoice.id %}" >Invoice {{ invoice.id }}</a>
                                 - ${{ invoice.value }} ({{ invoice.get_status_display }})
                                 </li>
-                                <a id="toggle-void-invoices" href="" onclick="toggleVoidInvoices();">Show void invoices</a>
                             {% endfor %}
                           </ul>
+                          <a id="toggle-void-invoices" href="#" onclick="toggleVoidInvoices();">Show void invoices</a>
                         </div>
                       </div>
                     </div>

From fbdf841f334cb7a7a2097e65c182ac9d59ba76eb Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Wed, 27 Sep 2017 18:31:53 +1000
Subject: [PATCH 02/40] Reorder dockerfile

This creates more intermediates, but moves those that are less likely
to change to the top of the file. In theory this will produce faster
builds on a developer's machine as they won't need to apt-get update
every time.
---
 docker/Dockerfile | 16 ++++++++++++----
 1 file changed, 12 insertions(+), 4 deletions(-)

diff --git a/docker/Dockerfile b/docker/Dockerfile
index 688276bf..9d24545c 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -1,6 +1,8 @@
 FROM python:3.6
 
-COPY constraints.txt requirements.txt /reqs/
+
+RUN set -ex \
+    && apt-get update
 
 RUN set -ex \
     && buildDeps=' \
@@ -15,12 +17,18 @@ RUN set -ex \
         libmemcached-dev \
         libsasl2-dev \
     ' \
-    && apt-get update \
+
     && apt-get install -y git xmlsec1 libmysqlclient18 \
     && apt-get install -y $buildDeps --no-install-recommends \
-    && rm -rf /var/lib/apt/lists/* \
+    && rm -rf /var/lib/apt/lists/*
+
+RUN set -ex \
+    && pip install uwsgi
+
+COPY constraints.txt requirements.txt /reqs/
+
+RUN set -ex \
     && pip install --no-cache-dir -r /reqs/requirements.txt -c /reqs/constraints.txt \
-    && pip install uwsgi \
     && apt-get purge -y --auto-remove $buildDeps \
     && rm -rf /usr/src/python ~/.cache
 

From 5409d4974aedf5d01e50d72770eb53746b5029ad Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Wed, 27 Sep 2017 23:46:13 +1000
Subject: [PATCH 03/40] The 2018ificiation

I think this removes most references to "hobart", "pycon", and "2017"

There are still some references to some images that we don't have a
replacement for.
---
 pinaxcon/registrasion/admin.py                |   5 +
 .../management/commands/populate_inventory.py |   4 +-
 .../migrations/0006_auto_20170927_2301.py     |  26 +++++
 pinaxcon/registrasion/models.py               |  13 ++-
 pinaxcon/templates/_form_snippet.html         |   2 +-
 pinaxcon/templates/content_page.html          |   2 +-
 pinaxcon/templates/dashboard.html             |   4 +-
 .../registrasion/_invoice_details.html        |   4 +-
 .../registrasion/guided_registration.html     |   2 +-
 pinaxcon/templates/registrasion/invoice.html  |  10 +-
 .../registrasion/product_category.html        |   2 +-
 .../templates/registrasion/profile_form.html  |   2 +-
 pinaxcon/templates/registrasion/review.html   |   4 +-
 .../stripe/credit_card_payment.html           |   2 +-
 .../symposion/proposals/_proposal_fields.html |   2 +-
 .../templates/symposion/schedule/_grid.html   |   2 +-
 .../schedule/presentation_detail.html         |   4 +-
 .../symposion/schedule/public_base.html       |   2 +-
 .../schedule/schedule_conference.html         |  16 +--
 .../symposion/schedule/schedule_detail.html   |   2 +-
 .../symposion/schedule/schedule_list.html     |   2 +-
 .../symposion/speakers/speaker_profile.html   |   2 +-
 .../symposion/sponsorship/_sponsor_link.html  |   2 +-
 .../templates/symposion/sponsorship/list.html |   2 +-
 .../{lca2017_tags.py => lca2018_tags.py}      |   4 +-
 pinaxcon/templatetags/pyconau2017_tags.py     | 100 ------------------
 26 files changed, 80 insertions(+), 142 deletions(-)
 create mode 100644 pinaxcon/registrasion/admin.py
 create mode 100644 pinaxcon/registrasion/migrations/0006_auto_20170927_2301.py
 rename pinaxcon/templatetags/{lca2017_tags.py => lca2018_tags.py} (93%)
 delete mode 100644 pinaxcon/templatetags/pyconau2017_tags.py

diff --git a/pinaxcon/registrasion/admin.py b/pinaxcon/registrasion/admin.py
new file mode 100644
index 00000000..09679267
--- /dev/null
+++ b/pinaxcon/registrasion/admin.py
@@ -0,0 +1,5 @@
+from django.contrib import admin
+
+from .models import PastEvent
+
+admin.site.register(PastEvent)
diff --git a/pinaxcon/registrasion/management/commands/populate_inventory.py b/pinaxcon/registrasion/management/commands/populate_inventory.py
index ae2a09e6..eb0011e0 100644
--- a/pinaxcon/registrasion/management/commands/populate_inventory.py
+++ b/pinaxcon/registrasion/management/commands/populate_inventory.py
@@ -13,7 +13,7 @@ from symposion import proposals
 
 class Command(BaseCommand):
 
-    help = 'Populates the inventory with the LCA2017 inventory model'
+    help = 'Populates the inventory with the LCA2018 inventory model'
 
     def add_arguments(self, parser):
         pass
@@ -111,7 +111,7 @@ class Command(BaseCommand):
             inv.Category,
             ("name",),
             name="T-Shirt",
-            description="Commemorative conference t-shirts, featuring the"
+            description="Commemorative conference t-shirts, featuring the "
                         "linux.conf.au 2018 artwork.",
             required=False,
             render_type=inv.Category.RENDER_TYPE_ITEM_QUANTITY,
diff --git a/pinaxcon/registrasion/migrations/0006_auto_20170927_2301.py b/pinaxcon/registrasion/migrations/0006_auto_20170927_2301.py
new file mode 100644
index 00000000..98330ac6
--- /dev/null
+++ b/pinaxcon/registrasion/migrations/0006_auto_20170927_2301.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2017-09-27 13:01
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('pinaxcon_registrasion', '0005_auto_20170702_2233'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='attendeeprofile',
+            name='future_conference',
+            field=models.BooleanField(default=False, help_text='Select to have your login details made available to future Linux Australia conferences who share the same Single Sign On system.', verbose_name='Reuse my login for future Linux Australia conferences?'),
+            preserve_default=False,
+        ),
+        migrations.AlterField(
+            model_name='attendeeprofile',
+            name='lca_chat',
+            field=models.BooleanField(help_text='lca2018-chat is a high-traffic mailing list used by attendees during the week of the conference for general discussion.', verbose_name='Subscribe to the lca2018-chat list'),
+        ),
+    ]
diff --git a/pinaxcon/registrasion/models.py b/pinaxcon/registrasion/models.py
index f30af712..d341738d 100644
--- a/pinaxcon/registrasion/models.py
+++ b/pinaxcon/registrasion/models.py
@@ -179,13 +179,22 @@ class AttendeeProfile(rego.AttendeeProfileBase):
     )
 
     lca_chat = models.BooleanField(
-        verbose_name="Subscribe to the lca2017-chat list",
-        help_text="lca2017-chat is a high-traffic mailing list used by "
+        verbose_name="Subscribe to the lca2018-chat list",
+        help_text="lca2018-chat is a high-traffic mailing list used by "
                   "attendees during the week of the conference for general "
                   "discussion.",
         blank=True,
     )
 
+    future_conference = models.BooleanField(
+        verbose_name = "Reuse my login for future Linux Australia conferences?",
+        help_text="Select to have your login details made available to future "
+                  "Linux Australia conferences who share the same Single Sign "
+                  "On system.",
+        blank=True,
+    )
+
+
     past_lca = models.ManyToManyField(
         PastEvent,
         verbose_name="Which past linux.conf.au events have you attended?",
diff --git a/pinaxcon/templates/_form_snippet.html b/pinaxcon/templates/_form_snippet.html
index 7d64b937..1e5193a3 100644
--- a/pinaxcon/templates/_form_snippet.html
+++ b/pinaxcon/templates/_form_snippet.html
@@ -1,4 +1,4 @@
-{% load pyconau2017_tags %}
+{% load lca2018_tags %}
 {% load bootstrap %}
 
 {% if form.non_field_errors %}
diff --git a/pinaxcon/templates/content_page.html b/pinaxcon/templates/content_page.html
index 1b87f005..7fb76a8e 100644
--- a/pinaxcon/templates/content_page.html
+++ b/pinaxcon/templates/content_page.html
@@ -1,7 +1,7 @@
 {% extends "site_base.html" %}
 {% load staticfiles %}
 
-{% load pyconau2017_tags %}
+{% load lca2018_tags %}
 
 
 {% load i18n %}
diff --git a/pinaxcon/templates/dashboard.html b/pinaxcon/templates/dashboard.html
index 70d0084b..eab991c5 100644
--- a/pinaxcon/templates/dashboard.html
+++ b/pinaxcon/templates/dashboard.html
@@ -5,7 +5,7 @@
 {% load review_tags %}
 {% load teams_tags %}
 {% load registrasion_tags %}
-{% load pyconau2017_tags %}
+{% load lca2018_tags %}
 {% load staticfiles %}
 
 
@@ -127,7 +127,7 @@
                                 </li>
                             {% endfor %}
                           </ul>
-                          <a id="toggle-void-invoices" href="#" onclick="toggleVoidInvoices();">Show void invoices</a>
+                          <button id="toggle-void-invoices" onclick="toggleVoidInvoices();">Show void invoices</button>
                         </div>
                       </div>
                     </div>
diff --git a/pinaxcon/templates/registrasion/_invoice_details.html b/pinaxcon/templates/registrasion/_invoice_details.html
index 12142037..0eba3c02 100644
--- a/pinaxcon/templates/registrasion/_invoice_details.html
+++ b/pinaxcon/templates/registrasion/_invoice_details.html
@@ -1,12 +1,12 @@
 {% load registrasion_tags %}
-{% load pyconau2017_tags %}
+{% load lca2018_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.pyconau2017.org">team@hobart.pyconau2017.org</a>
+  Enquiries: please e-mail <a href="mailto:team@lca2018.org">team@lca2018.org</a>
 </p>
 
 <ul>
diff --git a/pinaxcon/templates/registrasion/guided_registration.html b/pinaxcon/templates/registrasion/guided_registration.html
index 71af3298..01927a6b 100644
--- a/pinaxcon/templates/registrasion/guided_registration.html
+++ b/pinaxcon/templates/registrasion/guided_registration.html
@@ -1,5 +1,5 @@
 {% extends "registrasion/base.html" %}
-{% load pyconau2017_tags %}
+{% load lca2018_tags %}
 
 {% block header_title %}Buy Your Ticket{% endblock %}
 {% block header_paragraph %}Step {{ current_step }} of {{ total_steps|add:1 }} &ndash; {{ title }} {% endblock %}
diff --git a/pinaxcon/templates/registrasion/invoice.html b/pinaxcon/templates/registrasion/invoice.html
index ebfadd9a..6e8a0812 100644
--- a/pinaxcon/templates/registrasion/invoice.html
+++ b/pinaxcon/templates/registrasion/invoice.html
@@ -1,15 +1,15 @@
 {% extends "registrasion/base.html" %}
 {% load registrasion_tags %}
-{% load pyconau2017_tags %}
+{% load lca2018_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>
+  <p>Monday 22 January&ndash;Friday 26 January 2018.</p>
+  <p>University of Technology Sydney, New South Wales, Australia.</p>
 {% endblock %}
 {% block header_inset_image %}{% illustration "tuz.svg" %}{% endblock %}
-{% block header_background_image %}{% static "pyconau2017/images/wp_bg_optimised.jpg" %}{% endblock %}
+{% block header_background_image %}{% static "lca2018/images/wp_bg_optimised.jpg" %}{% endblock %}
 {% block content %}
 
 
@@ -25,7 +25,7 @@
   {% 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 for this invoice.</p>
+    You can print that page for your records, or give this URL to your accounts department to pay for this invoice</p>
 
   <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>
diff --git a/pinaxcon/templates/registrasion/product_category.html b/pinaxcon/templates/registrasion/product_category.html
index 0cbd23ce..fa2e518a 100644
--- a/pinaxcon/templates/registrasion/product_category.html
+++ b/pinaxcon/templates/registrasion/product_category.html
@@ -1,6 +1,6 @@
 {% extends "registrasion/base.html" %}
 {% load registrasion_tags %}
-{% load pyconau2017_tags %}
+{% load lca2018_tags %}
 
 {% block header_title %}Product Category: {{ category.name }}{% endblock %}
 {% block header_inset_image %}{% illustration "lavender.svg" %}{% endblock %}
diff --git a/pinaxcon/templates/registrasion/profile_form.html b/pinaxcon/templates/registrasion/profile_form.html
index 5911874d..fe3fdc36 100644
--- a/pinaxcon/templates/registrasion/profile_form.html
+++ b/pinaxcon/templates/registrasion/profile_form.html
@@ -1,5 +1,5 @@
 {% extends "registrasion/base.html" %}
-{% load pyconau2017_tags %}
+{% load lca2018_tags %}
 
 {% block header_title %}Your profile{% endblock %}
 {% block header_paragraph %}
diff --git a/pinaxcon/templates/registrasion/review.html b/pinaxcon/templates/registrasion/review.html
index f582673b..9a9ef144 100644
--- a/pinaxcon/templates/registrasion/review.html
+++ b/pinaxcon/templates/registrasion/review.html
@@ -1,6 +1,6 @@
 {% extends "registrasion/base.html" %}
 {% load registrasion_tags %}
-{% load pyconau2017_tags %}
+{% load lca2018_tags %}
 
 {% block header_title %}Review your selection{% endblock %}
 {% block header_inset_image %}{% illustration "wineglass.svg" %}{% endblock %}
@@ -58,7 +58,7 @@
       the dashboard.</p>
 
   <div class="btn-group">
-    <a class="btn" href="{% url "checkout" %}">
+    <a class="btn btn-success" 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>
diff --git a/pinaxcon/templates/registrasion/stripe/credit_card_payment.html b/pinaxcon/templates/registrasion/stripe/credit_card_payment.html
index 33b1a9e0..c35b96bf 100644
--- a/pinaxcon/templates/registrasion/stripe/credit_card_payment.html
+++ b/pinaxcon/templates/registrasion/stripe/credit_card_payment.html
@@ -1,6 +1,6 @@
 {% extends "registrasion/base.html" %}
 {% load registrasion_tags %}
-{% load pyconau2017_tags %}
+{% load lca2018_tags %}
 
 
 {% block scripts %}
diff --git a/pinaxcon/templates/symposion/proposals/_proposal_fields.html b/pinaxcon/templates/symposion/proposals/_proposal_fields.html
index 86940b0b..dd9f4925 100644
--- a/pinaxcon/templates/symposion/proposals/_proposal_fields.html
+++ b/pinaxcon/templates/symposion/proposals/_proposal_fields.html
@@ -1,5 +1,5 @@
 {% load i18n %}
-{% load pyconau2017_tags %}
+{% load lca2018_tags %}
 
     <div>
       <label class="col-sm-2 col-lg-2">Submitted by</label>
diff --git a/pinaxcon/templates/symposion/schedule/_grid.html b/pinaxcon/templates/symposion/schedule/_grid.html
index f5899cd5..eee236b7 100644
--- a/pinaxcon/templates/symposion/schedule/_grid.html
+++ b/pinaxcon/templates/symposion/schedule/_grid.html
@@ -1,4 +1,4 @@
-{% load pyconau2017_tags %}
+{% load lca2018_tags %}
 <table class="calendar table table-bordered">
   <thead>
     <tr>
diff --git a/pinaxcon/templates/symposion/schedule/presentation_detail.html b/pinaxcon/templates/symposion/schedule/presentation_detail.html
index cd19cb40..16626ab8 100644
--- a/pinaxcon/templates/symposion/schedule/presentation_detail.html
+++ b/pinaxcon/templates/symposion/schedule/presentation_detail.html
@@ -1,6 +1,6 @@
 {% extends "symposion/schedule/public_base.html" %}
 
-{% load pyconau2017_tags %}
+{% load lca2018_tags %}
 {% load sitetree %}
 {% load staticfiles %}
 {% load thumbnail %}
@@ -11,7 +11,7 @@
 
 {% block header_inset_image %}{% with audience=presentation.proposal.get_target_audience_display %}{% if audience == "Business" %}{% illustration "falls.svg" %}{% elif audience == "Community" %}{% illustration "bridge.svg" %}{% elif audience == "Developer"%}{% illustration "hobart.svg" %}{% elif audience == "User" %}{% illustration "antarctica.svg" %}{% else %}{% illustration "casino.svg" %}{% endif %}{% endwith %}{% endblock %}
 
-{% block header_background_image %}{% presentation_bg_number presentation 4 as bg_number %}{% if bg_number == 0 %}{% static "pyconau2017/images/mt_anne_bg_optimised.jpg" %}{% elif bg_number == 1 %}{% static "pyconau2017/images/the_neck_bg_optimised.jpg" %}{% elif bg_number == 2 %}{% static "pyconau2017/images/snug_falls_bg_optimised.jpg" %}{% elif bg_number == 3 %}{% static "pyconau2017/images/sleepy_bay_bg_optimised.jpg" %}{% endif %}{% endblock %}
+{% block header_background_image %}{% presentation_bg_number presentation 4 as bg_number %}{% if bg_number == 0 %}{% static "lca2018/images/mt_anne_bg_optimised.jpg" %}{% elif bg_number == 1 %}{% static "lca2018/images/the_neck_bg_optimised.jpg" %}{% elif bg_number == 2 %}{% static "lca2018/images/snug_falls_bg_optimised.jpg" %}{% elif bg_number == 3 %}{% static "lca2018/images/sleepy_bay_bg_optimised.jpg" %}{% endif %}{% endblock %}
 
 {% block header_title %}{{ presentation.title }}{% endblock %}
 
diff --git a/pinaxcon/templates/symposion/schedule/public_base.html b/pinaxcon/templates/symposion/schedule/public_base.html
index 1de13708..e2b6668b 100644
--- a/pinaxcon/templates/symposion/schedule/public_base.html
+++ b/pinaxcon/templates/symposion/schedule/public_base.html
@@ -3,5 +3,5 @@
 {% load staticfiles %}
 
 {% comment %}
-{% block header_background_image %}{% static "pyconau2017/images/hobart_bg_optimised.jpg" %}{% endblock %}
+{% block header_background_image %}{% static "lca2018/images/hobart_bg_optimised.jpg" %}{% endblock %}
 {% endcomment %}
diff --git a/pinaxcon/templates/symposion/schedule/schedule_conference.html b/pinaxcon/templates/symposion/schedule/schedule_conference.html
index ee075e76..7b8d92bf 100644
--- a/pinaxcon/templates/symposion/schedule/schedule_conference.html
+++ b/pinaxcon/templates/symposion/schedule/schedule_conference.html
@@ -2,7 +2,7 @@
 
 {% load i18n %}
 {% load cache %}
-{% load pyconau2017_tags %}
+{% load lca2018_tags %}
 
 {% block head_title %}Conference Schedule{% endblock %}
 {% block header_title %}Conference Schedule{% endblock %}
@@ -57,16 +57,16 @@
     fragment = window.location.hash.toLowerCase().substring(1);
 
     if (!fragment) {
-      OFFSET = -11 * (60 * 60 * 1000); // Hobart is 11 hours ahead of UTC in Jan.
+      OFFSET = -11 * (60 * 60 * 1000); // Sydney is 11 hours ahead of UTC in Jan.
       JAN = 0; // because January is 0, not 1
 
       fragments = [
-        {"day": "monday", "time": Date.UTC(2017, JAN, 16)},
-        {"day": "tuesday", "time": Date.UTC(2017, JAN, 17)},
-        {"day": "wednesday", "time": Date.UTC(2017, JAN, 18)},
-        {"day": "thursday", "time": Date.UTC(2017, JAN, 19)},
-        {"day": "friday", "time": Date.UTC(2017, JAN, 20)},
-        {"day": "saturday", "time": Date.UTC(2017, JAN, 21)},
+        {"day": "monday", "time": Date.UTC(2018, JAN, 22)},
+        {"day": "tuesday", "time": Date.UTC(2018, JAN, 23)},
+        {"day": "wednesday", "time": Date.UTC(2018, JAN, 24)},
+        {"day": "thursday", "time": Date.UTC(2018, JAN, 25)},
+        {"day": "friday", "time": Date.UTC(2018, JAN, 26)},
+        {"day": "saturday", "time": Date.UTC(2018, JAN, 27)},
       ];
 
       now = new Date().getTime();
diff --git a/pinaxcon/templates/symposion/schedule/schedule_detail.html b/pinaxcon/templates/symposion/schedule/schedule_detail.html
index 0b83d66e..abb2b776 100644
--- a/pinaxcon/templates/symposion/schedule/schedule_detail.html
+++ b/pinaxcon/templates/symposion/schedule/schedule_detail.html
@@ -2,7 +2,7 @@
 
 {% load i18n %}
 {% load cache %}
-{% load pyconau2017_tags %}
+{% load lca2018_tags %}
 {% load sitetree %}
 
 {% block head_title %}{{ schedule.section }} Schedule{% endblock %}
diff --git a/pinaxcon/templates/symposion/schedule/schedule_list.html b/pinaxcon/templates/symposion/schedule/schedule_list.html
index 565a8ff0..f2ac9364 100644
--- a/pinaxcon/templates/symposion/schedule/schedule_list.html
+++ b/pinaxcon/templates/symposion/schedule/schedule_list.html
@@ -2,7 +2,7 @@
 
 {% load i18n %}
 {% load cache %}
-{% load pyconau2017_tags %}
+{% load lca2018_tags %}
 {% load sitetree %}
 
 {% block head_title %}Presentation Listing{% endblock %}
diff --git a/pinaxcon/templates/symposion/speakers/speaker_profile.html b/pinaxcon/templates/symposion/speakers/speaker_profile.html
index f1221e59..ee3b49ed 100644
--- a/pinaxcon/templates/symposion/speakers/speaker_profile.html
+++ b/pinaxcon/templates/symposion/speakers/speaker_profile.html
@@ -1,7 +1,7 @@
 {% extends "symposion/schedule/public_base.html" %}
 
 {% load i18n %}
-{% load pyconau2017_tags %}
+{% load lca2018_tags %}
 {% load thumbnail %}
 
 {% if speaker.photo %}
diff --git a/pinaxcon/templates/symposion/sponsorship/_sponsor_link.html b/pinaxcon/templates/symposion/sponsorship/_sponsor_link.html
index 0b9bcd8d..2859683d 100644
--- a/pinaxcon/templates/symposion/sponsorship/_sponsor_link.html
+++ b/pinaxcon/templates/symposion/sponsorship/_sponsor_link.html
@@ -1,4 +1,4 @@
-{% load pyconau2017_tags %}
+{% load lca2018_tags %}
 
 {% spaceless %}
 <a href="{{ sponsor.external_url }}">
diff --git a/pinaxcon/templates/symposion/sponsorship/list.html b/pinaxcon/templates/symposion/sponsorship/list.html
index cb5d1c5a..6b5c285a 100644
--- a/pinaxcon/templates/symposion/sponsorship/list.html
+++ b/pinaxcon/templates/symposion/sponsorship/list.html
@@ -1,7 +1,7 @@
 {% extends "site_base.html" %}
 
 {% load sponsorship_tags %}
-{% load pyconau2017_tags %}
+{% load lca2018_tags %}
 {% load i18n %}
 
 {% block head_title %}{% trans "About Our Sponsors" %}{% endblock %}
diff --git a/pinaxcon/templatetags/lca2017_tags.py b/pinaxcon/templatetags/lca2018_tags.py
similarity index 93%
rename from pinaxcon/templatetags/lca2017_tags.py
rename to pinaxcon/templatetags/lca2018_tags.py
index 4e142983..02b624a7 100644
--- a/pinaxcon/templatetags/lca2017_tags.py
+++ b/pinaxcon/templatetags/lca2018_tags.py
@@ -34,11 +34,9 @@ def proposal_permission(context, permname, proposal):
     return context.request.user.has_perm(perm)
 
 
-# {% load statictags %}{% static 'pyconau2017/images/svgs/illustrations/' %}{{ illustration }}
-
 @register.simple_tag(takes_context=False)
 def illustration(name):
-    return staticfiles.static('pyconau2017/images/svgs/illustrations/') + name
+    return staticfiles.static('lca2018/images/svgs/illustrations/') + name
 
 
 @register.simple_tag(takes_context=True)
diff --git a/pinaxcon/templatetags/pyconau2017_tags.py b/pinaxcon/templatetags/pyconau2017_tags.py
deleted file mode 100644
index 894b077f..00000000
--- a/pinaxcon/templatetags/pyconau2017_tags.py
+++ /dev/null
@@ -1,100 +0,0 @@
-import hashlib
-
-import os
-
-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
-from symposion.schedule.models import Track
-
-CONFERENCE_ID = settings.CONFERENCE_ID
-
-register = template.Library()
-
-
-@register.assignment_tag()
-def classname(ob):
-    return ob.__class__.__name__
-
-
-@register.simple_tag(takes_context=True)
-def can_manage(context, proposal):
-    return proposal_permission(context, "manage", proposal)
-
-
-@register.simple_tag(takes_context=True)
-def can_review(context, proposal):
-    return proposal_permission(context, "review", proposal)
-
-
-def proposal_permission(context, permname, proposal):
-    slug = proposal.kind.section.slug
-    perm = "reviews.can_%s_%s" % (permname, slug)
-    return context.request.user.has_perm(perm)
-
-
-# {% load statictags %}{% static 'pyconau2017/images/svgs/illustrations/' %}{{ illustration }}
-
-@register.simple_tag(takes_context=False)
-def illustration(name):
-    return staticfiles.static('pyconau2017/images/svgs/illustrations/') + name
-
-
-@register.simple_tag(takes_context=True)
-def speaker_photo(context, speaker, size):
-    ''' Provides the speaker profile, or else fall back to libravatar or gravatar. '''
-
-    if speaker.photo:
-        thumbnailer = get_thumbnailer(speaker.photo)
-        thumbnail_options = {'crop': True, 'size': (size, size)}
-        thumbnail = thumbnailer.get_thumbnail(thumbnail_options)
-        return thumbnail.url
-    else:
-        email = speaker.user.email.encode("utf-8")
-        md5sum = hashlib.md5(email.strip().lower()).hexdigest()
-        fallback_image = ("https://2017.pycon-au.org/site_media/static"
-                          "/pyconau23017/images/speaker-fallback-devil.jpg")
-        url = "https://secure.gravatar.com/avatar/%s?s=%d&d=%s" % (md5sum, size, fallback_image)
-
-        return url
-
-
-@register.simple_tag()
-def define(value):
-    return value
-
-
-@register.simple_tag()
-def presentation_bg_number(presentation, count):
-    return sum(ord(i) for i in presentation.title) % count
-
-
-@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
-
-
-@register.filter()
-def trackname(room, day):
-    try:
-        track_name = room.track_set.get(day=day).name
-    except Track.DoesNotExist:
-        track_name = None
-    return track_name
-
-
-@register.simple_tag()
-def sponsor_thumbnail(sponsor_logo):
-    if sponsor_logo is not None:
-        if sponsor_logo.upload:
-            return sponsor_logo.upload.url
-    return ""

From 26f40ce9040d02f0b3cf0d34f365832480a2ca4e Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Fri, 29 Sep 2017 14:36:56 +1000
Subject: [PATCH 04/40] git subrepo init
 --remote=git@gitlab.com:tchaypo/registrasion.git --branch=lca2018
 vendor/registrasion

subrepo:
  subdir:   "vendor/registrasion"
  merged:   "8851e20"
upstream:
  origin:   "git@gitlab.com:tchaypo/registrasion.git"
  branch:   "lca2018"
  commit:   "8851e20"
git-subrepo:
  version:  "0.3.1"
  origin:   "???"
  commit:   "???"
---
 vendor/registrasion/.gitrepo | 11 +++++++++++
 1 file changed, 11 insertions(+)
 create mode 100644 vendor/registrasion/.gitrepo

diff --git a/vendor/registrasion/.gitrepo b/vendor/registrasion/.gitrepo
new file mode 100644
index 00000000..27d6fe0d
--- /dev/null
+++ b/vendor/registrasion/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+	remote = git@gitlab.com:tchaypo/registrasion.git
+	branch = lca2018
+	commit = 8851e2076c071120ffe1dcc8bf99ac009a0fb1d9
+	parent = 7c4cde09de905a9cf10a5b6490eddd2fa1e11b2e
+	cmdver = 0.3.1

From 3c29e3db8c32f6b6083cb35c5cf2b12e3a954f31 Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Fri, 29 Sep 2017 22:24:54 +1000
Subject: [PATCH 05/40] git subrepo pull vendor/registrasion

subrepo:
  subdir:   "vendor/registrasion"
  merged:   "c1e194a"
upstream:
  origin:   "git@gitlab.com:tchaypo/registrasion.git"
  branch:   "lca2018"
  commit:   "c1e194a"
git-subrepo:
  version:  "0.3.1"
  origin:   "???"
  commit:   "???"
---
 vendor/registrasion/.gitignore                |  64 ++++
 vendor/registrasion/.gitrepo                  |   4 +-
 vendor/registrasion/CONTRIBUTING.rst          |  39 +++
 vendor/registrasion/README.rst                |  32 ++
 vendor/registrasion/design/design.md          | 298 ++++++++++++++++++
 vendor/registrasion/design/goals.md           |  55 ++++
 vendor/registrasion/docs/Makefile             | 231 ++++++++++++++
 vendor/registrasion/docs/conf.py              | 297 +++++++++++++++++
 vendor/registrasion/docs/django_settings.py   | 122 +++++++
 .../registrasion/docs/for-zookeepr-users.rst  |  22 ++
 vendor/registrasion/docs/index.rst            |  36 +++
 vendor/registrasion/docs/integration.rst      |  67 ++++
 vendor/registrasion/docs/inventory.rst        | 158 ++++++++++
 vendor/registrasion/docs/make.bat             | 281 +++++++++++++++++
 vendor/registrasion/docs/overview.rst         |  51 +++
 vendor/registrasion/docs/payments.rst         | 165 ++++++++++
 vendor/registrasion/docs/templates.rst        |  78 +++++
 vendor/registrasion/docs/views.rst            |  41 +++
 .../{ => registrasion}/__init__.py            |   0
 .../registrasion/{ => registrasion}/admin.py  |   0
 .../registrasion/{ => registrasion}/apps.py   |   0
 .../{ => registrasion}/contrib/__init__.py    |   0
 .../{ => registrasion}/contrib/mail.py        |   0
 .../controllers/__init__.py                   |   0
 .../{ => registrasion}/controllers/batch.py   |   0
 .../{ => registrasion}/controllers/cart.py    |   0
 .../controllers/category.py                   |   0
 .../controllers/conditions.py                 |   0
 .../controllers/credit_note.py                |   0
 .../controllers/discount.py                   |   0
 .../{ => registrasion}/controllers/flag.py    |   0
 .../{ => registrasion}/controllers/for_id.py  |   0
 .../{ => registrasion}/controllers/invoice.py |   0
 .../{ => registrasion}/controllers/item.py    |   0
 .../{ => registrasion}/controllers/product.py |   0
 .../{ => registrasion}/exceptions.py          |   0
 .../registrasion/{ => registrasion}/forms.py  |   0
 .../migrations/0001_initial.py                |   0
 .../migrations/0002_auto_20160822_0034.py     |   0
 .../migrations/0003_auto_20160904_0235.py     |   0
 ...004_groupmemberdiscount_groupmemberflag.py |   0
 .../migrations/0005_auto_20160905_0945.py     |   0
 .../migrations/0006_auto_20170526_1624.py     |  20 ++
 .../migrations/0006_auto_20170702_2233.py     |   0
 .../{ => registrasion}/migrations/__init__.py |   0
 .../{ => registrasion}/models/__init__.py     |   0
 .../{ => registrasion}/models/commerce.py     |   0
 .../{ => registrasion}/models/conditions.py   |   0
 .../{ => registrasion}/models/inventory.py    |   0
 .../{ => registrasion}/models/people.py       |   0
 .../{ => registrasion}/reporting/__init__.py  |   0
 .../{ => registrasion}/reporting/forms.py     |   0
 .../{ => registrasion}/reporting/reports.py   |   0
 .../{ => registrasion}/reporting/views.py     |   0
 .../registrasion/amend_registration.html      |   5 +
 .../registrasion/amend_registration_.html     |  81 +++++
 .../templates/registrasion/badges.html        |   5 +
 .../templates/registrasion/badges_.html       |  17 +
 .../templates/registrasion/base.html          |  24 ++
 .../registrasion/checkout_errors.html         |   7 +
 .../registrasion/checkout_errors_.html        |  35 ++
 .../templates/registrasion/credit_note.html   |   9 +
 .../templates/registrasion/credit_note_.html  | 103 ++++++
 .../registrasion/dashboard_widget.html        |  11 +
 .../registrasion/dashboard_widget_.html       | 135 ++++++++
 .../emails/invoice_created/message.html       |  13 +
 .../emails/invoice_created/subject.txt        |   1 +
 .../emails/invoice_updated/message.html       |   1 +
 .../emails/invoice_updated/subject.txt        |   1 +
 .../templates/registrasion/form.html          |   9 +
 .../registrasion/guided_registration.html     |   8 +
 .../registrasion/guided_registration_.html    |  55 ++++
 .../templates/registrasion/invoice.html       |  14 +
 .../registrasion/invoice/details.html         |  11 +
 .../registrasion/invoice/details_.html        | 111 +++++++
 .../registrasion/invoice/unpaid_notice.html   |  11 +
 .../templates/registrasion/invoice_.html      |  55 ++++
 .../registrasion/invoice_mailout.html         |   5 +
 .../registrasion/invoice_mailout_.html        |  32 ++
 .../registrasion/manual_payment.html          |   5 +
 .../registrasion/manual_payment_.html         |  33 ++
 .../registrasion/product_category.html        |   9 +
 .../registrasion/product_category_.html       |  80 +++++
 .../templates/registrasion/profile_form.html  |   5 +
 .../templates/registrasion/profile_form_.html |  25 ++
 .../templates/registrasion/report.html        |   5 +
 .../templates/registrasion/report_.html       |  62 ++++
 .../templates/registrasion/reports_list.html  |   5 +
 .../templates/registrasion/reports_list_.html |  22 ++
 .../templates/registrasion/review.html        |  10 +
 .../templates/registrasion/review_.html       | 143 +++++++++
 .../registrasion/snippets/category_list.html  |   7 +
 .../registrasion/snippets/discounts_list.html |  22 ++
 .../registrasion/snippets/items_list.html     |   7 +
 .../registrasion/snippets/payment_list.html   |  16 +
 .../templates/registrasion/voucher_code.html  |   5 +
 .../templates/registrasion/voucher_code_.html |  28 ++
 .../templatetags/__init__.py                  |   0
 .../templatetags/registrasion_tags.py         |   0
 .../{ => registrasion}/tests/__init__.py      |   0
 .../tests/controller_helpers.py               |   0
 .../{ => registrasion}/tests/patches.py       |   0
 .../{ => registrasion}/tests/test_batch.py    |   0
 .../{ => registrasion}/tests/test_cart.py     |   0
 .../{ => registrasion}/tests/test_ceilings.py |   0
 .../tests/test_credit_note.py                 |   0
 .../{ => registrasion}/tests/test_discount.py |   0
 .../{ => registrasion}/tests/test_flag.py     |   0
 .../tests/test_group_member.py                |   0
 .../{ => registrasion}/tests/test_helpers.py  |   0
 .../{ => registrasion}/tests/test_invoice.py  |   0
 .../{ => registrasion}/tests/test_refund.py   |   0
 .../{ => registrasion}/tests/test_speaker.py  |   0
 .../{ => registrasion}/tests/test_voucher.py  |   0
 .../registrasion/{ => registrasion}/urls.py   |   0
 .../registrasion/{ => registrasion}/util.py   |   0
 .../registrasion/{ => registrasion}/views.py  |   0
 vendor/registrasion/requirements/base.txt     |   2 +
 .../requirements/dependencies.txt             |   1 +
 vendor/registrasion/requirements/docs.txt     |   4 +
 vendor/registrasion/requirements/extern.txt   |   4 +
 vendor/registrasion/setup.cfg                 |   2 +
 vendor/registrasion/setup.py                  |  38 +++
 123 files changed, 3358 insertions(+), 2 deletions(-)
 create mode 100644 vendor/registrasion/.gitignore
 create mode 100644 vendor/registrasion/CONTRIBUTING.rst
 create mode 100644 vendor/registrasion/README.rst
 create mode 100644 vendor/registrasion/design/design.md
 create mode 100644 vendor/registrasion/design/goals.md
 create mode 100644 vendor/registrasion/docs/Makefile
 create mode 100644 vendor/registrasion/docs/conf.py
 create mode 100644 vendor/registrasion/docs/django_settings.py
 create mode 100644 vendor/registrasion/docs/for-zookeepr-users.rst
 create mode 100644 vendor/registrasion/docs/index.rst
 create mode 100644 vendor/registrasion/docs/integration.rst
 create mode 100644 vendor/registrasion/docs/inventory.rst
 create mode 100644 vendor/registrasion/docs/make.bat
 create mode 100644 vendor/registrasion/docs/overview.rst
 create mode 100644 vendor/registrasion/docs/payments.rst
 create mode 100644 vendor/registrasion/docs/templates.rst
 create mode 100644 vendor/registrasion/docs/views.rst
 rename vendor/registrasion/{ => registrasion}/__init__.py (100%)
 rename vendor/registrasion/{ => registrasion}/admin.py (100%)
 rename vendor/registrasion/{ => registrasion}/apps.py (100%)
 rename vendor/registrasion/{ => registrasion}/contrib/__init__.py (100%)
 rename vendor/registrasion/{ => registrasion}/contrib/mail.py (100%)
 rename vendor/registrasion/{ => registrasion}/controllers/__init__.py (100%)
 rename vendor/registrasion/{ => registrasion}/controllers/batch.py (100%)
 rename vendor/registrasion/{ => registrasion}/controllers/cart.py (100%)
 rename vendor/registrasion/{ => registrasion}/controllers/category.py (100%)
 rename vendor/registrasion/{ => registrasion}/controllers/conditions.py (100%)
 rename vendor/registrasion/{ => registrasion}/controllers/credit_note.py (100%)
 rename vendor/registrasion/{ => registrasion}/controllers/discount.py (100%)
 rename vendor/registrasion/{ => registrasion}/controllers/flag.py (100%)
 rename vendor/registrasion/{ => registrasion}/controllers/for_id.py (100%)
 rename vendor/registrasion/{ => registrasion}/controllers/invoice.py (100%)
 rename vendor/registrasion/{ => registrasion}/controllers/item.py (100%)
 rename vendor/registrasion/{ => registrasion}/controllers/product.py (100%)
 rename vendor/registrasion/{ => registrasion}/exceptions.py (100%)
 rename vendor/registrasion/{ => registrasion}/forms.py (100%)
 rename vendor/registrasion/{ => registrasion}/migrations/0001_initial.py (100%)
 rename vendor/registrasion/{ => registrasion}/migrations/0002_auto_20160822_0034.py (100%)
 rename vendor/registrasion/{ => registrasion}/migrations/0003_auto_20160904_0235.py (100%)
 rename vendor/registrasion/{ => registrasion}/migrations/0004_groupmemberdiscount_groupmemberflag.py (100%)
 rename vendor/registrasion/{ => registrasion}/migrations/0005_auto_20160905_0945.py (100%)
 create mode 100644 vendor/registrasion/registrasion/migrations/0006_auto_20170526_1624.py
 rename vendor/registrasion/{ => registrasion}/migrations/0006_auto_20170702_2233.py (100%)
 rename vendor/registrasion/{ => registrasion}/migrations/__init__.py (100%)
 rename vendor/registrasion/{ => registrasion}/models/__init__.py (100%)
 rename vendor/registrasion/{ => registrasion}/models/commerce.py (100%)
 rename vendor/registrasion/{ => registrasion}/models/conditions.py (100%)
 rename vendor/registrasion/{ => registrasion}/models/inventory.py (100%)
 rename vendor/registrasion/{ => registrasion}/models/people.py (100%)
 rename vendor/registrasion/{ => registrasion}/reporting/__init__.py (100%)
 rename vendor/registrasion/{ => registrasion}/reporting/forms.py (100%)
 rename vendor/registrasion/{ => registrasion}/reporting/reports.py (100%)
 rename vendor/registrasion/{ => registrasion}/reporting/views.py (100%)
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/amend_registration.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/amend_registration_.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/badges.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/badges_.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/base.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/checkout_errors.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/checkout_errors_.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/credit_note.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/credit_note_.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/dashboard_widget.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/dashboard_widget_.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/emails/invoice_created/message.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/emails/invoice_created/subject.txt
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/emails/invoice_updated/message.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/emails/invoice_updated/subject.txt
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/form.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/guided_registration.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/guided_registration_.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/invoice.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/invoice/details.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/invoice/details_.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/invoice/unpaid_notice.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/invoice_.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/invoice_mailout.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/invoice_mailout_.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/manual_payment.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/manual_payment_.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/product_category.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/product_category_.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/profile_form.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/profile_form_.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/report.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/report_.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/reports_list.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/reports_list_.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/review.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/review_.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/snippets/category_list.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/snippets/discounts_list.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/snippets/items_list.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/snippets/payment_list.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/voucher_code.html
 create mode 100644 vendor/registrasion/registrasion/templates/registrasion/voucher_code_.html
 rename vendor/registrasion/{ => registrasion}/templatetags/__init__.py (100%)
 rename vendor/registrasion/{ => registrasion}/templatetags/registrasion_tags.py (100%)
 rename vendor/registrasion/{ => registrasion}/tests/__init__.py (100%)
 rename vendor/registrasion/{ => registrasion}/tests/controller_helpers.py (100%)
 rename vendor/registrasion/{ => registrasion}/tests/patches.py (100%)
 rename vendor/registrasion/{ => registrasion}/tests/test_batch.py (100%)
 rename vendor/registrasion/{ => registrasion}/tests/test_cart.py (100%)
 rename vendor/registrasion/{ => registrasion}/tests/test_ceilings.py (100%)
 rename vendor/registrasion/{ => registrasion}/tests/test_credit_note.py (100%)
 rename vendor/registrasion/{ => registrasion}/tests/test_discount.py (100%)
 rename vendor/registrasion/{ => registrasion}/tests/test_flag.py (100%)
 rename vendor/registrasion/{ => registrasion}/tests/test_group_member.py (100%)
 rename vendor/registrasion/{ => registrasion}/tests/test_helpers.py (100%)
 rename vendor/registrasion/{ => registrasion}/tests/test_invoice.py (100%)
 rename vendor/registrasion/{ => registrasion}/tests/test_refund.py (100%)
 rename vendor/registrasion/{ => registrasion}/tests/test_speaker.py (100%)
 rename vendor/registrasion/{ => registrasion}/tests/test_voucher.py (100%)
 rename vendor/registrasion/{ => registrasion}/urls.py (100%)
 rename vendor/registrasion/{ => registrasion}/util.py (100%)
 rename vendor/registrasion/{ => registrasion}/views.py (100%)
 create mode 100644 vendor/registrasion/requirements/base.txt
 create mode 100644 vendor/registrasion/requirements/dependencies.txt
 create mode 100644 vendor/registrasion/requirements/docs.txt
 create mode 100644 vendor/registrasion/requirements/extern.txt
 create mode 100644 vendor/registrasion/setup.cfg
 create mode 100644 vendor/registrasion/setup.py

diff --git a/vendor/registrasion/.gitignore b/vendor/registrasion/.gitignore
new file mode 100644
index 00000000..629c0e69
--- /dev/null
+++ b/vendor/registrasion/.gitignore
@@ -0,0 +1,64 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*,cover
+.hypothesis/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Grumble OSX Grumble
+
+.DS_Store
+*/.DS_Store
diff --git a/vendor/registrasion/.gitrepo b/vendor/registrasion/.gitrepo
index 27d6fe0d..cc64f22f 100644
--- a/vendor/registrasion/.gitrepo
+++ b/vendor/registrasion/.gitrepo
@@ -6,6 +6,6 @@
 [subrepo]
 	remote = git@gitlab.com:tchaypo/registrasion.git
 	branch = lca2018
-	commit = 8851e2076c071120ffe1dcc8bf99ac009a0fb1d9
-	parent = 7c4cde09de905a9cf10a5b6490eddd2fa1e11b2e
+	commit = c1e194aef92e4c06a8855fad22ca819c08736dad
+	parent = dd8a42e9f67cfebc39347cb87bacf79046bfbfec
 	cmdver = 0.3.1
diff --git a/vendor/registrasion/CONTRIBUTING.rst b/vendor/registrasion/CONTRIBUTING.rst
new file mode 100644
index 00000000..dc3621bd
--- /dev/null
+++ b/vendor/registrasion/CONTRIBUTING.rst
@@ -0,0 +1,39 @@
+Contributing to Registrasion
+============================
+
+I'm glad that you're interested in helping make Registrasion better! Thanks! This guide is meant to help make sure your contributions to the project fit in well.
+
+Making a contribution
+---------------------
+
+This project makes use of GitHub issues to track pending work, so new features that need implementation can be found there. If you think a new feature needs to be added, raise an issue for discussion before submitting a Pull Request.
+
+
+Code Style
+----------
+
+We use PEP8. Your code should pass checks by ``flake8`` with no errors or warnings before it will be merged.
+
+We use `Google-style docstrings <http://sphinxcontrib-napoleon.readthedocs.org/en/latest/example_google.html>`_, primarily because they're far far more readable than ReST docstrings. New functions should have complete docstrings, so that new contributors have a chance of understanding how the API works.
+
+
+Structure
+---------
+
+Django Models live in ``registrasion/models``; we separate our models out into separate files, because there's a lot of them. Models are grouped by logical functionality.
+
+Actions that operate on Models live in ``registrasion/controllers``.
+
+
+Testing
+-------
+
+Functionality that lives in ``regsistrasion/controllers`` was developed in a test-driven fashion, which is sensible, given it's where most of the business logic for registrasion lives. If you're changing behaviour of a controller, either submit a test with your pull request, or modify an existing test.
+
+
+Documentation
+-------------
+
+Registrasion aims towards high-quality documentation, so that conference registration managers can understand how the system works, and so that webmasters working for conferences understand how the system fits together. Make sure that you have docstrings :)
+
+The documentation is written in Australian English: *-ise* and not *-ize*, *-our* and not *-or*; *vegemite* and not *peanut butter*, etc etc etc.
diff --git a/vendor/registrasion/README.rst b/vendor/registrasion/README.rst
new file mode 100644
index 00000000..1a18a7c0
--- /dev/null
+++ b/vendor/registrasion/README.rst
@@ -0,0 +1,32 @@
+Registrasion
+============
+
+**Registra** (tion for Sympo) **sion**. A conference registration app for Django,
+letting conferences big and small sell tickets from within Symposion.
+
+Symposion
+---------
+``symposion`` is an Open Source conference management solution built with Pinax
+apps for Django. For more information, see https://github.com/pinax/symposion.
+
+registrasion
+------------
+``registrasion`` is a registration package that you use alongside Symposion. It
+handles inventory management, as well as complex product inclusions, automatic
+calculation of discounts, and invoicing. Payment of invoices can be faciliated
+by manual filings of payments by staff, or through plugging in a payment app.
+
+Initial development of ``registrasion`` was funded with the generous support of
+the Python Software Foundation.
+
+Quickstart
+----------
+``registrasion`` is a Django app. You will need to create a Django project to
+customize and manage your Registrasion and Symposion installation. A
+demonstration app project with templates is available at
+https://github.com/chrisjrn/registrasion-demo
+
+Documentation
+-------------
+The documentation for ``registrasion`` is available at
+http://registrasion.readthedocs.org/
diff --git a/vendor/registrasion/design/design.md b/vendor/registrasion/design/design.md
new file mode 100644
index 00000000..116ed2ca
--- /dev/null
+++ b/vendor/registrasion/design/design.md
@@ -0,0 +1,298 @@
+# Logic
+
+## Definitions
+- User has one 'active Cart' at a time. The Cart remains active until a paid Invoice is attached to it.
+- A 'paid Cart' is a Cart with a paid Invoice attached to it, where the Invoice has not been voided.
+- An unpaid Cart is 'reserved' if
+ - CURRENT_TIME - "Time last updated" <= max(reservation duration of Products in Cart),
+ - A Voucher was added and CURRENT_TIME - "Time last updated" < VOUCHER_RESERVATION_TIME (15 minutes?)
+- An Item is 'reserved' if:
+  - it belongs to a reserved Cart
+  - it belongs to a paid Cart
+- A Cart can have any number of Items added to it, subject to limits.
+
+
+## Entering Vouchers
+- Vouchers are attached to Carts
+- A user can enter codes for as many different Vouchers as they like.
+- A Voucher is added to the Cart if the number of paid or reserved Carts containing the Voucher is less than the "total available" for the voucher.
+- A cart is invalid if it contains a voucher that has been overused
+
+
+## Are products available?
+
+- Availability is determined by the number of items we want to add to the cart: items_to_add
+
+- If items_to_add + count(Product in their active and paid Carts) > "Limit per user" for the Product, the Product is "unavailable".
+- If the Product belongs to an exhausted Ceiling, the Product is "unavailable".
+- Otherwise, the product is available
+
+
+## Displaying Products:
+
+- If there is at least one mandatory EnablingCondition attached to the Product, display it only if all EnablingConditions are met
+- If there is at least one EnablingCondition attached to the Product, display it only if at least one EnablingCondition is met
+- If there are zero EnablingConditions attached to the Product, display it
+- If the product is not available for items_to_add=0, mark it as "unavailable"
+
+- If the Product is displayed and available, its price is the price for the Product, minus the greatest Discount available to this Cart and Product
+
+- The product is displayed per the rendering characteristics of the Category it belongs to
+
+
+## Displaying Categories
+
+- If the Category contains only "unavailable" Products, mark it as "unavailable"
+- If the Category contains no displayed Products, do not display the Category
+- If the Category contains at least one EnablingCondition, display it only if at least one EnablingCondition is met
+- If the Category contains no EnablingConditions, display it
+
+
+## Exhausting Ceilings
+
+- Exhaustion is determined by the number of items we want to add to the cart: items_to_add
+
+- A ceiling is exhausted if:
+ - Its start date has not yet been reached
+ - Its end date has been exceeded
+ - items_to_add + sum(paid and reserved Items for each Product in the ceiling) > Total available
+
+
+## Applying Discounts
+
+- Discounts only apply to the current cart
+- Discounts can be applied to multiple carts until the user has exhausted the quantity for each product attached to the discount.
+- Only one discount discount can be applied to each single item. Discounts are applied as follows:
+ - All non-exhausted discounts for the product or its category are ordered by value
+ - The highest discount is applied for the lower of the quantity of the product in the cart, or the remaining quantity from this discount
+ - If the quantity remaining is non-zero, apply the next available discount
+
+- Individual discount objects should not contain more than one DiscountForProduct for the same product
+- Individual discount objects should not contain more than one DiscountForCategory for the same category
+- Individual discount objects should not contain a discount for both a product and its category
+
+
+## Adding Items to the Cart
+
+- Products that are not displayed may not be added to a Cart
+- The requested number of items must be available for those items to be added to a Cart
+- If a different price applies to a Product when it is added to a cart, add at the new price, and display an alert to the user
+- If a discount is used when adding a Product to the cart, add the discount as well
+- Adding an item resets the "Time last updated" for the cart
+- Each time carts have items added or removed, the revision number is updated
+
+
+## Generating an invoice
+
+- User can ask to 'check out' the active Cart. Doing so generates an Invoice. The invoice corresponds to a revision number of the cart.
+- Checking out the active Cart resets the "Time last updated" for the cart.
+- The invoice represents the current state of the cart.
+- If the revision number for the cart is different to the cart's revision number for the invoice, the invoice is void.
+- The invoice is void if
+
+
+## Paying an invoice
+
+- A payment can only be attached to an invoice if all of the items in it are available at the time payment is processed
+
+### One-Shot
+- Update the "Time last updated" for the cart based on the expected time it takes for a payment to complete
+- Verify that all items are available, and if so:
+- Proceed to make payment
+- Apply payment record from amount received
+
+
+### Authorization-based approach:
+- Capture an authorization on the card
+- Verify that all items are available, and if so:
+- Apply payment record
+- Take payment
+
+
+# Registration workflow:
+
+## User has not taken a guided registration yet:
+
+User is shown two options:
+
+1. Undertake guided registration ("for current user")
+1. Purchase vouchers
+
+
+## User has not purchased a ticket, and wishes to:
+
+This gives the user a guided registration process.
+
+1. Take list of categories, sorted by display order, and display the next lowest enabled & available category
+1. Take user to category page
+1. User can click "back" to go to previous screen, or "next" to go the next lowest enabled & available category
+
+Once all categories have been seen:
+1. Ask for badge information -- badge information is *not* the same as the invoicee.
+1. User is taken to the "user has purchased a ticket" workflow
+
+
+## User is buying vouchers
+TODO: Consider separate workflow for purchasing ticket vouchers.
+
+
+## User has completed a guided registration or purchased vouchers
+
+1. Show list of products that are pending purchase.
+1. Show list of categories + badge information, as well as 'checkout' button if the user has items in their current cart
+
+
+## Category page
+
+- User can enter a voucher at any time
+- User is shown the list of products that have been paid for
+- User has the option to add/remove products that are in the current cart
+
+
+## Checkout
+
+1. Ask for invoicing details (pre-fill from previous invoice?)
+1. Ask for payment
+
+
+# User Models
+
+- Profile:
+ - User
+ - Has done guided registration?
+ - Badge
+ -
+
+## Transaction Models
+
+- Cart:
+ - User
+ - {Items}
+ - {Voucher}
+ - {DiscountItems}
+ - Time last updated
+ - Revision Number
+ - Active?
+
+- Item
+ - Product
+ - Quantity
+
+- DiscountItem
+ - Product
+ - Discount
+ - Quantity
+
+- Invoice:
+ - Invoice number
+ - User
+ - Cart
+ - Cart Revision
+ - {Line Items}
+ - (Invoice Details)
+ - {Payments}
+ - Voided?
+
+- LineItem
+ - Description
+ - Quantity
+ - Price
+
+- Payment
+ - Time
+ - Amount
+ - Reference
+
+
+## Inventory Model
+
+- Product:
+ - Name
+ - Description
+ - Category
+ - Price
+ - Limit per user
+ - Reservation duration
+ - Display order
+ - {Ceilings}
+
+
+- Voucher
+ - Description
+ - Code
+ - Total available
+
+
+- Category?
+ - Name
+ - Description
+ - Display Order
+ - Rendering Style
+
+
+## Product Modifiers
+
+- Discount:
+ - Description
+ - {DiscountForProduct}
+ - {DiscountForCategory}
+
+ - Discount Types:
+    - TimeOrStockLimitDiscount:
+     * A discount that is available for a limited amount of time, e.g. Early Bird sales *
+     - Start date
+     - End date
+     - Total available
+
+    - VoucherDiscount:
+     * A discount that is available to a specific voucher *
+     - Voucher
+
+    - RoleDiscount
+     * A discount that is available to a specific role *
+     - Role
+
+    - IncludedProductDiscount:
+     * A discount that is available because another product has been purchased *
+     - {Parent Product}
+
+- DiscountForProduct
+ - Product
+ - Amount
+ - Percentage
+ - Quantity
+
+- DiscountForCategory
+ - Category
+ - Percentage
+ - Quantity
+
+
+- EnablingCondition:
+ - Description
+ - Mandatory?
+ - {Products}
+ - {Categories}
+
+ - EnablingCondition Types:
+   - ProductEnablingCondition:
+    * Enabling because the user has purchased a specific product *
+    - {Products that enable}
+
+   - CategoryEnablingCondition:
+    * Enabling because the user has purchased a product in a specific category *
+    - {Categories that enable}
+
+   - VoucherEnablingCondition:
+    * Enabling because the user has entered a voucher code *
+     - Voucher
+
+   - RoleEnablingCondition:
+     * Enabling because the user has a specific role *
+     - Role
+
+   - TimeOrStockLimitEnablingCondition:
+    * Enabling because a time condition has been met, or a number of items underneath it have not been sold *
+    - Start date
+    - End date
+    - Total available
diff --git a/vendor/registrasion/design/goals.md b/vendor/registrasion/design/goals.md
new file mode 100644
index 00000000..776220df
--- /dev/null
+++ b/vendor/registrasion/design/goals.md
@@ -0,0 +1,55 @@
+# Registrasion
+
+## What
+
+A registration package that sits on top of the Symposion conference management system. It aims to be able to model complex events, such as those used by [Linux Australia events](http://lca2016.linux.org.au/register/info?_code=301).
+
+
+## Planned features
+
+### KEY:
+- _(MODEL)_: these have model/controller functionality, and tests, and needs UI
+- _(ADMIN)_: these have admin functionality
+
+### Inventory
+- Allow conferences to manage complex inventories of products, including tickets, t-shirts, dinner tickets, and accommodation _(MODEL)_ _(ADMIN)_
+- Reports of available inventory and progressive sales for conference staff
+- Restrict sales of products to specific classes of users
+- Restrict sales of products based to users who've purchased specific products _(MODEL)_ _(ADMIN)_
+- Restrict sales of products based on time/inventory limits _(MODEL)_ _(ADMIN)_
+- Restrict sales of products to users with a voucher _(MODEL)_ _(ADMIN)_
+
+### Tickets
+- Sell multiple types of tickets, each with different included products _(MODEL)_ _(ADMIN)_
+- Allow for early bird-style discounts _(MODEL)_ _(ADMIN)_
+- Allow attendees to purchase products after initial registration is complete _(MODEL)_
+ - Offer included products if they have not yet been claimed _(MODEL)_
+- Automatically offer free tickets to speakers and team
+- Offer free tickets for sponsor attendees by voucher _(MODEL)_ _(ADMIN)_
+
+### Vouchers
+- Vouchers for arbitrary discounts off visible products _(MODEL)_ _(ADMIN)_
+- Vouchers that enable secret products _(MODEL)_ _(ADMIN)_
+
+### Invoicing
+- Automatic invoicing including discount calculation _(MODEL)_
+- Manual invoicing for arbitrary products by organisers _(MODEL)_
+- Refunds
+
+### Payments
+- Allow multiple payment gateways (so that conferences are not locked into specific payment providers)
+- Allow payment of registrations by unauthenticated users (allow business admins to pay for registrations)
+- Allow payment of multiple registrations at once
+
+### Attendee profiles
+- Attendees can enter information to be shown on their badge/dietary requirements etc
+- Profile can be changed until check-in, allowing for badge/company updates
+
+### At the conference
+- Badge generation, in batches, or on-demand during check-in
+- Registration manifests for each attendee including purchased products
+- Check-in process at registration desk allowing manifested items to be claimed
+
+### Tooling
+- Generate simple registration cases (ones that don't have complex inventory requirements)
+- Generate complex registration cases from spreadsheets
diff --git a/vendor/registrasion/docs/Makefile b/vendor/registrasion/docs/Makefile
new file mode 100644
index 00000000..88006e7a
--- /dev/null
+++ b/vendor/registrasion/docs/Makefile
@@ -0,0 +1,231 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = _build
+
+# User-friendly check for sphinx-build
+ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
+	$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don\'t have Sphinx installed, grab it from http://sphinx-doc.org/)
+endif
+
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html       to make standalone HTML files"
+	@echo "  dirhtml    to make HTML files named index.html in directories"
+	@echo "  singlehtml to make a single large HTML file"
+	@echo "  pickle     to make pickle files"
+	@echo "  json       to make JSON files"
+	@echo "  htmlhelp   to make HTML files and a HTML help project"
+	@echo "  qthelp     to make HTML files and a qthelp project"
+	@echo "  applehelp  to make an Apple Help Book"
+	@echo "  devhelp    to make HTML files and a Devhelp project"
+	@echo "  epub       to make an epub"
+	@echo "  epub3      to make an epub3"
+	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+	@echo "  latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
+	@echo "  text       to make text files"
+	@echo "  man        to make manual pages"
+	@echo "  texinfo    to make Texinfo files"
+	@echo "  info       to make Texinfo files and run them through makeinfo"
+	@echo "  gettext    to make PO message catalogs"
+	@echo "  changes    to make an overview of all changed/added/deprecated items"
+	@echo "  xml        to make Docutils-native XML files"
+	@echo "  pseudoxml  to make pseudoxml-XML files for display purposes"
+	@echo "  linkcheck  to check all external links for integrity"
+	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+	@echo "  coverage   to run coverage check of the documentation (if enabled)"
+	@echo "  dummy      to check syntax errors of document sources"
+
+.PHONY: clean
+clean:
+	rm -rf $(BUILDDIR)/*
+
+.PHONY: html
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+.PHONY: dirhtml
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+.PHONY: singlehtml
+singlehtml:
+	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+	@echo
+	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+.PHONY: pickle
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+.PHONY: json
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+.PHONY: htmlhelp
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+.PHONY: qthelp
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Registrasion.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Registrasion.qhc"
+
+.PHONY: applehelp
+applehelp:
+	$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
+	@echo
+	@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
+	@echo "N.B. You won't be able to view it unless you put it in" \
+	      "~/Library/Documentation/Help or install it in your application" \
+	      "bundle."
+
+.PHONY: devhelp
+devhelp:
+	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+	@echo
+	@echo "Build finished."
+	@echo "To view the help file:"
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/Registrasion"
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Registrasion"
+	@echo "# devhelp"
+
+.PHONY: epub
+epub:
+	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+	@echo
+	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+.PHONY: epub3
+epub3:
+	$(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
+	@echo
+	@echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
+
+.PHONY: latex
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make' in that directory to run these through (pdf)latex" \
+	      "(use \`make latexpdf' here to do that automatically)."
+
+.PHONY: latexpdf
+latexpdf:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through pdflatex..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+.PHONY: latexpdfja
+latexpdfja:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through platex and dvipdfmx..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+.PHONY: text
+text:
+	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+	@echo
+	@echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+.PHONY: man
+man:
+	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+	@echo
+	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+.PHONY: texinfo
+texinfo:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo
+	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+	@echo "Run \`make' in that directory to run these through makeinfo" \
+	      "(use \`make info' here to do that automatically)."
+
+.PHONY: info
+info:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo "Running Texinfo files through makeinfo..."
+	make -C $(BUILDDIR)/texinfo info
+	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+.PHONY: gettext
+gettext:
+	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+	@echo
+	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+.PHONY: changes
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+.PHONY: linkcheck
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+.PHONY: doctest
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."
+
+.PHONY: coverage
+coverage:
+	$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
+	@echo "Testing of coverage in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/coverage/python.txt."
+
+.PHONY: xml
+xml:
+	$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
+	@echo
+	@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
+
+.PHONY: pseudoxml
+pseudoxml:
+	$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
+	@echo
+	@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
+
+.PHONY: dummy
+dummy:
+	$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
+	@echo
+	@echo "Build finished. Dummy builder generates no files."
diff --git a/vendor/registrasion/docs/conf.py b/vendor/registrasion/docs/conf.py
new file mode 100644
index 00000000..99d8902e
--- /dev/null
+++ b/vendor/registrasion/docs/conf.py
@@ -0,0 +1,297 @@
+# -*- coding: utf-8 -*-
+#
+# Registrasion documentation build configuration file, created by
+# sphinx-quickstart on Thu Apr 21 11:29:51 2016.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys
+import os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+    "sphinx.ext.autodoc",
+    "sphinx.ext.napoleon",
+]
+
+# Autodoc requires django to be ready to go, otherwise we can't import rego's
+# things...
+sys.path.insert(0, ".")
+os.environ["DJANGO_SETTINGS_MODULE"] = "django_settings"
+
+import django
+django.setup()
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+# source_suffix = ['.rst', '.md']
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'Registrasion'
+copyright = u'2016, Christopher Neugebauer'
+author = u'Christopher Neugebauer'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = u'0.1a1'
+# The full version, including alpha/beta/rc tags.
+release = u'0.1a1'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This patterns also effect to html_static_path and html_extra_path
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+
+# The reST default role (used for this markup: `text`) to use for all
+# documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+# If true, keep warnings as "system message" paragraphs in the built documents.
+#keep_warnings = False
+
+# If true, `todo` and `todoList` produce output, else they produce nothing.
+todo_include_todos = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+
+# on_rtd is whether we are on readthedocs.org, this line of code grabbed from docs.readthedocs.org
+on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
+
+if not on_rtd:  # only import and set the theme if we're building docs locally
+    import sphinx_rtd_theme
+    html_theme = 'sphinx_rtd_theme'
+    html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
+
+
+# The name for this set of Sphinx documents.
+# "<project> v<release> documentation" by default.
+#html_title = u'Registrasion v0.1a1'
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (relative to this directory) to use as a favicon of
+# the docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# Add any extra paths that contain custom files (such as robots.txt or
+# .htaccess) here, relative to this directory. These files are copied
+# directly to the root of the documentation.
+#html_extra_path = []
+
+# If not None, a 'Last updated on:' timestamp is inserted at every page
+# bottom, using the given strftime format.
+# The empty string is equivalent to '%b %d, %Y'.
+#html_last_updated_fmt = None
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Language to be used for generating the HTML full-text search index.
+# Sphinx supports the following languages:
+#   'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
+#   'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh'
+#html_search_language = 'en'
+
+# A dictionary with options for the search language support, empty by default.
+# 'ja' uses this config value.
+# 'zh' user can custom change `jieba` dictionary path.
+#html_search_options = {'type': 'default'}
+
+# The name of a javascript file (relative to the configuration directory) that
+# implements a search results scorer. If empty, the default will be used.
+#html_search_scorer = 'scorer.js'
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'Registrasiondoc'
+
+# -- Options for LaTeX output ---------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
+
+# The font size ('10pt', '11pt' or '12pt').
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+
+# Latex figure (float) alignment
+#'figure_align': 'htbp',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+#  author, documentclass [howto, manual, or own class]).
+latex_documents = [
+    (master_doc, 'Registrasion.tex', u'Registrasion Documentation',
+     u'Christopher Neugebauer', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    (master_doc, 'registrasion', u'Registrasion Documentation',
+     [author], 1)
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+    (master_doc, 'Registrasion', u'Registrasion Documentation',
+     author, 'Registrasion', 'One line description of project.',
+     'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
+
+# If true, do not generate a @detailmenu in the "Top" node's menu.
+#texinfo_no_detailmenu = False
diff --git a/vendor/registrasion/docs/django_settings.py b/vendor/registrasion/docs/django_settings.py
new file mode 100644
index 00000000..56a07eca
--- /dev/null
+++ b/vendor/registrasion/docs/django_settings.py
@@ -0,0 +1,122 @@
+"""
+Django settings for djangoenv project.
+
+Generated by 'django-admin startproject' using Django 1.9.5.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.9/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/1.9/ref/settings/
+"""
+
+import os
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = 'z$cu8&jcnm#qa=xbrkss-4w8do+(pp16j*usmp9j&bg=)&1@-a'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = []
+
+
+# Application definition
+
+INSTALLED_APPS = [
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+    'registrasion',
+]
+
+MIDDLEWARE_CLASSES = [
+    'django.middleware.security.SecurityMiddleware',
+    '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',
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+ROOT_URLCONF = 'djangoenv.urls'
+
+TEMPLATES = [
+    {
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'DIRS': [],
+        'APP_DIRS': True,
+        'OPTIONS': {
+            'context_processors': [
+                'django.template.context_processors.debug',
+                'django.template.context_processors.request',
+                'django.contrib.auth.context_processors.auth',
+                'django.contrib.messages.context_processors.messages',
+            ],
+        },
+    },
+]
+
+WSGI_APPLICATION = 'djangoenv.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/1.9/ref/settings/#databases
+
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3',
+        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
+    }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+    {
+        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+    },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/1.9/topics/i18n/
+
+LANGUAGE_CODE = 'en-us'
+
+TIME_ZONE = 'UTC'
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/1.9/howto/static-files/
+
+STATIC_URL = '/static/'
diff --git a/vendor/registrasion/docs/for-zookeepr-users.rst b/vendor/registrasion/docs/for-zookeepr-users.rst
new file mode 100644
index 00000000..28cd1932
--- /dev/null
+++ b/vendor/registrasion/docs/for-zookeepr-users.rst
@@ -0,0 +1,22 @@
+Registrasion for Zookeepr Keeprs
+================================
+
+Things that are the same
+------------------------
+* You have an inventory of products
+* Complete registrations are made up of multiple products
+* Products are split into categories
+* Products can be listed under ceilings
+* Products can be included for free by purchasing other items
+ * e.g. a Professional Ticket includes a dinner ticket
+* Products can be enabled by user roles
+ * e.g. Speakers Dinner tickets are visible to speakers
+* Vouchers can be used to discount products
+
+Things that are different
+-------------------------
+* Ceilings can be used to apply discounts, so Early Bird ticket rates can be implemented by applying a ceiling-type discount, rather than duplicating the ticket type.
+* Vouchers can be used to enable products
+ * e.g. Sponsor tickets do not appear until you supply a sponsor's voucher
+* Items may be enabled by having other specific items present
+ * e.g. Extra accommodation nights do not appear until you purchase the main week worth of accommodation.
diff --git a/vendor/registrasion/docs/index.rst b/vendor/registrasion/docs/index.rst
new file mode 100644
index 00000000..e8cab500
--- /dev/null
+++ b/vendor/registrasion/docs/index.rst
@@ -0,0 +1,36 @@
+.. Registrasion documentation master file, created by
+   sphinx-quickstart on Thu Apr 21 11:29:51 2016.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+Registrasion
+============
+
+Registra(tion for Sympo)sion.
+
+Registrasion is a conference registration package that goes well with the Symposion suite of conference management apps for Django. It's designed to manage the sorts of inventories that large conferences need to manage, build up complex tickets with multiple items, and handle payments using whatever payment gateway you happen to have access to
+
+Development of registrasion was commenced by Christopher Neugebauer in 2016, with the generous support of the Python Software Foundation.
+
+
+Contents:
+---------
+.. toctree::
+   :maxdepth: 2
+
+   overview
+   integration
+   inventory
+   payments
+   for-zookeepr-users
+   views
+   templates
+
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`search`
+
+.. * :ref:`modindex`
diff --git a/vendor/registrasion/docs/integration.rst b/vendor/registrasion/docs/integration.rst
new file mode 100644
index 00000000..dc012359
--- /dev/null
+++ b/vendor/registrasion/docs/integration.rst
@@ -0,0 +1,67 @@
+Installing and integrating Registrasion
+=======================================
+
+Registrasion is a Django app. It does not provide any templates -- you'll need to develop these yourself. You can use the `registrasion-demo <https://github.com/chrisjrn/registrasion-demo>`_ project as a starting point.
+
+To use Registrasion for your own conference, you'll need to do a small amount of configuration and development work, in your own Django App.
+
+The configuration that you'll need to do is minimal. The first piece of development work is to define a model and form for your attendee profile, and the second is to implement a payment app.
+
+
+Installing Registrasion
+-----------------------
+
+Registrasion depends on an in-development version of Symposion. You'll need to add the following  line to your ``requirements.txt`` files::
+
+    registrasion==0.1.0
+    https://github.com/pinax/symposion/tarball/ad81810#egg=symposion
+
+And also to enable dependency links in pip::
+
+    pip install --process-dependency-links -r requirements.txt
+
+Symposion currently specifies Django version 1.9.2. Note that ``pip`` version 1.6 does not support ``--process-dependency-links``, so you'll need to use an earlier, or later version of ``pip``.
+
+
+Configuring your Django App
+---------------------------
+
+In your Django ``settings.py`` file, you'll need to add the following to your ``INSTALLED_APPS``::
+
+  "registrasion",
+  "nested_admin",
+
+You will also need to configure ``symposion`` appropriately.
+
+
+Attendee profile
+----------------
+
+.. automodule:: registrasion.models.people
+
+Attendee profiles are where you ask for information such as what your attendee wants on their badge, and what the attendee's dietary and accessibility requirements are.
+
+Because every conference is different, Registrasion lets you define your own attendee profile model, and your own form for requesting this information. The only requirement is that you derive your model from ``AttendeeProfileBase``.
+
+.. autoclass :: AttendeeProfileBase
+    :members: name_field, invoice_recipient
+
+You specify how to find that model in your Django ``settings.py`` file::
+
+    ATTENDEE_PROFILE_MODEL = "democon.models.AttendeeProfile"
+
+When Registrasion asks the to edit their profile, a default form will be generated, showing all of the fields on the profile model.
+
+If you want to customise the profile editing form, you need to specify the location of that form in your ``settings.py`` file as well.
+
+    ATTENDEE_PROFILE_FORM = "democon.forms.AttendeeProfileForm"
+
+The only contract is that this form creates an instance of ``AttendeeProfileBase`` when saved, and that it can take an instance of your subclass on creation (so that your attendees can edit their profile).
+
+
+Payments
+--------
+
+Registrasion does not implement its own credit card processing. You'll need to do that yourself. Registrasion *does* provide a mechanism for recording cheques and direct deposits, if you do end up taking registrations that way.
+
+See :ref:`payments_and_refunds` for a guide on how to correctly implement payments.
diff --git a/vendor/registrasion/docs/inventory.rst b/vendor/registrasion/docs/inventory.rst
new file mode 100644
index 00000000..c9d6dec1
--- /dev/null
+++ b/vendor/registrasion/docs/inventory.rst
@@ -0,0 +1,158 @@
+
+Inventory Management
+====================
+
+Registrasion uses an inventory model to keep track of tickets, and the other various products that attendees of your conference might want to have, such as t-shirts and dinner tickets.
+
+All of the classes described herein are available through the Django Admin interface.
+
+Overview
+--------
+
+The inventory model is split up into Categories and Products. Categories are used to group Products.
+
+Registrasion uses conditionals to build up complex tickets, or enable/disable specific items to specific users:
+
+Often, you will want to offer free items, such as t-shirts or dinner tickets to your attendees. Registrasion has a Discounts facility that lets you automatically offer free items to your attendees as part of their tickets. You can also automatically offer promotional discounts, such as Early Bird discounts.
+
+Sometimes, you may want to restrict parts of the conference to specific attendees, for example, you might have a Speakers Dinner to only speakers. Or you might want to restrict certain Products to attendees who have purchased other items, for example, you might want to sell Comfy Chairs to people who've bought VIP tickets. You can control showing and hiding specific products using Flags.
+
+
+.. automodule:: registrasion.models.inventory
+
+Categories
+----------
+
+Categories are logical groups of Products. Generally, you should keep like products in the same category, and use as many categories as you need.
+
+You will need at least one Category to be able to sell tickets to your attendees.
+
+Each category has the following attributes:
+
+.. autoclass :: Category
+
+
+Products
+--------
+
+Products represent the different items that comprise a user's conference ticket.
+
+Each product has the following attributes:
+
+.. autoclass :: Product
+
+
+Vouchers
+--------
+
+Vouchers are used to enable Discounts or Flags for people who enter a voucher
+code.
+
+.. autoclass :: Voucher
+
+If an attendee enters a voucher code, they have at least an hour to finalise
+their registration before the voucher becomes unreserved. Only as many people
+as allowed by ``limit`` are allowed to have a voucher reserved.
+
+
+.. automodule:: registrasion.models.conditions
+
+Discounts
+---------
+
+Discounts serve multiple purposes: they can be used to build up complex tickets by automatically waiving the costs for sub-products; they can be used to offer freebie tickets to specific people, or people who hold voucher codes; or they can be used to enable short-term promotional discounts.
+
+Registrasion has several types of discounts, which enable themselves under specific conditions. We'll explain how these work later on, but first:
+
+Common features
+~~~~~~~~~~~~~~~
+Each discount type has the following common attributes:
+
+.. autoclass :: DiscountBase
+
+You can apply a discount to individual products, or to whole categories, or both. All of the products and categories affected by the discount are displayed on the discount's admin page.
+
+If you choose to specify individual products, you have these options:
+
+.. autoclass :: DiscountForProduct
+
+If you choose to specify whole categories, you have these options:
+
+.. autoclass :: DiscountForCategory
+
+Note that you cannot have a discount apply to both a category, and a product within that category.
+
+Product Inclusions
+~~~~~~~~~~~~~~~~~~
+Product inclusion discounts allow you to enable a discount when an attendee has selected a specific enabling Product.
+
+For example, if you want to give everyone with a ticket a free t-shirt, you can use a product inclusion to offer a 100% discount on the t-shirt category, if the attendee has selected one of your ticket Products.
+
+Once a discount has been enabled in one Invoice, it is available until the quantities are exhausted for that attendee.
+
+.. autoclass :: IncludedProductDiscount
+
+Time/stock limit discounts
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+These discounts allow you to offer a limited promotion that is automatically offered to all attendees. You can specify a time range for when the discount should be enabled, you can also specify a stock limit.
+
+.. autoclass :: TimeOrStockLimitDiscount
+
+Voucher discounts
+~~~~~~~~~~~~~~~~~
+Vouchers can be used to enable discounts.
+
+.. autoclass :: VoucherDiscount
+
+How discounts get applied
+~~~~~~~~~~~~~~~~~~~~~~~~~
+It's possible for multiple discounts to be available on any given Product. This means there need to be rules for how discounts get applied. It works like so:
+
+#. Take all of the Products that the user currently has selected, and sort them so that the most expensive comes first.
+#. Apply the highest-value discount line for the first Product, until either all such products have a discount applied, or the discount's Quantity has been exhausted for that user for that Product.
+#. Repeat until all products have been processed.
+
+In summary, the system greedily applies the highest-value discounts for each product. This may not provide a global optimum, but it'll do.
+
+As an example: say a user has a voucher available for a 100% discount of tickets, and there's a promotional discount for 15% off tickets. In this case, the 100% discount will apply, and the 15% discount will not be disturbed.
+
+
+Flags
+-----
+
+Flags are conditions that can be used to enable or disable Products or Categories, depending on whether conditions are met. They can be used to restrict specific products to specific people, or to place time limits on availability for products.
+
+Common Features
+~~~~~~~~~~~~~~~
+
+.. autoclass :: FlagBase
+
+
+Dependencies on products from category
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Category Dependency flags have their condition met if a product from the enabling category has been selected by the attendee. For example, if there is an *Accommodation* Category, this flag could be used to enable an *Accommodation Breakfast* category, allowing only attendees with accommodation to purchase breakfast.
+
+.. autoclass :: CategoryFlag
+
+
+Dependencies on products
+~~~~~~~~~~~~~~~~~~~~~~~~
+Product dependency flags have their condition met if one of the enabling products have been selected by the attendee.
+
+.. autoclass :: ProductFlag
+
+Time/stock limit flags
+~~~~~~~~~~~~~~~~~~~~~~
+These flags allow the products that they cover to be made available for a limited time, or to set a global ceiling on the products covered.
+
+These can be used to remove items from sale once a sales deadline has been met, or if a venue for a specific event has reached capacity.  If there are items that fall under multiple such groupings, it makes sense to set all of these flags to be ``DISABLE_IF_FALSE``.
+
+.. autoclass :: TimeOrStockLimitFlag
+
+If any of the attributes are omitted, then only the remaining attributes affect the availablility of the products covered. If there's no attributes set at all, then the grouping has no effect, but it can be used to group products for reporting purposes.
+
+Voucher flags
+~~~~~~~~~~~~~
+Vouchers can be used to enable flags.
+
+.. autoclass :: VoucherFlag
diff --git a/vendor/registrasion/docs/make.bat b/vendor/registrasion/docs/make.bat
new file mode 100644
index 00000000..da9a651f
--- /dev/null
+++ b/vendor/registrasion/docs/make.bat
@@ -0,0 +1,281 @@
+@ECHO OFF
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+	set SPHINXBUILD=sphinx-build
+)
+set BUILDDIR=_build
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
+set I18NSPHINXOPTS=%SPHINXOPTS% .
+if NOT "%PAPER%" == "" (
+	set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+	set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
+)
+
+if "%1" == "" goto help
+
+if "%1" == "help" (
+	:help
+	echo.Please use `make ^<target^>` where ^<target^> is one of
+	echo.  html       to make standalone HTML files
+	echo.  dirhtml    to make HTML files named index.html in directories
+	echo.  singlehtml to make a single large HTML file
+	echo.  pickle     to make pickle files
+	echo.  json       to make JSON files
+	echo.  htmlhelp   to make HTML files and a HTML help project
+	echo.  qthelp     to make HTML files and a qthelp project
+	echo.  devhelp    to make HTML files and a Devhelp project
+	echo.  epub       to make an epub
+	echo.  epub3      to make an epub3
+	echo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter
+	echo.  text       to make text files
+	echo.  man        to make manual pages
+	echo.  texinfo    to make Texinfo files
+	echo.  gettext    to make PO message catalogs
+	echo.  changes    to make an overview over all changed/added/deprecated items
+	echo.  xml        to make Docutils-native XML files
+	echo.  pseudoxml  to make pseudoxml-XML files for display purposes
+	echo.  linkcheck  to check all external links for integrity
+	echo.  doctest    to run all doctests embedded in the documentation if enabled
+	echo.  coverage   to run coverage check of the documentation if enabled
+	echo.  dummy      to check syntax errors of document sources
+	goto end
+)
+
+if "%1" == "clean" (
+	for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
+	del /q /s %BUILDDIR%\*
+	goto end
+)
+
+
+REM Check if sphinx-build is available and fallback to Python version if any
+%SPHINXBUILD% 1>NUL 2>NUL
+if errorlevel 9009 goto sphinx_python
+goto sphinx_ok
+
+:sphinx_python
+
+set SPHINXBUILD=python -m sphinx.__init__
+%SPHINXBUILD% 2> nul
+if errorlevel 9009 (
+	echo.
+	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+	echo.installed, then set the SPHINXBUILD environment variable to point
+	echo.to the full path of the 'sphinx-build' executable. Alternatively you
+	echo.may add the Sphinx directory to PATH.
+	echo.
+	echo.If you don't have Sphinx installed, grab it from
+	echo.http://sphinx-doc.org/
+	exit /b 1
+)
+
+:sphinx_ok
+
+
+if "%1" == "html" (
+	%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+	goto end
+)
+
+if "%1" == "dirhtml" (
+	%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+	goto end
+)
+
+if "%1" == "singlehtml" (
+	%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
+	goto end
+)
+
+if "%1" == "pickle" (
+	%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can process the pickle files.
+	goto end
+)
+
+if "%1" == "json" (
+	%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can process the JSON files.
+	goto end
+)
+
+if "%1" == "htmlhelp" (
+	%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can run HTML Help Workshop with the ^
+.hhp project file in %BUILDDIR%/htmlhelp.
+	goto end
+)
+
+if "%1" == "qthelp" (
+	%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can run "qcollectiongenerator" with the ^
+.qhcp project file in %BUILDDIR%/qthelp, like this:
+	echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Registrasion.qhcp
+	echo.To view the help file:
+	echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Registrasion.ghc
+	goto end
+)
+
+if "%1" == "devhelp" (
+	%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished.
+	goto end
+)
+
+if "%1" == "epub" (
+	%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The epub file is in %BUILDDIR%/epub.
+	goto end
+)
+
+if "%1" == "epub3" (
+	%SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The epub3 file is in %BUILDDIR%/epub3.
+	goto end
+)
+
+if "%1" == "latex" (
+	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+	goto end
+)
+
+if "%1" == "latexpdf" (
+	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+	cd %BUILDDIR%/latex
+	make all-pdf
+	cd %~dp0
+	echo.
+	echo.Build finished; the PDF files are in %BUILDDIR%/latex.
+	goto end
+)
+
+if "%1" == "latexpdfja" (
+	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+	cd %BUILDDIR%/latex
+	make all-pdf-ja
+	cd %~dp0
+	echo.
+	echo.Build finished; the PDF files are in %BUILDDIR%/latex.
+	goto end
+)
+
+if "%1" == "text" (
+	%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The text files are in %BUILDDIR%/text.
+	goto end
+)
+
+if "%1" == "man" (
+	%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The manual pages are in %BUILDDIR%/man.
+	goto end
+)
+
+if "%1" == "texinfo" (
+	%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
+	goto end
+)
+
+if "%1" == "gettext" (
+	%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
+	goto end
+)
+
+if "%1" == "changes" (
+	%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.The overview file is in %BUILDDIR%/changes.
+	goto end
+)
+
+if "%1" == "linkcheck" (
+	%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Link check complete; look for any errors in the above output ^
+or in %BUILDDIR%/linkcheck/output.txt.
+	goto end
+)
+
+if "%1" == "doctest" (
+	%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Testing of doctests in the sources finished, look at the ^
+results in %BUILDDIR%/doctest/output.txt.
+	goto end
+)
+
+if "%1" == "coverage" (
+	%SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Testing of coverage in the sources finished, look at the ^
+results in %BUILDDIR%/coverage/python.txt.
+	goto end
+)
+
+if "%1" == "xml" (
+	%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The XML files are in %BUILDDIR%/xml.
+	goto end
+)
+
+if "%1" == "pseudoxml" (
+	%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
+	goto end
+)
+
+if "%1" == "dummy" (
+	%SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. Dummy builder generates no files.
+	goto end
+)
+
+:end
diff --git a/vendor/registrasion/docs/overview.rst b/vendor/registrasion/docs/overview.rst
new file mode 100644
index 00000000..5f6b25fa
--- /dev/null
+++ b/vendor/registrasion/docs/overview.rst
@@ -0,0 +1,51 @@
+Overview
+========
+
+Registrasion's approach to handling conference registrations is to use a cart and inventory model, where the various things sold by the conference to attendees are handled as Products, which can be added to a Cart. Carts can be used to generate Invoices, and Invoices can then be paid.
+
+
+Guided registration
+-------------------
+
+Unlike a generic e-commerce platform, Registrasion is designed for building up conference tickets.
+
+When they first attempt registration, attendees start off in a process called *guided mode*. Guided mode is multi-step form that takes users through a complete registration process:
+
+#. The attendee fills out their profile
+#. The attendee selects a ticket type
+#. The attendee selects additional products such as t-shirts and dinner tickets, which may be sold at a cost, or have waivers applied.
+#. The attendee is offered the opportunity to check out their cart, generating an invoice; or to enter amendments mode.
+
+For specifics on how guided mode works, see *code guide to be written*.
+
+
+Amendments mode
+---------------
+
+Once attendees have reached the end of guided registration, they are permanently added to *amendments mode*. Amendments mode allows attendees to change their product selections in a given category, with one rule: once an invoice has been paid, product selections cannot be changed without voiding that invoice (and optionally releasing a Credit Note).
+
+Users can check out their current selections at any time, and generate an Invoice for their selections. That invoice can then be paid, and the attendee will then be making selections on a new cart.
+
+
+Invoices
+--------
+
+When an attendee checks out their Cart, an Invoice is generated for their cart.
+
+An invoice is valid for as long as the items in the cart do not change, and remain generally available. If a user amends their cart after generating an invoice, the user will need to check out their cart again, and generate a new invoice.
+
+Once an invoice is paid, it is no longer possible for an invoice to be void, instead, it needs to have a refund generated.
+
+
+User-Attendee Model
+-------------------
+
+Registrasion uses a User-Attendee model. This means that Registrasion expects each user account on the system to represent a single attendee at the conference. It also expects that the attendee themselves fill out the registration form.
+
+This means that each attendee has a confirmed e-mail address for conference-related communications. It's usually a good idea for the conference to make sure that their account sign-up page points this out, so that administrative assistants at companies don't end up being the ones getting communicated at.
+
+How do people get their employers to pay for their tickets?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Registrasion provides a semi-private URL that allows anyone in possession of this URL to view that attendee's most recent invoice, and make payments against that invoice.
+
+A future release will add the ability to bulk-pay multiple invoices at once.
diff --git a/vendor/registrasion/docs/payments.rst b/vendor/registrasion/docs/payments.rst
new file mode 100644
index 00000000..d0105afb
--- /dev/null
+++ b/vendor/registrasion/docs/payments.rst
@@ -0,0 +1,165 @@
+.. automodule:: registrasion.models.commerce
+.. _payments_and_refunds:
+
+Payments and Refunds
+====================
+
+Registrasion aims to support whatever payment platform you have available to use. Therefore, Registrasion uses a bare minimum payments model to track money within the system. It's the role of you, as a deployer of Registrasion, to implement a payment application that communicates with your own payment platform.
+
+Invoices may have multiple ``PaymentBase`` objects attached to them; each of these represent a payment against the invoice. Payments can be negative (and this represents a refund against the Invoice), however, this approach is not recommended for use by implementers.
+
+Registrasion also keeps track of money that is not currently attached to invoices through `credit notes`_. Credit notes may be applied to unpaid invoices *in full*, if there is an amount left over from the credit note, a new credit note will be created from that amount. Credit notes may also be released, at which point they're the responsibility of the payment application to create a refund.
+
+Finally, Registrasion provides a `manual payments`_ feature, which allows for staff members to manually report payments into the system. This is the only payment facility built into Registrasion, but it's not intended as a reference implementation.
+
+
+Invoice and payment access control
+----------------------------------
+
+Conferences are interesting: usually you want attendees to fill in their own registration so that they get their catering options right, so that they can personally agree to codes of conduct, and so that you can make sure that you're communicating key information directly with them.
+
+On the other hand, employees at companies often need for their employers to directly pay for their registration.
+
+Registrasion solves this problem by having attendees complete their own registration, and then providing an access URL that allows anyone who holds that URL to view their invoice and make payment.
+
+You can call ``InvoiceController.can_view`` to determine whether or not you're allowed to show the invoice. It returns true if the user is allowed to view the invoice::
+
+    InvoiceController.can_view(self, user=request.user, access_code="CODE")
+
+As a rule, you should call ``can_view`` before doing any operations that amend the status of an invoice. This includes taking payments or requesting refunds.
+
+The access code is unique for each attendee -- this means that every invoice that an attendee generates can be viewed with the same access code. This is useful if the user amends their registration between giving the URL to their employer, and their employer making payment.
+
+
+
+
+Making payments
+---------------
+
+Making payments is a three-step process:
+
+#. Validate that the invoice is ready to be paid,
+#. Create a payment object for the amount that you are paying towards an invoice,
+#. Ask the invoice to calculate its status now that the payment has been made.
+
+Pre-validation
+~~~~~~~~~~~~~~
+Registrasion's ``InvoiceController`` has a ``validate_allowed_to_pay`` method, which performs all of the pre-payment checks (is the invoice still unpaid and non-void? has the registration been amended?).
+
+If the pre-payment check fails, ``InvoiceController`` will raise a Django ``ValidationError``.
+
+Our the ``demopay`` view from the ``registrasion-demo`` project implements pre-validation like so::
+
+    from registrasion.controllers.invoice import InvoiceController
+    from django.core.exceptions import ValidationError
+
+    invoice = InvoiceController.for_id_or_404(invoice.id)
+
+    try:
+        invoice.validate_allowed_to_pay()  # Verify that we're allowed to do this.
+    except ValidationError as ve:
+        messages.error(request, ve.message)
+        return REDIRECT_TO_INVOICE  # And display the validation message.
+
+In most cases, you don't engage your actual payment application until after pre-validation is finished, as this gives you an opportunity to bail out if the invoice isn't able to have funds applied to it.
+
+Applying payments
+~~~~~~~~~~~~~~~~~
+Payments in Registrasion are represented as subclasses of the ``PaymentBase`` model. ``PaymentBase`` looks like this:
+
+.. autoclass :: PaymentBase
+
+When you implement your own payment application, you'll need to subclass ``PaymentBase``, as this will allow you to add metadata that lets you link the Registrasion payment object with your payment platform's object.
+
+Generally, the ``reference`` field should be something that lets your end-users identify the payment on their credit card statement, and to provide to your team's tech support in case something goes wrong.
+
+Once you've subclassed ``PaymentBase``, applying a payment is really quite simple. In the ``demopay`` view, we have a subclass called ``DemoPayment``::
+
+    invoice = InvoiceController(some_invoice_model)
+
+    # Create the payment object
+    models.DemoPayment.objects.create(
+        invoice=invoice.invoice,
+        reference="Demo payment by user: " + request.user.username,
+        amount=invoice.invoice.value,
+    )
+
+Note that multiple payments can be provided against an ``Invoice``, however, payments that exceed the total value of the invoice will have credit notes generated.
+
+Updating an invoice's status
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+``InvoiceController`` has a method called ``update_status``. You should call ``update_status`` immediately after you create a ``PaymentBase`` object, as this keeps invoice and its payments synchronised::
+
+    invoice = InvoiceController(some_invoice_model)
+    invoice.update_status()
+
+Calling ``update_status`` collects the ``PaymentBase`` objects that are attached to the ``Invoice``, and will update the status of the invoice:
+
+* If an invoice is ``VOID``, it will remain void.
+* If an invoice is ``UNPAID`` and it now has ``PaymentBase`` objects whose value meets or exceed's the invoice's value, the invoice becomes ``PAID``.
+* If an invoice is ``UNPAID`` and it now has ``PaymentBase`` objects whose values sum to zero, the invoice becomes ``VOID``.
+* If an invoice is ``PAID`` and it now has ``PaymentBase`` objects of less than the invoice's value, the invoice becomes ``REFUNDED``.
+
+When your invoice becomes ``PAID`` for the first time, if there's a cart of inventory items attached to it, that cart becomes permanently reserved -- that is, all of the items within it are no longer available for other users to purchase. If an invoice becomes ``REFUNDED``, the items in the cart are released, which means that they are available for anyone to purchase again.
+
+If you overpay an invoice, or pay into an invoice that should not have funds attached, a credit note for the residual payments will also be issued.
+
+In general, although this means you *can* use negative payments to take an invoice into a *REFUNDED* state, it's still much more sensible to use the credit notes facility, as this makes sure that any leftover funds remain tracked in the system.
+
+
+Credit Notes
+------------
+
+When you refund an invoice, often you're doing so in order to make a minor amendment to items that the attendee has purchased. In order to make it easy to transfer funds from a refunded invoice to a new invoice, Registrasion provides an automatic credit note facility.
+
+Credit notes are created when you mark an invoice as refunded, but they're also created if you overpay an invoice, or if you direct money into an invoice that can no longer take payment.
+
+Once created, Credit Notes act as a payment that can be put towards other invoices, or that can be cashed out, back to the original payment platform. Credits can only be applied or cashed out in full.
+
+This means that it's easy to track funds that aren't accounted for by invoices -- it's just the sum of the credit notes that haven't been applied to new invoices, or haven't been cashed out.
+
+We recommend using credit notes to track all of your refunds for consistency; it also allows you to invoice for cancellation fees and the like.
+
+Creating credit notes
+~~~~~~~~~~~~~~~~~~~~~
+In Registrasion, credit notes originate against invoices, and are represented as negative payments to an invoice.
+
+Credit notes are usually created automatically. In most cases, Credit Notes come about from asking to refund an invoice::
+
+    InvoiceController(invoice).refund()
+
+Calling ``refund()`` will generate a refund of all of the payments applied to that invoice.
+
+Otherwise, credit notes come about when invoices are overpaid, in this case, a credit for the overpay amount will be generated.
+
+Applying credits to new invoices
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Credits can be applied to invoices::
+
+    CreditNoteController(credit_not).apply_to_invoice(invoice)
+
+This will result in an instance of ``CreditNoteApplication`` being applied as a payment to ``invoice``. ``CreditNoteApplication`` will always be a payment of the full amount of its parent credit note. If this payment overpays the invoice it's being applied to, a credit note for the residual will be generated.
+
+Refunding credit notes
+~~~~~~~~~~~~~~~~~~~~~~
+It is possible to release a credit note back to the original payment platform. To do so, you attach an instance of ``CreditNoteRefund`` to the original ``CreditNote``:
+
+.. autoclass :: CreditNoteRefund
+
+You'll usually want to make a subclass of ``CreditNoteRefund`` for your own purposes, usually so that you can tie Registrasion's internal representation of the refund to a concrete refund on the side of your payment platform.
+
+Note that you can only release a credit back to the payment platform for the full amount of the credit.
+
+
+Manual payments
+---------------
+
+Registrasion provides a *manual payments* feature, which allows for staff members to manually report payments into the system. This is the only payment facility built into Registrasion, but it's not intended as a reference implementation.
+
+The main use case for manual payments is to record the receipt of funds from bank transfers or cheques sent on behalf of attendees.
+
+It's not intended as a reference implementation is because it does not perform validation of the cart before the payment is applied to the invoice.
+
+This means that it's possible for a staff member to apply a payment with a specific invoice reference into the invoice matching that reference. Registrasion will generate a credit note if the invoice is not able to receive payment (e.g. because it has since been voided), you can use that credit note to pay into a valid invoice if necessary.
+
+It is possible for staff to enter a negative amount on a manual payment. This will be treated as a refund. Generally, it's preferred to issue a credit note to an invoice rather than enter a negative amount manually.
diff --git a/vendor/registrasion/docs/templates.rst b/vendor/registrasion/docs/templates.rst
new file mode 100644
index 00000000..0cb7f192
--- /dev/null
+++ b/vendor/registrasion/docs/templates.rst
@@ -0,0 +1,78 @@
+Basic Templates
+===============
+Registrasion provides basic templates for all of its views. This means that if new features come along, you won't need to do extra work just to enable them.
+
+
+What is the point of this?
+--------------------------
+
+`registrasion` provides a bunch of django views that make the app tick. As new features get added, so will this package. By keeping this package up to date, you'll get a default template for each new view that gets added.
+
+
+How does it work
+----------------
+
+For each template required by registrasion, `registrasion_templates` provides two templates. Say the template used by the view is called `view.html`. We provide:
+
+* `view.html`, which is the template that is loaded directly -- this will be *very* modular, and will let you easily override things that you need to override in your own installations
+* `view_.html`, which is the thing that lays everything out.
+
+So you can either override `view_.html` if you're happy with the text and markup that `view.html` provides, or you can override `view.html` if you want to change the entire thing. Your choice!
+
+
+Installation
+------------
+
+Ensure that `APP_DIRS` is switched on in your `settings`, like so:
+
+```
+TEMPLATES = [{
+    'BACKEND': 'django.template.backends.django.DjangoTemplates',
+    'APP_DIRS': True,
+}]
+```
+
+
+Overriding our defaults:
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+* `registrasion/form.html` is used by these templates whenever a form needs to be rendered. The default implementation of this just calls ``{form}``, however, you may want to render your forms differently.
+* `registrasion/base.html` extends `site_base.html`. Each `view_.html` template that we provide extends `registrasion/base.html`
+
+
+Using the templates
+-------------------
+
+* All of the default templates provide the following blocks:
+  * `title`, which is added to the site's `<title>` tag
+  * `heading`, which is the page's heading
+  * `lede`, a paragraph that describes the page
+  * `content`, the body content for the page
+* If you want that content to appear in your pages, you must include these blocks in your `registrasion/base.html`.
+
+* `content` may include other blocks, so that you can override default text. Each `view.html` template will document the blocks that you can override.
+
+
+CSS styling
+-----------
+
+The in-built templates do a small amount of layout and styling work, using bootstrap conventions. The following CSS classes are used:
+
+* `panel panel-default`
+* `panel panel-primary`
+* `panel panel-info`
+* `panel-heading`
+* `panel-title`
+* `panel-body`
+* `panel-footer`
+* `form-actions`
+* `btn btn-default`
+* `btn btn-primary`
+* `btn btn-xs btn-default`
+* `alert alert-info`
+* `alert alert-warning`
+* `list-group`
+* `list-group-item`
+* `well`
+* `table`
+* `table table-striped`
diff --git a/vendor/registrasion/docs/views.rst b/vendor/registrasion/docs/views.rst
new file mode 100644
index 00000000..313337ec
--- /dev/null
+++ b/vendor/registrasion/docs/views.rst
@@ -0,0 +1,41 @@
+User-facing views
+=================
+
+
+View functions
+--------------
+
+Here's all of the views that Registrasion exposes to the public.
+
+.. automodule:: registrasion.views
+    :members:
+
+Data types
+~~~~~~~~~~
+
+.. automodule:: registrasion.controllers.discount
+
+.. autoclass:: DiscountAndQuantity
+
+
+Template tags
+-------------
+
+Registrasion makes template tags available:
+
+.. automodule:: registrasion.templatetags.registrasion_tags
+    :members:
+
+
+Rendering invoices
+------------------
+
+You'll need to render the following Django models in order to view invoices.
+
+.. automodule:: registrasion.models.commerce
+
+.. autoclass:: Invoice
+
+.. autoclass:: LineItem
+
+See also: :class:`PaymentBase`
diff --git a/vendor/registrasion/__init__.py b/vendor/registrasion/registrasion/__init__.py
similarity index 100%
rename from vendor/registrasion/__init__.py
rename to vendor/registrasion/registrasion/__init__.py
diff --git a/vendor/registrasion/admin.py b/vendor/registrasion/registrasion/admin.py
similarity index 100%
rename from vendor/registrasion/admin.py
rename to vendor/registrasion/registrasion/admin.py
diff --git a/vendor/registrasion/apps.py b/vendor/registrasion/registrasion/apps.py
similarity index 100%
rename from vendor/registrasion/apps.py
rename to vendor/registrasion/registrasion/apps.py
diff --git a/vendor/registrasion/contrib/__init__.py b/vendor/registrasion/registrasion/contrib/__init__.py
similarity index 100%
rename from vendor/registrasion/contrib/__init__.py
rename to vendor/registrasion/registrasion/contrib/__init__.py
diff --git a/vendor/registrasion/contrib/mail.py b/vendor/registrasion/registrasion/contrib/mail.py
similarity index 100%
rename from vendor/registrasion/contrib/mail.py
rename to vendor/registrasion/registrasion/contrib/mail.py
diff --git a/vendor/registrasion/controllers/__init__.py b/vendor/registrasion/registrasion/controllers/__init__.py
similarity index 100%
rename from vendor/registrasion/controllers/__init__.py
rename to vendor/registrasion/registrasion/controllers/__init__.py
diff --git a/vendor/registrasion/controllers/batch.py b/vendor/registrasion/registrasion/controllers/batch.py
similarity index 100%
rename from vendor/registrasion/controllers/batch.py
rename to vendor/registrasion/registrasion/controllers/batch.py
diff --git a/vendor/registrasion/controllers/cart.py b/vendor/registrasion/registrasion/controllers/cart.py
similarity index 100%
rename from vendor/registrasion/controllers/cart.py
rename to vendor/registrasion/registrasion/controllers/cart.py
diff --git a/vendor/registrasion/controllers/category.py b/vendor/registrasion/registrasion/controllers/category.py
similarity index 100%
rename from vendor/registrasion/controllers/category.py
rename to vendor/registrasion/registrasion/controllers/category.py
diff --git a/vendor/registrasion/controllers/conditions.py b/vendor/registrasion/registrasion/controllers/conditions.py
similarity index 100%
rename from vendor/registrasion/controllers/conditions.py
rename to vendor/registrasion/registrasion/controllers/conditions.py
diff --git a/vendor/registrasion/controllers/credit_note.py b/vendor/registrasion/registrasion/controllers/credit_note.py
similarity index 100%
rename from vendor/registrasion/controllers/credit_note.py
rename to vendor/registrasion/registrasion/controllers/credit_note.py
diff --git a/vendor/registrasion/controllers/discount.py b/vendor/registrasion/registrasion/controllers/discount.py
similarity index 100%
rename from vendor/registrasion/controllers/discount.py
rename to vendor/registrasion/registrasion/controllers/discount.py
diff --git a/vendor/registrasion/controllers/flag.py b/vendor/registrasion/registrasion/controllers/flag.py
similarity index 100%
rename from vendor/registrasion/controllers/flag.py
rename to vendor/registrasion/registrasion/controllers/flag.py
diff --git a/vendor/registrasion/controllers/for_id.py b/vendor/registrasion/registrasion/controllers/for_id.py
similarity index 100%
rename from vendor/registrasion/controllers/for_id.py
rename to vendor/registrasion/registrasion/controllers/for_id.py
diff --git a/vendor/registrasion/controllers/invoice.py b/vendor/registrasion/registrasion/controllers/invoice.py
similarity index 100%
rename from vendor/registrasion/controllers/invoice.py
rename to vendor/registrasion/registrasion/controllers/invoice.py
diff --git a/vendor/registrasion/controllers/item.py b/vendor/registrasion/registrasion/controllers/item.py
similarity index 100%
rename from vendor/registrasion/controllers/item.py
rename to vendor/registrasion/registrasion/controllers/item.py
diff --git a/vendor/registrasion/controllers/product.py b/vendor/registrasion/registrasion/controllers/product.py
similarity index 100%
rename from vendor/registrasion/controllers/product.py
rename to vendor/registrasion/registrasion/controllers/product.py
diff --git a/vendor/registrasion/exceptions.py b/vendor/registrasion/registrasion/exceptions.py
similarity index 100%
rename from vendor/registrasion/exceptions.py
rename to vendor/registrasion/registrasion/exceptions.py
diff --git a/vendor/registrasion/forms.py b/vendor/registrasion/registrasion/forms.py
similarity index 100%
rename from vendor/registrasion/forms.py
rename to vendor/registrasion/registrasion/forms.py
diff --git a/vendor/registrasion/migrations/0001_initial.py b/vendor/registrasion/registrasion/migrations/0001_initial.py
similarity index 100%
rename from vendor/registrasion/migrations/0001_initial.py
rename to vendor/registrasion/registrasion/migrations/0001_initial.py
diff --git a/vendor/registrasion/migrations/0002_auto_20160822_0034.py b/vendor/registrasion/registrasion/migrations/0002_auto_20160822_0034.py
similarity index 100%
rename from vendor/registrasion/migrations/0002_auto_20160822_0034.py
rename to vendor/registrasion/registrasion/migrations/0002_auto_20160822_0034.py
diff --git a/vendor/registrasion/migrations/0003_auto_20160904_0235.py b/vendor/registrasion/registrasion/migrations/0003_auto_20160904_0235.py
similarity index 100%
rename from vendor/registrasion/migrations/0003_auto_20160904_0235.py
rename to vendor/registrasion/registrasion/migrations/0003_auto_20160904_0235.py
diff --git a/vendor/registrasion/migrations/0004_groupmemberdiscount_groupmemberflag.py b/vendor/registrasion/registrasion/migrations/0004_groupmemberdiscount_groupmemberflag.py
similarity index 100%
rename from vendor/registrasion/migrations/0004_groupmemberdiscount_groupmemberflag.py
rename to vendor/registrasion/registrasion/migrations/0004_groupmemberdiscount_groupmemberflag.py
diff --git a/vendor/registrasion/migrations/0005_auto_20160905_0945.py b/vendor/registrasion/registrasion/migrations/0005_auto_20160905_0945.py
similarity index 100%
rename from vendor/registrasion/migrations/0005_auto_20160905_0945.py
rename to vendor/registrasion/registrasion/migrations/0005_auto_20160905_0945.py
diff --git a/vendor/registrasion/registrasion/migrations/0006_auto_20170526_1624.py b/vendor/registrasion/registrasion/migrations/0006_auto_20170526_1624.py
new file mode 100644
index 00000000..c65625d0
--- /dev/null
+++ b/vendor/registrasion/registrasion/migrations/0006_auto_20170526_1624.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.2 on 2017-05-26 16:24
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('registrasion', '0005_auto_20160905_0945'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='category',
+            name='render_type',
+            field=models.IntegerField(choices=[(1, 'Radio button'), (2, 'Quantity boxes'), (3, 'Product selector and quantity box'), (4, 'Checkbox button')], help_text='The registration form will render this category in this style.', verbose_name='Render type'),
+        ),
+    ]
diff --git a/vendor/registrasion/migrations/0006_auto_20170702_2233.py b/vendor/registrasion/registrasion/migrations/0006_auto_20170702_2233.py
similarity index 100%
rename from vendor/registrasion/migrations/0006_auto_20170702_2233.py
rename to vendor/registrasion/registrasion/migrations/0006_auto_20170702_2233.py
diff --git a/vendor/registrasion/migrations/__init__.py b/vendor/registrasion/registrasion/migrations/__init__.py
similarity index 100%
rename from vendor/registrasion/migrations/__init__.py
rename to vendor/registrasion/registrasion/migrations/__init__.py
diff --git a/vendor/registrasion/models/__init__.py b/vendor/registrasion/registrasion/models/__init__.py
similarity index 100%
rename from vendor/registrasion/models/__init__.py
rename to vendor/registrasion/registrasion/models/__init__.py
diff --git a/vendor/registrasion/models/commerce.py b/vendor/registrasion/registrasion/models/commerce.py
similarity index 100%
rename from vendor/registrasion/models/commerce.py
rename to vendor/registrasion/registrasion/models/commerce.py
diff --git a/vendor/registrasion/models/conditions.py b/vendor/registrasion/registrasion/models/conditions.py
similarity index 100%
rename from vendor/registrasion/models/conditions.py
rename to vendor/registrasion/registrasion/models/conditions.py
diff --git a/vendor/registrasion/models/inventory.py b/vendor/registrasion/registrasion/models/inventory.py
similarity index 100%
rename from vendor/registrasion/models/inventory.py
rename to vendor/registrasion/registrasion/models/inventory.py
diff --git a/vendor/registrasion/models/people.py b/vendor/registrasion/registrasion/models/people.py
similarity index 100%
rename from vendor/registrasion/models/people.py
rename to vendor/registrasion/registrasion/models/people.py
diff --git a/vendor/registrasion/reporting/__init__.py b/vendor/registrasion/registrasion/reporting/__init__.py
similarity index 100%
rename from vendor/registrasion/reporting/__init__.py
rename to vendor/registrasion/registrasion/reporting/__init__.py
diff --git a/vendor/registrasion/reporting/forms.py b/vendor/registrasion/registrasion/reporting/forms.py
similarity index 100%
rename from vendor/registrasion/reporting/forms.py
rename to vendor/registrasion/registrasion/reporting/forms.py
diff --git a/vendor/registrasion/reporting/reports.py b/vendor/registrasion/registrasion/reporting/reports.py
similarity index 100%
rename from vendor/registrasion/reporting/reports.py
rename to vendor/registrasion/registrasion/reporting/reports.py
diff --git a/vendor/registrasion/reporting/views.py b/vendor/registrasion/registrasion/reporting/views.py
similarity index 100%
rename from vendor/registrasion/reporting/views.py
rename to vendor/registrasion/registrasion/reporting/views.py
diff --git a/vendor/registrasion/registrasion/templates/registrasion/amend_registration.html b/vendor/registrasion/registrasion/templates/registrasion/amend_registration.html
new file mode 100644
index 00000000..803602c4
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/amend_registration.html
@@ -0,0 +1,5 @@
+{% extends "registrasion/amend_registration_.html" %}
+{% comment %}
+  Blocks that you can override:
+
+{% endcomment %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/amend_registration_.html b/vendor/registrasion/registrasion/templates/registrasion/amend_registration_.html
new file mode 100644
index 00000000..fcae5fde
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/amend_registration_.html
@@ -0,0 +1,81 @@
+{% extends "registrasion/base.html" %}
+{% load registrasion_tags %}
+
+{% block title %}Amend registration{% endblock %}
+{% block heading %}Amend registration{% endblock %}
+
+{% block content %}
+
+  <dl>
+    <dt>Attendee name</dt>
+    <dd>{{ user.attendee.attendeeprofilebase.attendee_name }}</dd>
+    <dt>Attendee ID</dt>
+    <dd>{{ user.id }}</dd>
+  </dl>
+
+  <h2>Item summary</h2>
+
+  <div class="panel panel-default">
+    <div class="panel-heading">
+      <h3 class="panel-title">Paid Items</h3>
+    </div>
+
+    <div class="panel-body">
+      <div class="alert alert-warning">
+        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.
+      </div>
+    </div>
+
+    {% include "registrasion/snippets/items_list.html" with items=paid ul_class="list-group" li_class="list-group-item" %}
+  </div>
+
+  <div class="panel panel-default">
+    <div class="panel-heading">
+      <h3 class="panel-title">Cancelled Items</h3>
+    </div>
+
+    {% if cancelled %}
+      {% include "registrasion/snippets/items_list.html" with items=cancelled ul_class="list-group" li_class="list-group-item" %}
+    {% else %}
+      <div class="panel-body">No cancelled items.</div>
+    {% endif %}
+  </div>
+
+  <div class="panel panel-default">
+    <div class="panel-heading">
+      <h3 class="panel-title">Amend pending items</h3>
+    </div>
+
+    <form method="POST">
+      <div class="panel-body">
+        {% csrf_token %}
+        {% include "registrasion/form.html" with form=form %}
+      </div>
+
+      <div class="panel-footer">
+        <input class="btn btn-primary" type="submit">
+        <!-- todo: disable the checkout button if the form changes. -->
+        <a class="btn btn-default" href="{% url "checkout" user.id %}">Check out cart and view invoice</a>
+      </div>
+    </form>
+  </div>
+
+  <div class="panel panel-default">
+    <div class="panel-heading">
+      <h3 class="panel-title">Apply voucher</h3>
+    </div>
+
+    <form method="POST">
+      <div class="panel-body">
+        {% csrf_token %}
+        {% include "registrasion/form.html" with form=voucher_form %}
+      </div>
+      <div class="panel-footer">
+        <input class="btn btn-primary" type="submit">
+      </div>
+    </form>
+  </div>
+
+{% endblock %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/badges.html b/vendor/registrasion/registrasion/templates/registrasion/badges.html
new file mode 100644
index 00000000..7e89db23
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/badges.html
@@ -0,0 +1,5 @@
+{% extends "registrasion/badges_.html" %}
+{% comment %}
+  Blocks that you can override:
+
+{% endcomment %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/badges_.html b/vendor/registrasion/registrasion/templates/registrasion/badges_.html
new file mode 100644
index 00000000..11ca9fc1
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/badges_.html
@@ -0,0 +1,17 @@
+{% extends "registrasion/base.html" %}
+{% load registrasion_tags %}
+
+{% block title %}Generate badges zip{% endblock  %}
+{% block heading %}Generate badges zip{% endblock %}
+
+{% block content %}
+
+  <form method="POST">
+    {% csrf_token %}
+    {% include "registrasion/form.html" with form=form %}
+    <br/>
+    <!-- TODO: include themed submit button -->
+    <input type="submit">
+  </form>
+
+{% endblock %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/base.html b/vendor/registrasion/registrasion/templates/registrasion/base.html
new file mode 100644
index 00000000..575f1f29
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/base.html
@@ -0,0 +1,24 @@
+{% extends "site_base.html" %}
+
+{% block title %}{% endblock %}
+
+{% block body_outer %}
+  {% block body %}
+
+    {% block heading_outer %}
+      <h1>{% block heading %}{% endblock %}</h1>
+    {% endblock %}
+
+    {% block lede_outer %}
+      <p class="lead">{% block lede %}{% endblock %}
+    {% endblock %}
+
+    {% block content_outer %}
+      <div>
+        {% block content %}
+
+        {% endblock %}
+      </div>
+    {% endblock %}
+  {% endblock %}
+{% endblock %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/checkout_errors.html b/vendor/registrasion/registrasion/templates/registrasion/checkout_errors.html
new file mode 100644
index 00000000..8989daf1
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/checkout_errors.html
@@ -0,0 +1,7 @@
+{% extends "registrasion/checkout_errors_.html" %}
+{% comment %}
+  Blocks that you can override:
+
+  - fix_intro
+
+{% endcomment %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/checkout_errors_.html b/vendor/registrasion/registrasion/templates/registrasion/checkout_errors_.html
new file mode 100644
index 00000000..981b9094
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/checkout_errors_.html
@@ -0,0 +1,35 @@
+{% extends "registrasion/base.html" %}
+{% load registrasion_tags %}
+
+{% block title %}Cannot check out{% endblock %}
+{% block heading %}Oh No!{% endblock %}
+{% block lede %}We can't check out your current selections because of the following errors:{% endblock %}
+
+{% block content %}
+
+  <div class="alert alert-danger">
+    <ul>
+      {% for error in error_list %}
+        <li>{{ error.message }}</li>
+      {% endfor %}
+    </ul>
+  </div>
+
+  <div class="panel panel-danger">
+    {% block fix_intro_outer %}
+      <div class="panel-body">
+        {% block fix_intro %}
+          We can automatically try to remove products and vouchers that aren't
+          available anymore, or you can return to the dashboard and try to fix
+          it yourself.
+        {% endblock %}
+      </div>
+    {% endblock %}
+
+    <div class="panel-footer">
+      <a class="btn btn-primary" href="{% url "checkout" %}?fix_errors=true">Try fixing these errors</a>
+      <a class="btn btn-default" href="{% url "dashboard" %}">Return to dashboard</a>
+    </div>
+  </div>
+
+{% endblock %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/credit_note.html b/vendor/registrasion/registrasion/templates/registrasion/credit_note.html
new file mode 100644
index 00000000..c5709aff
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/credit_note.html
@@ -0,0 +1,9 @@
+{% extends "registrasion/credit_note_.html" %}
+{% comment %}
+  Blocks that you can override:
+  - apply_to_invoice
+  - cancellation_fee
+  - refund_actions
+  - manual_refund_actions
+
+{% endcomment %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/credit_note_.html b/vendor/registrasion/registrasion/templates/registrasion/credit_note_.html
new file mode 100644
index 00000000..c7005096
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/credit_note_.html
@@ -0,0 +1,103 @@
+{% extends "registrasion/base.html" %}
+{% load registrasion_tags %}
+
+{% block title %}Credit Note{% endblock %}
+{% block heading %}Credit Note{% endblock %}
+
+{% block content %}
+
+  {% block credit_note_info_outer %}
+    <div>
+      {% block credit_note_info %}
+        {% with note_user=credit_note.invoice.user %}
+          <dl>
+            <dt>Number</dt><dd> {{ credit_note.id }}</dd>
+            <dt>Attention</dt><dd> {{ credit_note.invoice.recipient }}</dd>
+            <dt>User</dt>
+            <dd> {{ credit_note.invoice.user.email }} ({{ credit_note.invoice.user.id}})</dd>
+            <dt>Value</dt><dd> {{ credit_note.value }}</dd>
+            <dt>Status</dt><dd> {{ credit_note.status }}</dd>
+          </dl>
+        {% endwith %}
+
+        <p>
+            This credit note was generated from funds excess from
+            <a href="{% url "invoice" credit_note.invoice.id %}">
+              invoice {{ credit_note.invoice.id }}</a>.
+        </p>
+      {% endblock %}
+    </div>
+  {% endblock %}
+
+  {% if credit_note.is_unclaimed %}
+    {% block staff_actions %}
+      <form method="post" action="">
+        {% csrf_token %}
+
+        {% block apply_to_invoice_outer %}
+          <div>
+            {% block apply_to_invoice %}
+              <h3>Apply to invoice</h3>
+              <p>You can apply this credit note to an unpaid invoice.</p>
+
+              <p>
+                <strong>This credit note belongs to:</strong> {{ credit_note.invoice.user.email }} ({{ credit_note.invoice.user.id}}).
+                You can apply this credit note to any user's invoice.
+              </p>
+
+              {% include "registrasion/form.html" with form=apply_form %}
+              <!-- TODO: make this button less bootstrap-dependent -->
+              <div class="form-actions">
+                  <input class="btn btn-primary" type="submit" value="Apply to invoice" />
+              </div>
+            {% endblock %}
+          </div>
+        {% endblock %}
+
+        {% block cancellation_fee_outer %}
+          <div>
+            {% block cancellation_fee %}
+              <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>
+            {% endblock %}
+          </div>
+        {% endblock %}
+
+        {% include "registrasion/form.html" with form=cancellation_fee_form %}
+        <!-- TODO: make this button less bootstrap-dependent -->
+        <div class="form-actions">
+            <input class="btn btn-primary" type="submit" value="Generate fee" />
+        </div>
+
+        {% block refund_actions_outer %}
+          <div>
+            {% block refund_actions %}
+              {% with credit_note_id=credit_note.id %}
+                {% include_if_exists "registrasion/stripe/link_to_refunds.html" %}
+              {% endwith %}
+            {% endblock %}
+          </div>
+        {% endblock %}
+
+        {% block manual_refund_actions_outer %}
+          <div>
+            {% block manual_refund_actions %}
+              <h3>Manual refund</h3>
+              <p>You can mark this credit note as refunded, and handle the refund manually.
+              </p>
+
+              {% include "registrasion/form.html" with form=refund_form %}
+              <!-- TODO: make this button less bootstrap-dependent -->
+              <div class="form-actions">
+                  <input class="btn btn-primary" type="submit" value="Mark as refunded" />
+              </div>
+            {% endblock %}
+          </div>
+        {% endblock %}
+      </form>
+    {% endblock %}
+  {% endif %}
+
+{% endblock %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/dashboard_widget.html b/vendor/registrasion/registrasion/templates/registrasion/dashboard_widget.html
new file mode 100644
index 00000000..4c8638de
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/dashboard_widget.html
@@ -0,0 +1,11 @@
+{% extends "registrasion/dashboard_widget_.html" %}
+
+{% comment %}
+  Blocks that you can override:
+
+  - heading_actions
+  - heading
+  - panel_content
+  - credit
+
+{% endcomment %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/dashboard_widget_.html b/vendor/registrasion/registrasion/templates/registrasion/dashboard_widget_.html
new file mode 100644
index 00000000..bd703f72
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/dashboard_widget_.html
@@ -0,0 +1,135 @@
+{% load i18n %}
+{% load registrasion_tags %}
+
+{% sold_out_and_unregistered as sold_out %}
+
+<div class="panel panel-default">
+  <div class="panel-heading">
+    {% block heading_actions_outer %}
+      <div class="pull-right">
+        {% block heading_actions %}
+          {% if not user.attendee.completed_registration %}
+
+            {% if not sold_out %}
+              <a href="{% url "guided_registration" %}" class="btn btn-xs btn-default">
+                  <i class="fa fa-plus-sign"></i> Register for the conference
+              </a>
+            {% endif %}
+
+          {% 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> Check out and pay
+              </a>
+            {% endif %}
+
+          {% endif %}
+
+          <a href="{% url "voucher_code" %}" class="btn btn-xs btn-default">
+              <i class="fa fa-heart"></i> Enter a voucher code
+          </a>
+
+          {% if user.is_staff %}
+
+            <a href="{% url "reports_list" %}" class="btn btn-xs btn-default">
+              <i class="fa fa-dashboard"></i> View reports</a>
+
+          {% endif %}
+        {% endblock %}
+      </div>
+    {% endblock %}
+
+    {% block heading_outer %}
+      <h3 class="panel-title">
+        <i class="fa fa-ticket"></i>
+        {% block heading %}
+          {% trans "Registration" %}
+        {% endblock %}
+      </h3>
+    {% endblock %}
+
+  </div>
+
+  {% block panel_content_outer %}
+    <div class="panel-body">
+      {% block panel_content %}
+
+        {% if not user.attendee.completed_registration %}
+
+          {% if not sold_out %}
+            {% block begin_registration_outer %}
+              <p>
+                {% block begin_registration %}
+                  To attend the conference, you must purchase a ticket. <a href="{% url "guided_registration" %}">Use our registration form to purchase your ticket</a>.
+                {% endblock %}
+              </p>
+            {% endblock %}
+          {% else %}
+            {% block sold_out_outer %}
+              <p>
+                {% block sold_out %}
+                  There are no tickets available to the general public. If you have a voucher code, you can <a href="{% url "voucher_code" %}">enter that voucher code</a>, which may unlock tickets for you.
+                {% endblock %}
+              </p>
+            {% endblock %}
+          {% endif %}
+
+        {% else %}
+
+          {% items_pending as pending %}
+          {% if pending %}
+            <h4>Items pending payment</h4>
+            {% include "registrasion/snippets/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/snippets/items_list.html" with items=purchased %}
+          {% endif %}
+
+          <h4>Add/Update items</h4>
+          {% available_categories as categories %}
+          {% include "registrasion/snippets/category_list.html" with categories=categories %}
+
+          {% invoices as invoices %}
+          {% if invoices %}
+          <h4>Invoices</h4>
+            <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 %}
+            {% block available_credit_outer %}
+              <p>
+                {% block available_credit %}
+                  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.
+                {% endblock %}
+              </p>
+            {% endblock %}
+
+          {% endif %}
+        {% endif %}
+      {% endblock %}
+
+    </div>
+  {% endblock %}
+</div>
diff --git a/vendor/registrasion/registrasion/templates/registrasion/emails/invoice_created/message.html b/vendor/registrasion/registrasion/templates/registrasion/emails/invoice_created/message.html
new file mode 100644
index 00000000..ff5bef81
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/emails/invoice_created/message.html
@@ -0,0 +1,13 @@
+{% load i18n %}
+
+{% if invoice.is_unpaid %}
+
+  {% with "http://"|add:current_site as current_host %}
+    {% include "registrasion/invoice/unpaid_notice.html" %}
+  {% endwith %}
+
+  <hr />
+
+{% endif %}
+
+{% include "registrasion/invoice/details.html" %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/emails/invoice_created/subject.txt b/vendor/registrasion/registrasion/templates/registrasion/emails/invoice_created/subject.txt
new file mode 100644
index 00000000..f578d53c
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/emails/invoice_created/subject.txt
@@ -0,0 +1 @@
+{% load i18n %}Invoice {{ invoice.id }}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/emails/invoice_updated/message.html b/vendor/registrasion/registrasion/templates/registrasion/emails/invoice_updated/message.html
new file mode 100644
index 00000000..aa59ddd6
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/emails/invoice_updated/message.html
@@ -0,0 +1 @@
+{% include "registrasion/emails/invoice_created/message.html" %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/emails/invoice_updated/subject.txt b/vendor/registrasion/registrasion/templates/registrasion/emails/invoice_updated/subject.txt
new file mode 100644
index 00000000..f9a74eec
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/emails/invoice_updated/subject.txt
@@ -0,0 +1 @@
+{% load i18n %}{{ invoice.get_status_display }} -- Invoice {{ invoice.id }}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/form.html b/vendor/registrasion/registrasion/templates/registrasion/form.html
new file mode 100644
index 00000000..494eeaeb
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/form.html
@@ -0,0 +1,9 @@
+{% comment %}
+  form.html is included whenever there is a form in one of our base templates.
+
+  To override how your form is rendered, just override form.html. The form is
+  included as `form`.
+
+{% endcomment %}
+
+{{ form }}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/guided_registration.html b/vendor/registrasion/registrasion/templates/registrasion/guided_registration.html
new file mode 100644
index 00000000..dd7d460a
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/guided_registration.html
@@ -0,0 +1,8 @@
+{% extends "registrasion/guided_registration_.html" %}
+{% comment %}
+  Blocks that you can override:
+
+  - discounts_intro
+  - products_intro
+
+{% endcomment %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/guided_registration_.html b/vendor/registrasion/registrasion/templates/registrasion/guided_registration_.html
new file mode 100644
index 00000000..9a8aa9be
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/guided_registration_.html
@@ -0,0 +1,55 @@
+{% extends "registrasion/base.html" %}
+
+{% block title %}Conference registrasion{% endblock %}
+{% block heading %}Conference registration{% endblock %}
+{% block lede %}Step {{ current_step }} of {{ total_steps }} &ndash; {{ title }}{% endblock %}
+
+{% block content %}
+
+  <form method="post" action="">
+    {% csrf_token %}
+
+    {% for section in sections %}
+      <div class="panel panel-default">
+        <div class="panel-heading">
+          <h3 class="panel-title">{{ section.title }}</h3>
+        </div>
+
+        <div class="panel-body">
+
+          {% if section.description %}
+            <div class="well">
+              {{ section.description|safe }}
+            </div>
+          {% endif %}
+
+          {% if section.discounts %}
+            <div class="panel panel-info">
+              {% include "registrasion/snippets/discounts_list.html" with discounts=section.discounts %}
+              {% block discounts_intro_outer %}
+                <div class="panel-footer">
+                  {% block discounts_intro %}
+                    The best available discounts will be automatically applied
+                    to any selections you make.
+                  {% endblock %}
+                </div>
+              {% endblock %}
+
+            </div>
+          {% endif %}
+
+          {% include "registrasion/form.html" with form=section.form %}
+        </div>
+      </div>
+    {% endfor %}
+
+    <div class="form-actions">
+      {% if current_step > 1 %}
+        <a class="btn btn-default" href="{% url "guided_registration" current_step|add:"-1" %}">Back</a>
+      {% endif %}
+      <input class="btn btn-primary" type="submit" value="Next" />
+    </div>
+  </form>
+
+
+{% endblock %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/invoice.html b/vendor/registrasion/registrasion/templates/registrasion/invoice.html
new file mode 100644
index 00000000..fa952485
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/invoice.html
@@ -0,0 +1,14 @@
+{% extends "registrasion/invoice_.html" %}
+{% comment %}
+  Blocks that you can override:
+
+  - payment_actions
+  - refund_actions
+
+  This template includes two sub-templates:
+
+  - registrasion/invoice/details.html
+  - registrasion/invoice/unpaid_notice.html
+
+
+{% endcomment %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/invoice/details.html b/vendor/registrasion/registrasion/templates/registrasion/invoice/details.html
new file mode 100644
index 00000000..d09f2195
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/invoice/details.html
@@ -0,0 +1,11 @@
+{% extends "registrasion/invoice/details_.html" %}
+{% comment %}
+  Blocks that you can override:
+
+  - heading
+  - subheading
+  - invoice_intro
+  - extra_line_items
+  - contact_info
+
+{% endcomment %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/invoice/details_.html b/vendor/registrasion/registrasion/templates/registrasion/invoice/details_.html
new file mode 100644
index 00000000..e4ab2f8f
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/invoice/details_.html
@@ -0,0 +1,111 @@
+{% load registrasion_tags %}
+
+<div class="panel panel-primary">
+  <div class="panel-heading">
+    {% block heading_outer %}
+      <h2>{% block heading %}Invoice{% endblock %}</h2>
+    {% endblock %}
+    {% block subheading_outer %}
+      <div>{% block subheading %}Subtitle{% endblock %}</div>
+    {% endblock %}
+  </div>
+
+  {% with invoice_user=invoice.cart.user %}
+    <table class="table">
+      <tr><th>Number</th><td> {{ invoice.id }}</td></tr>
+      <tr><th>Status</th><td> {{ invoice.get_status_display }}</td></tr>
+      <tr><th>Issue date</th><td> {{ invoice.issue_time|date:"DATE_FORMAT" }}</td></tr>
+
+      {% if not invoice.is_void %}
+        <tr><th>Due</th><td> {{ invoice.due_time|date:"DATETIME_FORMAT"}}</td></tr>
+      {% endif %}
+
+      <tr><th>Recipient</th><td> {{ invoice.recipient|linebreaksbr}}</td></tr>
+    </table>
+  {% endwith %}
+
+  <div class="panel-body">
+    {% block invoice_intro_outer %}
+      <div class="alert alert-info">
+        {% block invoice_intro %}
+          This invoice has been issued as a result of an application to attend (conference name).
+        {% endblock %}
+      </div>
+    {% endblock %}
+
+    <div class="panel panel-default">
+      <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 %}
+
+        {% block extra_line_items %}
+
+        {% endblock %}
+
+        <tr>
+          <th colspan="3">TOTAL</th>
+          <td class="text-right">${{ invoice.value }}</td>
+        </tr>
+      </table>
+    </div>
+
+    <div class="panel panel-info">
+      <div class="panel-heading">
+        <h3 class="panel-title">Balance</h3>
+      </div>
+      <table class="table table-striped">
+        <tr>
+          <td colspan="3">Total payments:</td>
+          <td class="text-right">${{ invoice.total_payments }}</td>
+        </tr>
+        <tr>
+          <td colspan="3">Balance due:</td>
+          <td class="text-right">${{ invoice.balance_due }}</td>
+        </tr>
+      </table>
+    </div>
+
+    {% if invoice.paymentbase_set.all %}
+      <div class="panel panel-info">
+        <div class="panel-heading">
+          <h4 class="panel-title">Payments received</h4>
+        </div>
+        {% include "registrasion/snippets/payment_list.html" with payments=invoice.paymentbase_set.all %}
+      </div>
+    {% endif %}
+
+    <div class="panel panel-default">
+      {% block contact_info_heading_outer %}
+        <div class="panel-heading">
+          <h3 class="panel-title">
+            {% block contact_info_heading %}
+              Contact Information
+            {% endblock %}
+          </h3>
+        </div>
+      {% endblock %}
+
+      {% block contact_info_outer %}
+        <div class="panel-body">
+          <p>
+            {% block contact_info %}
+              Direct inquiries to (EMAIL ADDRESS)
+            {% endblock %}
+          </p>
+        </div>
+      {% endblock %}
+    </div>
+  </div>
+</div>
diff --git a/vendor/registrasion/registrasion/templates/registrasion/invoice/unpaid_notice.html b/vendor/registrasion/registrasion/templates/registrasion/invoice/unpaid_notice.html
new file mode 100644
index 00000000..3ec9e386
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/invoice/unpaid_notice.html
@@ -0,0 +1,11 @@
+<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>
+
+{% url "invoice_access" invoice.user.attendee.access_code as access_url %}
+{% url "invoice" invoice.id invoice.user.attendee.access_code as invoice_url %}
+
+<p>You can send the following links to your accounts department to pay for your registration:</p>
+
+<ul>
+  <li>{{ current_host|add:access_url|urlize }} &ndash; your most recent invoice</li>
+  <li>{{ current_host|add:invoice_url|urlize }} &ndash; this invoice, even if it becomes void.</li>
+</ul>
diff --git a/vendor/registrasion/registrasion/templates/registrasion/invoice_.html b/vendor/registrasion/registrasion/templates/registrasion/invoice_.html
new file mode 100644
index 00000000..432ff821
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/invoice_.html
@@ -0,0 +1,55 @@
+{% extends "registrasion/base.html" %}
+{% load bootstrap %}
+{% load registrasion_tags %}
+
+{% block content %}
+
+  {% if invoice.is_unpaid %}
+
+    <div class="alert alert-warning">
+    {% with scheme=request.scheme host=request.get_host %}
+      {% with scheme|add:"://"|add:host as current_host %}
+        {% include "registrasion/invoice/unpaid_notice.html" with current_host=current_host %}
+      {% endwith %}
+    {% endwith %}
+    </div>
+
+    <div class="panel panel-default">
+
+      {% block payment_actions_outer %}
+        <div class="panel-footer">
+          {% block payment_actions %}
+
+            {% with invoice_id=invoice.id access_code=invoice.user.attendee.access_code %}
+              {% include_if_exists "registrasion/stripe/link_to_payment.html" %}
+            {% endwith %}
+
+            {% if user.is_staff %}
+              <a class="btn btn-default" href="{% url "manual_payment" invoice.id %}">Apply manual payment</a>
+            {% endif %}
+          {% endblock %}
+          <a class="btn btn-default" href="{% url "dashboard" %}">Return to dashboard</a>
+
+        </div>
+      {% endblock %}
+    </div>
+
+  {% elif invoice.is_paid %}
+
+    {% if user.is_staff %}
+      {% block refund_actions_outer %}
+        <div class="panel-footer">
+          {% block refund_actions %}
+            <a class="btn btn-primary" href="{% url "refund" invoice.id %}">Refund by issuing credit note</a>
+            <a class="btn btn-default" href="{% url "manual_payment" invoice.id %}">Apply manual payment/refund</a>
+            <a class="btn btn-default" href="{% url "dashboard" %}">Return to dashboard</a>
+          {% endblock %}
+        </div>
+      {% endblock %}
+    {% endif %}
+
+  {% endif %}
+
+  {% include "registrasion/invoice/details.html" %}
+
+{% endblock %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/invoice_mailout.html b/vendor/registrasion/registrasion/templates/registrasion/invoice_mailout.html
new file mode 100644
index 00000000..31a73be0
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/invoice_mailout.html
@@ -0,0 +1,5 @@
+{% extends "registrasion/invoice_mailout_.html" %}
+{% comment %}
+  Blocks that you can override:
+
+{% endcomment %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/invoice_mailout_.html b/vendor/registrasion/registrasion/templates/registrasion/invoice_mailout_.html
new file mode 100644
index 00000000..500b9642
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/invoice_mailout_.html
@@ -0,0 +1,32 @@
+{% extends "registrasion/base.html" %}
+{% load registrasion_tags %}
+
+{% block title %}Send reminder e-mails{% endblock %}
+{% block heading %}Send reminder e-mails{% endblock %}
+
+{% block content %}
+
+<form method="POST">
+  {% csrf_token %}
+  {% include "registrasion/form.html" with form=form %}
+  <br/>
+  <!-- TODO: include themed submit button -->
+  <input type="submit">
+</form>
+
+{% if emails %}
+  <h3>Previews</h3>
+
+  {% for email in emails %}
+    <dl>
+      <dt>From</dt><dd>{{ email.from_email }}</dd>
+      <dt>To</dt><dd>{{ email.recipient_list|join:", " }}</dd>
+      <dt>Subject</dt><dd>{{ email.subject }}</dd>
+      <dt>Body</dt><dd><pre>{{ email.body }}</pre></dd>
+    </dl>
+    <hr />
+  {% endfor %}
+
+{% endif %}
+
+{% endblock %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/manual_payment.html b/vendor/registrasion/registrasion/templates/registrasion/manual_payment.html
new file mode 100644
index 00000000..fad6d5ac
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/manual_payment.html
@@ -0,0 +1,5 @@
+{% extends "registrasion/manual_payment_.html" %}
+{% comment %}
+  Blocks that you can override:
+
+{% endcomment %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/manual_payment_.html b/vendor/registrasion/registrasion/templates/registrasion/manual_payment_.html
new file mode 100644
index 00000000..d47d353f
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/manual_payment_.html
@@ -0,0 +1,33 @@
+{% extends "registrasion/base.html" %}
+
+{% block title %}Apply manual payment{% endblock %}
+{% block heading %}Apply manual payment{% endblock %}
+{% block lede %}Enter a reference and the amount of the payment. A refund is a negative
+payment.{% endblock %}
+
+{% block content %}
+
+  <dl>
+    <dt>Invoice ID:</dt>
+    <dd>{{ invoice.id }}</dd>
+    <dt>Recipient</dt>
+    <dd>{{ invoice.recipient }}</dd>  
+  </dl>
+
+  <form method="post" action="">
+    {% csrf_token %}
+
+    {% include "registrasion/form.html" with form=form %}
+
+    <!-- TODO: make this button less bootstrap-dependent -->
+    <div class="form-actions">
+      <input class="btn btn-primary" type="submit" value="Apply payment" />
+      <a class="btn btn-default" href="{% url "invoice" invoice.id %}">Return to invoice</a>
+    </div>
+  </form>
+
+  <h2>Past payments</h2>
+
+  {% include "registrasion/snippets/payment_list.html" with payments=invoice.paymentbase_set.all %}
+
+{% endblock %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/product_category.html b/vendor/registrasion/registrasion/templates/registrasion/product_category.html
new file mode 100644
index 00000000..ecf3476d
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/product_category.html
@@ -0,0 +1,9 @@
+{% extends "registrasion/product_category_.html" %}
+{% comment %}
+  Blocks that you can override:
+
+  - paid_items_intro
+  - discounts_intro
+  - products_intro
+
+{% endcomment %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/product_category_.html b/vendor/registrasion/registrasion/templates/registrasion/product_category_.html
new file mode 100644
index 00000000..7e0071f6
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/product_category_.html
@@ -0,0 +1,80 @@
+{% extends "registrasion/base.html" %}
+{% load registrasion_tags %}
+
+{% block title %}Select {{ category.name }} products{% endblock %}
+{% block heading %}Select {{ category.name }} products{% endblock %}
+
+{% block content %}
+
+  <form method="post" action="">
+
+    {% csrf_token %}
+
+    <div class="panel panel-default">
+      <div class="panel-heading">
+        <h3 class="panel-title">Enter voucher code</h3>
+      </div>
+      <div class="panel-body">
+        {% include "registrasion/form.html" with form=voucher_form %}
+        <!-- TODO: make this button less bootstrap-dependent -->
+        <div class="form-actions">
+            <input class="btn btn-primary" type="submit" value="Add voucher" />
+        </div>
+      </div>
+    </div>
+
+    <div class="panel panel-primary">
+      <div class="panel-heading">
+        <h3 class="panel-title">{{ category.name }}</h3>
+      </div>
+
+      <div class="panel-body">
+
+        {% block products_intro_outer %}
+          <div class="well">
+            {% block products_intro %}
+              {{ category.description }}
+            {% endblock %}
+          </div>
+        {% endblock %}
+
+        {% items_purchased category as items %}
+        {% if items %}
+          <div class="panel panel-info">
+            {% block paid_items_intro_outer %}
+              <div class="panel-heading">
+                {% block paid_items_intro %}
+                  You have already paid for the following items:
+                {% endblock %}
+              </div>
+            {% endblock %}
+            {% include "registrasion/snippets/items_list.html" with items=items ul_class="list-group" li_class="list-group-item" %}
+          </div>
+        {% endif %}
+
+        {% if discounts %}
+          <div class="panel panel-info">
+            {% include "registrasion/snippets/discounts_list.html" with discounts=discounts %}
+            {% block discounts_intro_outer %}
+              <div class="panel-footer">
+                {% block discounts_intro %}
+                  The best available discounts will be automatically applied to any selections you make.
+                {% endblock %}
+              </div>
+            {% endblock %}
+          </div>
+        {% endif %}
+
+        {% include "registrasion/form.html" with form=form %}
+
+      </div>
+
+      <div class="panel-footer">
+        <input class="btn btn-primary" type="submit" value="Add to cart" />
+        <a href="{% url "dashboard" %}" class="btn btn-default">Return to dashboard</a>
+      </div>
+    </div>
+
+  </form>
+
+{% endblock %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/profile_form.html b/vendor/registrasion/registrasion/templates/registrasion/profile_form.html
new file mode 100644
index 00000000..7c03fcca
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/profile_form.html
@@ -0,0 +1,5 @@
+{% extends "registrasion/profile_form_.html" %}
+{% comment %}
+  Blocks that you can override:
+
+{% endcomment %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/profile_form_.html b/vendor/registrasion/registrasion/templates/registrasion/profile_form_.html
new file mode 100644
index 00000000..5dfd3543
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/profile_form_.html
@@ -0,0 +1,25 @@
+{% extends "registrasion/base.html" %}
+
+{% block title %}Your profile{% endblock %}
+{% block heading %}Your profile{% endblock %}
+{% block lede %}
+  These details will appear on your badge, your invoices, and will be used
+  to order catered food at the conference.
+{% endblock %}
+
+{% block content %}
+
+  <form method="post" action="">
+    {% csrf_token %}
+
+    <div>
+      {% include "registrasion/form.html" with form=form %}
+    </div>
+
+    <!-- TODO: make this button less bootstrap-dependent -->
+    <div class="form-actions">
+        <input class="btn btn-primary" type="submit" value="Save" />
+    </div>
+  </form>
+
+{% endblock %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/report.html b/vendor/registrasion/registrasion/templates/registrasion/report.html
new file mode 100644
index 00000000..88dc5e45
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/report.html
@@ -0,0 +1,5 @@
+{% extends "registrasion/report_.html" %}
+{% comment %}
+  Blocks that you can override:
+
+{% endcomment %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/report_.html b/vendor/registrasion/registrasion/templates/registrasion/report_.html
new file mode 100644
index 00000000..03dc71bc
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/report_.html
@@ -0,0 +1,62 @@
+{% extends "registrasion/base.html" %}
+{% load registrasion_tags %}
+
+{% block title %}{{ title }}{% endblock %}
+{% block heading %}{{ title }}{% endblock %}
+
+{% block content %}
+
+  {% if form %}
+    <div class="panel panel-primary">
+      <form method="GET">
+
+        <div class="panel-body">
+            {% include "registrasion/form.html" with form=form %}
+        </div>
+        <div class="panel-footer">
+          <input class="btn btn-primary" type="submit">
+          <a class="btn btn-default" href="{% url "reports_list" %}">Back to reports list</a>
+        </div>
+
+      </form>
+    </div>
+  {% endif %}
+
+  {% for report in reports %}
+
+    <div class="panel panel-default">
+      <div class="panel-heading">
+
+        <div class="btn-group pull-right">
+          <a class="btn btn-default btn-xs" href="{% report_as_csv forloop.counter0 %}">View as CSV</a>
+        </div>
+
+        <h3 class="panel-title">{{ report.title }}</h3>
+      </div>
+
+      <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>
+    </div>
+
+  {% endfor %}
+
+  <div class="form-actions">
+    <a class="btn btn-default" href="{% url "reports_list" %}">Back to reports list</a>
+  </div>
+
+
+{% endblock %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/reports_list.html b/vendor/registrasion/registrasion/templates/registrasion/reports_list.html
new file mode 100644
index 00000000..77d70906
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/reports_list.html
@@ -0,0 +1,5 @@
+{% extends "registrasion/reports_list_.html" %}
+{% comment %}
+  Blocks that you can override:
+
+{% endcomment %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/reports_list_.html b/vendor/registrasion/registrasion/templates/registrasion/reports_list_.html
new file mode 100644
index 00000000..b4dfdec0
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/reports_list_.html
@@ -0,0 +1,22 @@
+{% extends "registrasion/base.html" %}
+{% load registrasion_tags %}
+
+{% block title %}Registration reports{% endblock %}
+{% block heading %}Registration reports{% endblock %}
+
+{% block content %}
+
+  <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 %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/review.html b/vendor/registrasion/registrasion/templates/registrasion/review.html
new file mode 100644
index 00000000..248e25ef
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/review.html
@@ -0,0 +1,10 @@
+{% extends "registrasion/review_.html" %}
+{% comment %}
+  Blocks that you can override:
+  - selected_items_intro
+  - purchased_items_intro
+  - add_to_selection_intro
+  - missing_categories_intro
+  - non_missing_categories_intro
+  - what_next_intro
+{% endcomment %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/review_.html b/vendor/registrasion/registrasion/templates/registrasion/review_.html
new file mode 100644
index 00000000..a1d54236
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/review_.html
@@ -0,0 +1,143 @@
+{% extends "registrasion/base.html" %}
+{% load registrasion_tags %}
+
+{% block title %}Review your selection{% endblock %}
+{% block heading %}Review your selection{% endblock %}
+
+{% block content %}
+
+  {% items_pending as pending %}
+  {% if pending %}
+
+    <div class="panel panel-default">
+      <div class="panel-heading">
+        <h3 class="panel-title">Current selection</h3>
+      </div>
+
+      <div class="panel-body">
+        {% block selected_items_intro_outer %}
+          {% block selected_items_intro %}
+            You've selected the following items, which will be in your invoice
+            when you check out:
+          {% endblock %}
+        {% endblock %}
+      </div>
+
+      {% include "registrasion/snippets/items_list.html" with items=pending ul_class="list-group" li_class="list-group-item" %}
+
+    </div>
+
+    {% items_purchased as purchased %}
+    {% if purchased %}
+      <div class="panel panel-default">
+        <div class="panel-heading">
+          <h3 class="panel-title">Already purchased</h3>
+        </div>
+        <div class="panel-body">
+            <div>
+              {% block purchased_items_intro_outer %}
+                <p>
+                  {% block purchased_items_intro %}
+                    You've already paid for the following items:
+                  {% endblock %}
+                </p>
+              {% endblock %}
+              {% include "registrasion/snippets/items_list.html" with items=purchased suffix="(PAID)" ul_class="list-group" li_class="list-group-item" %}
+            </div>
+        </div>
+      </div>
+    {% endif %}
+
+    <div class="panel panel-default">
+      <div class="panel-heading">
+        <h3 class="panel-title">Add to your selection</h3>
+      </div>
+
+      <div class="panel-body">
+
+        {% block add_to_selection_intro_outer %}
+          <p>{% block add_to_selection_intro %}
+            You can add these items now, or you can come back and add them in a
+            later purchase.
+          {% endblock %}</p>
+        {% endblock %}
+
+        {% missing_categories as missing %}
+        {% if missing %}
+          <div class="alert alert-warning">
+            {% block missing_categories_intro_outer %}
+              <p>
+                {% block missing_categories_intro %}
+                  You have <em>not</em> selected any items from the following
+                  categories. Even if your ticket includes complimentary tickets
+                  to social events, or t-shirts, you must still add them to your
+                  selection.
+                {% endblock %}
+              </p>
+            {% endblock %}
+
+            {% include "registrasion/snippets/category_list.html" with categories=missing %}
+          </div>
+        {% endif %}
+
+        {% block non_missing_categories_intro_outer %}
+        <p>
+          <strong>
+            {% block non_missing_categories_intro %}
+              You can also change your selection from these categories:
+            {% endblock %}
+          </strong>
+        </p>
+        {% endblock %}
+
+        {% available_categories as available %}
+        {% include "registrasion/snippets/category_list.html" with categories=available exclude=missing %}
+      </div>
+    </div>
+
+    <div class="panel panel-default">
+
+      <div class="panel-heading">
+        <h3 class="panel-title">What next?</h3>
+      </div>
+
+      <div class="panel-body">
+
+        {% block what_next_intro_outer %}
+            {% block what_next_intro %}
+              You can either check out and pay for your selections, or return to
+              the dashboard.
+            {% endblock %}
+          </p>
+        {% endblock %}
+
+        <div class="form-actions">
+          <a class="btn btn-primary" href="{% url "checkout" %}">
+            <i class="fa fa-credit-card"></i> Check out and pay
+          </a>
+          <a class="btn btn-default" href="{% url "dashboard" %}">Return to dashboard</a>
+        </div>
+      </div>
+    </div>
+
+  {% else %}
+
+    <div>
+
+      {% block nothing_to_do_intro_outer %}
+        <p>
+          {% block nothing_to_do_intro %}
+            You have no items that need to be paid.
+          {% endblock %}
+        </p>
+      {% endblock %}
+
+      <div class="form-actions">
+        <a class="btn btn-default" href="{% url "dashboard" %}">Return to dashboard</a>
+      </div>
+
+    </div>
+
+  {% endif %}
+
+{% endblock %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/snippets/category_list.html b/vendor/registrasion/registrasion/templates/registrasion/snippets/category_list.html
new file mode 100644
index 00000000..ecd4a478
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/snippets/category_list.html
@@ -0,0 +1,7 @@
+<ul class="{{ ul_class }}">
+  {% for category in categories %}
+    {% if not category in exclude %}
+      <li class="{{ li_class }}"><a href="{% url "product_category" category.id %}">{{ category.name }}</a></li>
+    {% endif %}
+  {% endfor %}
+</ul>
diff --git a/vendor/registrasion/registrasion/templates/registrasion/snippets/discounts_list.html b/vendor/registrasion/registrasion/templates/registrasion/snippets/discounts_list.html
new file mode 100644
index 00000000..add25917
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/snippets/discounts_list.html
@@ -0,0 +1,22 @@
+{% if discounts %}
+
+  {% regroup discounts by discount.description as discounts_grouped %}
+
+  <table class="table">
+    {% for discount_type in discounts_grouped %}
+      <tr>
+        <th rowspan="{{ discount_type.list|length }}">{{ discount_type.grouper }}</th>
+
+        {% for discount in discount_type.list %}
+          {% if not forloop.first %}
+            </tr><tr>
+          {% endif %}
+          <td>
+            {{ discount.quantity }} &times; {{ discount.clause }}
+          </td>
+        {% endfor %}
+    </tr>
+    {% endfor %}
+  </table>
+
+{% endif %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/snippets/items_list.html b/vendor/registrasion/registrasion/templates/registrasion/snippets/items_list.html
new file mode 100644
index 00000000..9bf3c828
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/snippets/items_list.html
@@ -0,0 +1,7 @@
+{% if items %}
+  <ul class="{{ ul_class }}">
+    {% for item in items %}
+      <li class="{{ li_class }}">{{ item.quantity }} &times; {{ item.product }} {{ suffix }}</li>
+    {% endfor %}
+  </ul>
+{% endif %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/snippets/payment_list.html b/vendor/registrasion/registrasion/templates/registrasion/snippets/payment_list.html
new file mode 100644
index 00000000..962cc186
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/snippets/payment_list.html
@@ -0,0 +1,16 @@
+{% if payments %}
+  <table class="table table-striped">
+    <tr>
+      <th>Payment time</th>
+      <th>Reference</th>
+      <th>Amount</th>
+    </tr>
+    {% for payment in payments %}
+      <tr>
+        <td>{{payment.time}}</td>
+        <td>{{payment.reference}}</td>
+        <td>{{payment.amount}}</td>
+      </tr>
+    {% endfor %}
+  </table>
+{% endif %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/voucher_code.html b/vendor/registrasion/registrasion/templates/registrasion/voucher_code.html
new file mode 100644
index 00000000..bd789e03
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/voucher_code.html
@@ -0,0 +1,5 @@
+{% extends "registrasion/voucher_code_.html" %}
+{% comment %}
+  Blocks that you can override:
+
+{% endcomment %}
diff --git a/vendor/registrasion/registrasion/templates/registrasion/voucher_code_.html b/vendor/registrasion/registrasion/templates/registrasion/voucher_code_.html
new file mode 100644
index 00000000..1f7dc211
--- /dev/null
+++ b/vendor/registrasion/registrasion/templates/registrasion/voucher_code_.html
@@ -0,0 +1,28 @@
+{% extends "registrasion/base.html" %}
+
+{% block title %}Enter voucher code{% endblock %}
+{% block heading %}Enter voucher code{% endblock %}
+{% block lede %}
+  If you have a voucher code, you can enter it here to get
+  discounts on products, or unlock products that aren't otherwise
+  available.
+{% endblock %}
+
+{% block content %}
+
+  <form method="post" action="">
+    {% csrf_token %}
+
+    <div class="panel panel-primary">
+      <div class="panel-body">
+        {% include "registrasion/form.html" with form=voucher_form %}
+      </div>
+
+      <div class="panel-footer">
+        <input class="btn btn-primary" type="submit" value="Apply voucher code" />
+        <a href="{% url "dashboard" %}" class="btn btn-default">Back to dashboard</a>
+      </div>
+    </div>
+  </form>
+
+{% endblock %}
diff --git a/vendor/registrasion/templatetags/__init__.py b/vendor/registrasion/registrasion/templatetags/__init__.py
similarity index 100%
rename from vendor/registrasion/templatetags/__init__.py
rename to vendor/registrasion/registrasion/templatetags/__init__.py
diff --git a/vendor/registrasion/templatetags/registrasion_tags.py b/vendor/registrasion/registrasion/templatetags/registrasion_tags.py
similarity index 100%
rename from vendor/registrasion/templatetags/registrasion_tags.py
rename to vendor/registrasion/registrasion/templatetags/registrasion_tags.py
diff --git a/vendor/registrasion/tests/__init__.py b/vendor/registrasion/registrasion/tests/__init__.py
similarity index 100%
rename from vendor/registrasion/tests/__init__.py
rename to vendor/registrasion/registrasion/tests/__init__.py
diff --git a/vendor/registrasion/tests/controller_helpers.py b/vendor/registrasion/registrasion/tests/controller_helpers.py
similarity index 100%
rename from vendor/registrasion/tests/controller_helpers.py
rename to vendor/registrasion/registrasion/tests/controller_helpers.py
diff --git a/vendor/registrasion/tests/patches.py b/vendor/registrasion/registrasion/tests/patches.py
similarity index 100%
rename from vendor/registrasion/tests/patches.py
rename to vendor/registrasion/registrasion/tests/patches.py
diff --git a/vendor/registrasion/tests/test_batch.py b/vendor/registrasion/registrasion/tests/test_batch.py
similarity index 100%
rename from vendor/registrasion/tests/test_batch.py
rename to vendor/registrasion/registrasion/tests/test_batch.py
diff --git a/vendor/registrasion/tests/test_cart.py b/vendor/registrasion/registrasion/tests/test_cart.py
similarity index 100%
rename from vendor/registrasion/tests/test_cart.py
rename to vendor/registrasion/registrasion/tests/test_cart.py
diff --git a/vendor/registrasion/tests/test_ceilings.py b/vendor/registrasion/registrasion/tests/test_ceilings.py
similarity index 100%
rename from vendor/registrasion/tests/test_ceilings.py
rename to vendor/registrasion/registrasion/tests/test_ceilings.py
diff --git a/vendor/registrasion/tests/test_credit_note.py b/vendor/registrasion/registrasion/tests/test_credit_note.py
similarity index 100%
rename from vendor/registrasion/tests/test_credit_note.py
rename to vendor/registrasion/registrasion/tests/test_credit_note.py
diff --git a/vendor/registrasion/tests/test_discount.py b/vendor/registrasion/registrasion/tests/test_discount.py
similarity index 100%
rename from vendor/registrasion/tests/test_discount.py
rename to vendor/registrasion/registrasion/tests/test_discount.py
diff --git a/vendor/registrasion/tests/test_flag.py b/vendor/registrasion/registrasion/tests/test_flag.py
similarity index 100%
rename from vendor/registrasion/tests/test_flag.py
rename to vendor/registrasion/registrasion/tests/test_flag.py
diff --git a/vendor/registrasion/tests/test_group_member.py b/vendor/registrasion/registrasion/tests/test_group_member.py
similarity index 100%
rename from vendor/registrasion/tests/test_group_member.py
rename to vendor/registrasion/registrasion/tests/test_group_member.py
diff --git a/vendor/registrasion/tests/test_helpers.py b/vendor/registrasion/registrasion/tests/test_helpers.py
similarity index 100%
rename from vendor/registrasion/tests/test_helpers.py
rename to vendor/registrasion/registrasion/tests/test_helpers.py
diff --git a/vendor/registrasion/tests/test_invoice.py b/vendor/registrasion/registrasion/tests/test_invoice.py
similarity index 100%
rename from vendor/registrasion/tests/test_invoice.py
rename to vendor/registrasion/registrasion/tests/test_invoice.py
diff --git a/vendor/registrasion/tests/test_refund.py b/vendor/registrasion/registrasion/tests/test_refund.py
similarity index 100%
rename from vendor/registrasion/tests/test_refund.py
rename to vendor/registrasion/registrasion/tests/test_refund.py
diff --git a/vendor/registrasion/tests/test_speaker.py b/vendor/registrasion/registrasion/tests/test_speaker.py
similarity index 100%
rename from vendor/registrasion/tests/test_speaker.py
rename to vendor/registrasion/registrasion/tests/test_speaker.py
diff --git a/vendor/registrasion/tests/test_voucher.py b/vendor/registrasion/registrasion/tests/test_voucher.py
similarity index 100%
rename from vendor/registrasion/tests/test_voucher.py
rename to vendor/registrasion/registrasion/tests/test_voucher.py
diff --git a/vendor/registrasion/urls.py b/vendor/registrasion/registrasion/urls.py
similarity index 100%
rename from vendor/registrasion/urls.py
rename to vendor/registrasion/registrasion/urls.py
diff --git a/vendor/registrasion/util.py b/vendor/registrasion/registrasion/util.py
similarity index 100%
rename from vendor/registrasion/util.py
rename to vendor/registrasion/registrasion/util.py
diff --git a/vendor/registrasion/views.py b/vendor/registrasion/registrasion/views.py
similarity index 100%
rename from vendor/registrasion/views.py
rename to vendor/registrasion/registrasion/views.py
diff --git a/vendor/registrasion/requirements/base.txt b/vendor/registrasion/requirements/base.txt
new file mode 100644
index 00000000..a87391f9
--- /dev/null
+++ b/vendor/registrasion/requirements/base.txt
@@ -0,0 +1,2 @@
+django-nested-admin==2.2.6
+#symposion==1.0b2.dev3
diff --git a/vendor/registrasion/requirements/dependencies.txt b/vendor/registrasion/requirements/dependencies.txt
new file mode 100644
index 00000000..42ef60c8
--- /dev/null
+++ b/vendor/registrasion/requirements/dependencies.txt
@@ -0,0 +1 @@
+https://github.com/pinax/symposion/tarball/ad81810#egg=symposion
diff --git a/vendor/registrasion/requirements/docs.txt b/vendor/registrasion/requirements/docs.txt
new file mode 100644
index 00000000..f070a205
--- /dev/null
+++ b/vendor/registrasion/requirements/docs.txt
@@ -0,0 +1,4 @@
+# requirements needed to build the docs
+
+Sphinx==1.4.1
+sphinx-rtd-theme==0.1.9
diff --git a/vendor/registrasion/requirements/extern.txt b/vendor/registrasion/requirements/extern.txt
new file mode 100644
index 00000000..f9f15786
--- /dev/null
+++ b/vendor/registrasion/requirements/extern.txt
@@ -0,0 +1,4 @@
+# Requirements that currently live in git land, so are necessary to make the
+# project build, but can't live in setup.py
+
+-e git+https://github.com/pinax/symposion.git@ad81810#egg=SymposionMaster-1.0.0b3-dev # Symposion lives on git at the moment
diff --git a/vendor/registrasion/setup.cfg b/vendor/registrasion/setup.cfg
new file mode 100644
index 00000000..9b05640c
--- /dev/null
+++ b/vendor/registrasion/setup.cfg
@@ -0,0 +1,2 @@
+[flake8]
+exclude = registrasion/migrations/*, build/*, docs/*, dist/*
diff --git a/vendor/registrasion/setup.py b/vendor/registrasion/setup.py
new file mode 100644
index 00000000..d28633c6
--- /dev/null
+++ b/vendor/registrasion/setup.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+import os
+from setuptools import setup, find_packages
+
+import registrasion
+
+
+def read_file(filename):
+    """Read a file into a string."""
+    path = os.path.abspath(os.path.dirname(__file__))
+    filepath = os.path.join(path, filename)
+    try:
+        return open(filepath).read()
+    except IOError:
+        return ''
+
+
+setup(
+    name="registrasion",
+    author="Christopher Neugebauer",
+    author_email="_@chrisjrn.com",
+    version=registrasion.__version__,
+    description="A registration app for the Symposion conference management "
+                "system.",
+    url="http://github.com/chrisjrn/registrasion/",
+    packages=find_packages(),
+    include_package_data=True,
+    classifiers=(
+        "Development Status :: 3 - Alpha",
+        "Programming Language :: Python",
+        "Framework :: Django",
+        "Intended Audience :: Developers",
+        "Natural Language :: English",
+        "License :: OSI Approved :: Apache Software License",
+    ),
+    install_requires=read_file("requirements/base.txt").splitlines(),
+    dependency_links=read_file("requirements/dependencies.txt").splitlines(),
+)

From 2580584597b5a16a5e0f096ddf93d0b9c318f0d4 Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Fri, 29 Sep 2017 23:13:37 +1000
Subject: [PATCH 06/40] Prepare to use the newly-revendored registrasion

---
 README.rst                                       |  7 ++++---
 docker/Dockerfile                                |  2 ++
 docker/Dockerfile.makemigrations                 |  3 +++
 .../migrations/0007_merge_20170929_2259.py       | 16 ++++++++++++++++
 vendored_requirements.txt                        |  1 +
 5 files changed, 26 insertions(+), 3 deletions(-)
 create mode 100644 vendor/registrasion/registrasion/migrations/0007_merge_20170929_2259.py
 create mode 100644 vendored_requirements.txt

diff --git a/README.rst b/README.rst
index 187a15cf..8f3fc64c 100644
--- a/README.rst
+++ b/README.rst
@@ -80,9 +80,10 @@ version of pip than is packaged with distros virtualenv.
 Note that this application is python 3 only so you must create your virtualenv
 with a python3 interpreter.
 
-- ``virtualenv -p `which python3` venv``
+- ``python3 -m venv venv``
 - ``source ./venv/bin/activate``
 - ``pip install -c constraints.txt -r requirements.txt``
+- ``pip install -c constraints.txt -r vendored_requirements.txt``
 
 Once your dev instance is up and running
 ----------------------------------------
@@ -138,7 +139,7 @@ admin3:XzynbNH9Sw3pLPXe
 Creating review permissions objects
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Some more voodoo magic that needs to be manually run because that's just how symposion works.
-This creates the permission that needs to be applied to a user/group/team to be able to see the review sections of the site.
+After conference Sections have been created, this command will add
+Permission objects for those sections.
 
 ``./manage.py create_review_permissions``
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 9d24545c..baf81ccd 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -35,6 +35,8 @@ RUN set -ex \
 COPY . /app/symposion_app
 
 WORKDIR /app/symposion_app
+RUN set -x \
+    && pip install -r vendored_requirements.txt -c /reqs/constraints.txt
 RUN set -x \
     && DJANGO_SECRET_KEY=1234 STRIPE_PUBLIC_KEY=1234 STRIPE_SECRET_KEY=1234 \
        DATABASE_URL="sqlite:////dev/null" \
diff --git a/docker/Dockerfile.makemigrations b/docker/Dockerfile.makemigrations
index 5cf1dfeb..d64f416d 100644
--- a/docker/Dockerfile.makemigrations
+++ b/docker/Dockerfile.makemigrations
@@ -18,5 +18,8 @@ RUN set -ex \
     && apt-get install -y git xmlsec1 libmysqlclient18 \
     && apt-get install -y $buildDeps --no-install-recommends
 RUN pip install -c /setup/constraints.txt -r /setup/requirements.txt
+COPY . /source
+WORKDIR /source
+RUN pip install -c /setup/constraints.txt -r /source/vendored_requirements.txt
 ENTRYPOINT ["python","/source/manage.py", "makemigrations"]
 
diff --git a/vendor/registrasion/registrasion/migrations/0007_merge_20170929_2259.py b/vendor/registrasion/registrasion/migrations/0007_merge_20170929_2259.py
new file mode 100644
index 00000000..bcd52f24
--- /dev/null
+++ b/vendor/registrasion/registrasion/migrations/0007_merge_20170929_2259.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2017-09-29 12:59
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('registrasion', '0006_auto_20170526_1624'),
+        ('registrasion', '0006_auto_20170702_2233'),
+    ]
+
+    operations = [
+    ]
diff --git a/vendored_requirements.txt b/vendored_requirements.txt
new file mode 100644
index 00000000..99e409a2
--- /dev/null
+++ b/vendored_requirements.txt
@@ -0,0 +1 @@
+vendor/registrasion

From 162b5edc209c335bf2ca72fa80d9ca83346a345c Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Fri, 29 Sep 2017 23:30:11 +1000
Subject: [PATCH 07/40] git subrepo clone
 git@gitlab.com:tchaypo/registrasion.git vendor/registrasion

subrepo:
  subdir:   "vendor/registrasion"
  merged:   "7cf314a"
upstream:
  origin:   "git@gitlab.com:tchaypo/registrasion.git"
  branch:   "lca2018"
  commit:   "7cf314a"
git-subrepo:
  version:  "0.3.1"
  origin:   "???"
  commit:   "???"
---
 vendor/registrasion/.gitrepo                  |   4 +-
 .../registrasion/controllers/category.py      |   6 +-
 .../registrasion/controllers/credit_note.py   |  10 +-
 .../registrasion/controllers/flag.py          |  42 +-
 .../registrasion/controllers/invoice.py       |   6 +-
 vendor/registrasion/registrasion/forms.py     |  44 ++-
 .../migrations/0007_merge_20170929_2259.py    |  16 -
 .../registrasion/models/__init__.py           |   8 +-
 .../registrasion/models/commerce.py           |   1 -
 .../registrasion/models/inventory.py          |   4 +
 .../registrasion/reporting/reports.py         |   6 +-
 .../registrasion/reporting/views.py           |  76 +++-
 .../templatetags/registrasion_tags.py         |  69 +++-
 .../registrasion/tests/test_cart.py           |   2 +-
 .../registrasion/tests/test_invoice.py        |   6 +-
 .../registrasion/tests/test_speaker.py        |   4 +-
 vendor/registrasion/registrasion/urls.py      |   9 +-
 vendor/registrasion/registrasion/util.py      |   2 +-
 vendor/registrasion/registrasion/views.py     | 373 +++++++++++-------
 19 files changed, 477 insertions(+), 211 deletions(-)
 delete mode 100644 vendor/registrasion/registrasion/migrations/0007_merge_20170929_2259.py

diff --git a/vendor/registrasion/.gitrepo b/vendor/registrasion/.gitrepo
index cc64f22f..c14eed14 100644
--- a/vendor/registrasion/.gitrepo
+++ b/vendor/registrasion/.gitrepo
@@ -6,6 +6,6 @@
 [subrepo]
 	remote = git@gitlab.com:tchaypo/registrasion.git
 	branch = lca2018
-	commit = c1e194aef92e4c06a8855fad22ca819c08736dad
-	parent = dd8a42e9f67cfebc39347cb87bacf79046bfbfec
+	commit = 7cf314adae9f8de1519db63828f55a10aa09f0ee
+	parent = 7c5c6c02f20ddcbc6c54b3065311880fce586192
 	cmdver = 0.3.1
diff --git a/vendor/registrasion/registrasion/controllers/category.py b/vendor/registrasion/registrasion/controllers/category.py
index 2b2b18d1..77875946 100644
--- a/vendor/registrasion/registrasion/controllers/category.py
+++ b/vendor/registrasion/registrasion/controllers/category.py
@@ -9,6 +9,8 @@ from django.db.models import Value
 
 from .batch import BatchController
 
+from operator import attrgetter
+
 
 class AllProducts(object):
     pass
@@ -26,7 +28,7 @@ class CategoryController(object):
         products, otherwise it'll do all. '''
 
         # STOPGAP -- this needs to be elsewhere tbqh
-        from registrasion.controllers.product import ProductController
+        from .product import ProductController
 
         if products is AllProducts:
             products = inventory.Product.objects.all().select_related(
@@ -38,7 +40,7 @@ class CategoryController(object):
             products=products,
         )
 
-        return set(i.category for i in available)
+        return sorted(set(i.category for i in available), key=attrgetter("order"))
 
     @classmethod
     @BatchController.memoise
diff --git a/vendor/registrasion/registrasion/controllers/credit_note.py b/vendor/registrasion/registrasion/controllers/credit_note.py
index 500d0ad7..6c2f7102 100644
--- a/vendor/registrasion/registrasion/controllers/credit_note.py
+++ b/vendor/registrasion/registrasion/controllers/credit_note.py
@@ -4,7 +4,7 @@ from django.db import transaction
 
 from registrasion.models import commerce
 
-from registrasion.controllers.for_id import ForId
+from .for_id import ForId
 
 
 class CreditNoteController(ForId, object):
@@ -40,8 +40,8 @@ class CreditNoteController(ForId, object):
         paid.
         '''
 
-        # Circular Import
-        from registrasion.controllers.invoice import InvoiceController
+        # Local import to fix import cycles. Can we do better?
+        from .invoice import InvoiceController
         inv = InvoiceController(invoice)
         inv.validate_allowed_to_pay()
 
@@ -65,8 +65,8 @@ class CreditNoteController(ForId, object):
         a cancellation fee. Must be 0 <= percentage <= 100.
         '''
 
-        # Circular Import
-        from registrasion.controllers.invoice import InvoiceController
+        # Local import to fix import cycles. Can we do better?
+        from .invoice import InvoiceController
 
         assert(percentage >= 0 and percentage <= 100)
 
diff --git a/vendor/registrasion/registrasion/controllers/flag.py b/vendor/registrasion/registrasion/controllers/flag.py
index b8d1b4e3..b64713e7 100644
--- a/vendor/registrasion/registrasion/controllers/flag.py
+++ b/vendor/registrasion/registrasion/controllers/flag.py
@@ -45,6 +45,28 @@ class FlagController(object):
         else:
             all_conditions = []
 
+        all_conditions = conditions.FlagBase.objects.filter(
+            id__in=set(i.id for i in all_conditions)
+        ).select_subclasses()
+
+        # Prefetch all of the products and categories (Saves a LOT of queries)
+        all_conditions = all_conditions.prefetch_related(
+            "products", "categories", "products__category",
+        )
+
+        # Now pre-select all of the products attached to those categories
+        all_categories = set(
+            cat for condition in all_conditions
+                for cat in condition.categories.all()
+        )
+        all_category_ids = (i.id for i in all_categories)
+        all_category_products = inventory.Product.objects.filter(
+            category__in=all_category_ids
+        ).select_related("category")
+
+        products_by_category_ = itertools.groupby(all_category_products, lambda prod: prod.category)
+        products_by_category = dict((k.id, list(v)) for (k, v) in products_by_category_)
+
         # All disable-if-false conditions on a product need to be met
         do_not_disable = defaultdict(lambda: True)
         # At least one enable-if-true condition on a product must be met
@@ -64,17 +86,19 @@ class FlagController(object):
             # Get all products covered by this condition, and the products
             # from the categories covered by this condition
 
-            ids = [product.id for product in products]
-
-            # TODO: This is re-evaluated a lot.
-            all_products = inventory.Product.objects.filter(id__in=ids)
-            cond = (
-                Q(flagbase_set=condition) |
-                Q(category__in=condition.categories.all())
+            condition_products = condition.products.all()
+            category_products = (
+                product for cat in condition.categories.all() for product in products_by_category[cat.id]
             )
 
-            all_products = all_products.filter(cond)
-            all_products = all_products.select_related("category")
+            all_products = itertools.chain(
+                condition_products, category_products
+            )
+            all_products = set(all_products)
+
+            # Filter out the products from this condition that
+            # are not part of this query.
+            all_products = set(i for i in all_products if i in products)
 
             if quantities:
                 consumed = sum(quantities[i] for i in all_products)
diff --git a/vendor/registrasion/registrasion/controllers/invoice.py b/vendor/registrasion/registrasion/controllers/invoice.py
index 706db7f9..8937843b 100644
--- a/vendor/registrasion/registrasion/controllers/invoice.py
+++ b/vendor/registrasion/registrasion/controllers/invoice.py
@@ -10,9 +10,9 @@ from registrasion.models import commerce
 from registrasion.models import conditions
 from registrasion.models import people
 
-from registrasion.controllers.cart import CartController
-from registrasion.controllers.credit_note import CreditNoteController
-from registrasion.controllers.for_id import ForId
+from .cart import CartController
+from .credit_note import CreditNoteController
+from .for_id import ForId
 
 
 class InvoiceController(ForId, object):
diff --git a/vendor/registrasion/registrasion/forms.py b/vendor/registrasion/registrasion/forms.py
index fec2ec79..3e0beaaa 100644
--- a/vendor/registrasion/registrasion/forms.py
+++ b/vendor/registrasion/registrasion/forms.py
@@ -1,6 +1,6 @@
-from registrasion.controllers.product import ProductController
-from registrasion.models import commerce
-from registrasion.models import inventory
+from .controllers.product import ProductController
+from .models import commerce
+from .models import inventory
 
 from django import forms
 from django.db.models import Q
@@ -31,12 +31,13 @@ class ApplyCreditNoteForm(forms.Form):
                 "user_email": users[invoice["user_id"]].email,
             })
 
-
         key = lambda inv: (0 - (inv["user_id"] == self.user.id), inv["id"])  # noqa
         invoices_annotated.sort(key=key)
 
-        template = ('Invoice %(id)d - user: %(user_email)s (%(user_id)d) '
-                    '-  $%(value)d')
+        template = (
+            'Invoice %(id)d - user: %(user_email)s (%(user_id)d) '
+            '-  $%(value)d'
+        )
         return [
             (invoice["id"], template % invoice)
             for invoice in invoices_annotated
@@ -94,6 +95,7 @@ def ProductsForm(category, products):
         cat.RENDER_TYPE_QUANTITY: _QuantityBoxProductsForm,
         cat.RENDER_TYPE_RADIO: _RadioButtonProductsForm,
         cat.RENDER_TYPE_ITEM_QUANTITY: _ItemQuantityProductsForm,
+        cat.RENDER_TYPE_CHECKBOX: _CheckboxProductsForm,
     }
 
     # Produce a subclass of _ProductsForm which we can alter the base_fields on
@@ -252,6 +254,35 @@ class _RadioButtonProductsForm(_ProductsForm):
         self.add_error(self.FIELD, error)
 
 
+class _CheckboxProductsForm(_ProductsForm):
+    ''' Products entry form that allows users to say yes or no
+    to desired products. Basically, it's a quantity form, but the quantity
+    is either zero or one.'''
+
+    @classmethod
+    def set_fields(cls, category, products):
+        for product in products:
+            field = forms.BooleanField(
+                label='%s -- %s' % (product.name, product.price),
+                required=False,
+            )
+            cls.base_fields[cls.field_name(product)] = field
+
+    @classmethod
+    def initial_data(cls, product_quantities):
+        initial = {}
+        for product, quantity in product_quantities:
+            initial[cls.field_name(product)] = bool(quantity)
+
+        return initial
+
+    def product_quantities(self):
+        for name, value in self.cleaned_data.items():
+            if name.startswith(self.PRODUCT_PREFIX):
+                product_id = int(name[len(self.PRODUCT_PREFIX):])
+                yield (product_id, int(value))
+
+
 class _ItemQuantityProductsForm(_ProductsForm):
     ''' Products entry form that allows users to select a product type, and
      enter a quantity of that product. This version _only_ allows a single
@@ -449,7 +480,6 @@ class InvoicesWithProductAndStatusForm(forms.Form):
         product = [int(i) for i in product]
 
         super(InvoicesWithProductAndStatusForm, self).__init__(*a, **k)
-        print(status)
 
         qs = commerce.Invoice.objects.filter(
             status=status or commerce.Invoice.STATUS_UNPAID,
diff --git a/vendor/registrasion/registrasion/migrations/0007_merge_20170929_2259.py b/vendor/registrasion/registrasion/migrations/0007_merge_20170929_2259.py
deleted file mode 100644
index bcd52f24..00000000
--- a/vendor/registrasion/registrasion/migrations/0007_merge_20170929_2259.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.11.5 on 2017-09-29 12:59
-from __future__ import unicode_literals
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('registrasion', '0006_auto_20170526_1624'),
-        ('registrasion', '0006_auto_20170702_2233'),
-    ]
-
-    operations = [
-    ]
diff --git a/vendor/registrasion/registrasion/models/__init__.py b/vendor/registrasion/registrasion/models/__init__.py
index 47efa127..3d247559 100644
--- a/vendor/registrasion/registrasion/models/__init__.py
+++ b/vendor/registrasion/registrasion/models/__init__.py
@@ -1,4 +1,4 @@
-from registrasion.models.commerce import *  # NOQA
-from registrasion.models.conditions import *  # NOQA
-from registrasion.models.inventory import *  # NOQA
-from registrasion.models.people import *  # NOQA
+from .commerce import *  # NOQA
+from .conditions import *  # NOQA
+from .inventory import *  # NOQA
+from .people import *  # NOQA
diff --git a/vendor/registrasion/registrasion/models/commerce.py b/vendor/registrasion/registrasion/models/commerce.py
index a392c96b..4791ec3d 100644
--- a/vendor/registrasion/registrasion/models/commerce.py
+++ b/vendor/registrasion/registrasion/models/commerce.py
@@ -324,7 +324,6 @@ class CreditNote(PaymentBase):
 
         elif hasattr(self, 'creditnoterefund'):
             reference = self.creditnoterefund.reference
-            print(reference)
             return "Refunded with reference: %s" % reference
 
         raise ValueError("This should never happen.")
diff --git a/vendor/registrasion/registrasion/models/inventory.py b/vendor/registrasion/registrasion/models/inventory.py
index 575566e2..6dfc6cd4 100644
--- a/vendor/registrasion/registrasion/models/inventory.py
+++ b/vendor/registrasion/registrasion/models/inventory.py
@@ -42,6 +42,8 @@ class Category(models.Model):
             have a lot of options, from which the user is not going to select
             all of the options.
 
+            ``RENDER_TYPE_CHECKBOX`` shows a checkbox beside each product.
+
         limit_per_user (Optional[int]): This restricts the number of items
             from this Category that each attendee may claim. This extends
             across multiple Invoices.
@@ -63,11 +65,13 @@ class Category(models.Model):
     RENDER_TYPE_RADIO = 1
     RENDER_TYPE_QUANTITY = 2
     RENDER_TYPE_ITEM_QUANTITY = 3
+    RENDER_TYPE_CHECKBOX = 4
 
     CATEGORY_RENDER_TYPES = [
         (RENDER_TYPE_RADIO, _("Radio button")),
         (RENDER_TYPE_QUANTITY, _("Quantity boxes")),
         (RENDER_TYPE_ITEM_QUANTITY, _("Product selector and quantity box")),
+        (RENDER_TYPE_CHECKBOX, _("Checkbox button")),
     ]
 
     name = models.CharField(
diff --git a/vendor/registrasion/registrasion/reporting/reports.py b/vendor/registrasion/registrasion/reporting/reports.py
index a9684802..d58219f9 100644
--- a/vendor/registrasion/registrasion/reporting/reports.py
+++ b/vendor/registrasion/registrasion/reporting/reports.py
@@ -177,7 +177,6 @@ class Links(Report):
         return []
 
     def rows(self, content_type):
-        print(self._links)
         for url, link_text in self._links:
             yield [
                 self._linked_text(content_type, url, link_text)
@@ -299,9 +298,10 @@ class ReportView(object):
         response = HttpResponse(content_type='text/csv')
 
         writer = csv.writer(response)
-        writer.writerow(report.headings())
+        encode = lambda i: i.encode("utf8") if isinstance(i, unicode) else i  # NOQA
+        writer.writerow(list(encode(i) for i in report.headings()))
         for row in report.rows():
-            writer.writerow(row)
+            writer.writerow(list(encode(i) for i in row))
 
         return response
 
diff --git a/vendor/registrasion/registrasion/reporting/views.py b/vendor/registrasion/registrasion/reporting/views.py
index 239c174a..fbae7906 100644
--- a/vendor/registrasion/registrasion/reporting/views.py
+++ b/vendor/registrasion/registrasion/reporting/views.py
@@ -1,4 +1,4 @@
-from registrasion.reporting import forms
+from . import forms
 
 import collections
 import datetime
@@ -24,11 +24,11 @@ from registrasion import views
 
 from symposion.schedule import models as schedule_models
 
-from registrasion.reporting.reports import get_all_reports
-from registrasion.reporting.reports import Links
-from registrasion.reporting.reports import ListReport
-from registrasion.reporting.reports import QuerysetReport
-from registrasion.reporting.reports import report_view
+from .reports import get_all_reports
+from .reports import Links
+from .reports import ListReport
+from .reports import QuerysetReport
+from .reports import report_view
 
 
 def CURRENCY():
@@ -95,8 +95,6 @@ def items_sold():
         total_quantity=Sum("quantity"),
     )
 
-    print(line_items)
-
     headings = ["Description", "Quantity", "Price", "Total"]
 
     data = []
@@ -312,6 +310,55 @@ def discount_status(request, form):
     return ListReport("Usage by item", headings, data)
 
 
+@report_view("Product Line Items By Date & Customer", form_type=forms.ProductAndCategoryForm)
+def product_line_items(request, form):
+    ''' Shows each product line item from invoices, including their date and
+    purchashing customer. '''
+
+    products = form.cleaned_data["product"]
+    categories = form.cleaned_data["category"]
+
+    invoices = commerce.Invoice.objects.filter(
+        (
+            Q(lineitem__product__in=products) |
+            Q(lineitem__product__category__in=categories)
+        ),
+        status=commerce.Invoice.STATUS_PAID,
+    ).select_related(
+        "cart",
+        "user",
+        "user__attendee",
+        "user__attendee__attendeeprofilebase"
+    ).order_by("issue_time")
+
+    headings = [
+        'Invoice', 'Invoice Date', 'Attendee', 'Qty', 'Product', 'Status'
+    ]
+
+    data = []
+    for invoice in invoices:
+        for item in invoice.cart.productitem_set.all():
+            if item.product in products or item.product.category in categories:
+                output = []
+                output.append(invoice.id)
+                output.append(invoice.issue_time.strftime('%Y-%m-%d %H:%M:%S'))
+                output.append(
+                    invoice.user.attendee.attendeeprofilebase.attendee_name()
+                )
+                output.append(item.quantity)
+                output.append(item.product)
+                cart = invoice.cart
+                if cart.status == commerce.Cart.STATUS_PAID:
+                    output.append('PAID')
+                elif cart.status == commerce.Cart.STATUS_ACTIVE:
+                    output.append('UNPAID')
+                elif cart.status == commerce.Cart.STATUS_RELEASED:
+                    output.append('REFUNDED')
+                data.append(output)
+
+    return ListReport("Line Items", headings, data)
+
+
 @report_view("Paid invoices by date", form_type=forms.ProductAndCategoryForm)
 def paid_invoices_by_date(request, form):
     ''' Shows the number of paid invoices containing given products or
@@ -353,8 +400,8 @@ def paid_invoices_by_date(request, form):
         )
         by_date[date] += 1
 
-    data = [(date, count) for date, count in sorted(by_date.items())]
-    data = [(date.strftime("%Y-%m-%d"), count) for date, count in data]
+    data = [(date_, count) for date_, count in sorted(by_date.items())]
+    data = [(date_.strftime("%Y-%m-%d"), count) for date_, count in data]
 
     return ListReport(
         "Paid Invoices By Date",
@@ -417,8 +464,6 @@ def attendee(request, form, user_id=None):
     if user_id is None:
         return attendee_list(request)
 
-    print(user_id)
-
     attendee = people.Attendee.objects.get(user__id=user_id)
     name = attendee.attendeeprofilebase.attendee_name()
 
@@ -846,9 +891,10 @@ def manifest(request, form):
     headings = ["User ID", "Name", "Paid", "Unpaid", "Refunded"]
 
     def format_items(item_list):
-        strings = []
-        for item in item_list:
-            strings.append('%d x %s' % (item.quantity, str(item.product)))
+        strings = [
+            '%d x %s' % (item.quantity, str(item.product))
+            for item in item_list
+        ]
         return ", \n".join(strings)
 
     output = []
diff --git a/vendor/registrasion/registrasion/templatetags/registrasion_tags.py b/vendor/registrasion/registrasion/templatetags/registrasion_tags.py
index 7108ae95..4c72431b 100644
--- a/vendor/registrasion/registrasion/templatetags/registrasion_tags.py
+++ b/vendor/registrasion/registrasion/templatetags/registrasion_tags.py
@@ -3,8 +3,9 @@ from registrasion.controllers.category import CategoryController
 from registrasion.controllers.item import ItemController
 
 from django import template
+from django.conf import settings
 from django.db.models import Sum
-from urllib.parse import urlencode
+from urllib import urlencode  # TODO: s/urllib/six.moves.urllib/
 
 register = template.Library()
 
@@ -117,3 +118,69 @@ def report_as_csv(context, section):
         querystring = old_query + "&" + querystring
 
     return context.request.path + "?" + querystring
+
+
+@register.assignment_tag(takes_context=True)
+def sold_out_and_unregistered(context):
+    ''' If the current user is unregistered, returns True if there are no
+    products in the TICKET_PRODUCT_CATEGORY that are available to that user.
+
+    If there *are* products available, the return False.
+
+    If the current user *is* registered, then return None (it's not a
+    pertinent question for people who already have a ticket).
+
+    '''
+
+    user = user_for_context(context)
+    if hasattr(user, "attendee") and user.attendee.completed_registration:
+        # This user has completed registration, and so we don't need to answer
+        # whether they have sold out yet.
+
+        # TODO: what if a user has got to the review phase?
+        # currently that user will hit the review page, click "Check out and
+        # pay", and that will fail. Probably good enough for now.
+
+        return None
+
+    ticket_category = settings.TICKET_PRODUCT_CATEGORY
+    categories = available_categories(context)
+
+    return ticket_category not in [cat.id for cat in categories]
+
+
+class IncludeNode(template.Node):
+    ''' https://djangosnippets.org/snippets/2058/ '''
+
+
+    def __init__(self, template_name):
+        # template_name as passed in includes quotmarks?
+        # strip them from the start and end
+        self.template_name = template_name[1:-1]
+
+    def render(self, context):
+        try:
+            # Loading the template and rendering it
+            return template.loader.render_to_string(
+                self.template_name, context=context,
+            )
+        except template.TemplateDoesNotExist:
+            return ""
+
+
+@register.tag
+def include_if_exists(parser, token):
+    """Usage: {% include_if_exists "head.html" %}
+
+    This will fail silently if the template doesn't exist. If it does, it will
+    be rendered with the current context.
+
+    From: https://djangosnippets.org/snippets/2058/
+    """
+    try:
+        tag_name, template_name = token.split_contents()
+    except ValueError:
+        raise (template.TemplateSyntaxError,
+            "%r tag requires a single argument" % token.contents.split()[0])
+
+    return IncludeNode(template_name)
diff --git a/vendor/registrasion/registrasion/tests/test_cart.py b/vendor/registrasion/registrasion/tests/test_cart.py
index d8f5b4c0..825ff675 100644
--- a/vendor/registrasion/registrasion/tests/test_cart.py
+++ b/vendor/registrasion/registrasion/tests/test_cart.py
@@ -85,7 +85,7 @@ class RegistrationCartTestCase(MixInPatches, TestCase):
             prod = inventory.Product.objects.create(
                 name="Product " + str(i + 1),
                 description="This is a test product.",
-                category=cls.categories[int(i / 2)],  # 2 products per category
+                category=cls.categories[i / 2],  # 2 products per category
                 price=Decimal("10.00"),
                 reservation_duration=cls.RESERVATION,
                 limit_per_user=10,
diff --git a/vendor/registrasion/registrasion/tests/test_invoice.py b/vendor/registrasion/registrasion/tests/test_invoice.py
index 0c650dd4..b77fa223 100644
--- a/vendor/registrasion/registrasion/tests/test_invoice.py
+++ b/vendor/registrasion/registrasion/tests/test_invoice.py
@@ -98,11 +98,7 @@ class InvoiceTestCase(TestHelperMixin, RegistrationCartTestCase):
 
     def test_total_payments_balance_due(self):
         invoice = self._invoice_containing_prod_1(2)
-        # range only takes int, and the following logic fails if not a round
-        # number.  So fail if we are not a round number so developer may fix
-        # this test or the product.
-        self.assertTrue((invoice.invoice.value % 1).is_zero())
-        for i in range(0, int(invoice.invoice.value)):
+        for i in xrange(0, invoice.invoice.value):
             self.assertTrue(
                 i + 1, invoice.invoice.total_payments()
             )
diff --git a/vendor/registrasion/registrasion/tests/test_speaker.py b/vendor/registrasion/registrasion/tests/test_speaker.py
index f31266b6..cf64074e 100644
--- a/vendor/registrasion/registrasion/tests/test_speaker.py
+++ b/vendor/registrasion/registrasion/tests/test_speaker.py
@@ -67,7 +67,7 @@ class SpeakerTestCase(RegistrationCartTestCase):
             kind=kind_1,
             title="Proposal 1",
             abstract="Abstract",
-            private_abstract="Private Abstract",
+            description="Description",
             speaker=speaker_1,
         )
         proposal_models.AdditionalSpeaker.objects.create(
@@ -80,7 +80,7 @@ class SpeakerTestCase(RegistrationCartTestCase):
             kind=kind_2,
             title="Proposal 2",
             abstract="Abstract",
-            private_abstract="Private Abstract",
+            description="Description",
             speaker=speaker_1,
         )
         proposal_models.AdditionalSpeaker.objects.create(
diff --git a/vendor/registrasion/registrasion/urls.py b/vendor/registrasion/registrasion/urls.py
index 0789912d..6afdd5f0 100644
--- a/vendor/registrasion/registrasion/urls.py
+++ b/vendor/registrasion/registrasion/urls.py
@@ -1,4 +1,4 @@
-from registrasion.reporting import views as rv
+from .reporting import views as rv
 
 from django.conf.urls import include
 from django.conf.urls import url
@@ -19,6 +19,7 @@ from .views import (
     product_category,
     refund,
     review,
+    voucher_code,
 )
 
 
@@ -43,6 +44,7 @@ public = [
     url(r"^profile$", edit_profile, name="attendee_edit"),
     url(r"^register$", guided_registration, name="guided_registration"),
     url(r"^review$", review, name="review"),
+    url(r"^voucher$", voucher_code, name="voucher_code"),
     url(r"^register/([0-9]+)$", guided_registration,
         name="guided_registration"),
 ]
@@ -55,6 +57,11 @@ reports = [
     url(r"^attendee/([0-9]*)$", rv.attendee, name="attendee"),
     url(r"^credit_notes/?$", rv.credit_notes, name="credit_notes"),
     url(r"^manifest/?$", rv.manifest, name="manifest"),
+    url(
+        r"^product_line_items/?$",
+        rv.product_line_items,
+        name="product_line_items",
+    ),
     url(r"^discount_status/?$", rv.discount_status, name="discount_status"),
     url(r"^invoices/?$", rv.invoices, name="invoices"),
     url(
diff --git a/vendor/registrasion/registrasion/util.py b/vendor/registrasion/registrasion/util.py
index 98079c7e..b5fa0620 100644
--- a/vendor/registrasion/registrasion/util.py
+++ b/vendor/registrasion/registrasion/util.py
@@ -12,7 +12,7 @@ def generate_access_code():
 
     length = 6
     # all upper-case letters + digits 1-9 (no 0 vs O confusion)
-    chars = string.ascii_uppercase + string.digits[1:]
+    chars = string.uppercase + string.digits[1:]
     # 6 chars => 35 ** 6 = 1838265625 (should be enough for anyone)
     return get_random_string(length=length, allowed_chars=chars)
 
diff --git a/vendor/registrasion/registrasion/views.py b/vendor/registrasion/registrasion/views.py
index 239e924b..e16313f2 100644
--- a/vendor/registrasion/registrasion/views.py
+++ b/vendor/registrasion/registrasion/views.py
@@ -1,19 +1,20 @@
 import datetime
 import zipfile
 
-from registrasion import forms
-from registrasion import util
-from registrasion.models import commerce
-from registrasion.models import inventory
-from registrasion.models import people
-from registrasion.controllers.batch import BatchController
-from registrasion.controllers.cart import CartController
-from registrasion.controllers.credit_note import CreditNoteController
-from registrasion.controllers.discount import DiscountController
-from registrasion.controllers.invoice import InvoiceController
-from registrasion.controllers.item import ItemController
-from registrasion.controllers.product import ProductController
-from registrasion.exceptions import CartValidationError
+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
 
@@ -64,12 +65,19 @@ class GuidedRegistrationSection(_GuidedRegistrationSection):
 
 
 @login_required
-def guided_registration(request):
+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) Remaining products
+            4) Mark registration as complete
+
     Returns:
         render: Renders ``registrasion/guided_registration.html``,
             with the following data::
@@ -85,148 +93,225 @@ def guided_registration(request):
 
     '''
 
-    SESSION_KEY = "guided_registration_categories"
-    ASK_FOR_PROFILE = 777  # Magic number. Meh.
+    PAGE_PROFILE = 1
+    PAGE_TICKET = 2
+    PAGE_PRODUCTS = 3
+    PAGE_PRODUCTS_MAX = 4
+    TOTAL_PAGES = 4
 
-    next_step = redirect("guided_registration")
-
-    sections = []
+    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)
 
-    # Step 1: Fill in a badge and collect a voucher code
-    try:
-        profile = attendee.attendeeprofilebase
-    except ObjectDoesNotExist:
-        profile = None
-
-    # Figure out if we need to show the profile form and the voucher form
-    show_profile_and_voucher = False
-    if SESSION_KEY not in request.session:
-        if not profile:
-            show_profile_and_voucher = True
+    # 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:
-        if request.session[SESSION_KEY] == ASK_FOR_PROFILE:
-            show_profile_and_voucher = True
-
-    if show_profile_and_voucher:
-        # Keep asking for the profile until everything passes.
-        request.session[SESSION_KEY] = ASK_FOR_PROFILE
-
-        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,
+        # We have a profile.
+        # Do they have a ticket?
+        products = inventory.Product.objects.filter(
+            productitem__cart=cart.cart
         )
+        products = products.filter(category=ticket_category)
 
-        profile_section = GuidedRegistrationSection(
-            title="Profile and Personal Information",
-            form=profile_form,
-        )
-
-        title = "Attendee information"
-        current_step = 1
-        sections.append(voucher_section)
-        sections.append(profile_section)
-    else:
-        # We're selling products
-
-        starting = attendee.guided_categories_complete.count() == 0
-
-        # Get the next category
-        cats = inventory.Category.objects
-        if SESSION_KEY in request.session:
-            _cats = request.session[SESSION_KEY]
-            cats = cats.filter(id__in=_cats)
+        if products.count() == 0:
+            # If no ticket, they can only see the profile or ticket page.
+            max_page = PAGE_TICKET
+            redirect_page = PAGE_TICKET
         else:
-            cats = cats.exclude(
-                id__in=attendee.guided_categories_complete.all(),
+            # 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)
+
+    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_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
             )
 
-        cats = cats.order_by("order")
+        if not sections:
+            # We've filled in every category
+            attendee.completed_registration = True
+            attendee.save()
+            return redirect("review")
 
-        request.session[SESSION_KEY] = []
-
-        if starting:
-            # Only display the first Category
-            title = "Select ticket type"
-            current_step = 2
-            cats = [cats[0]]
-        else:
-            # Set title appropriately for remaining categories
-            current_step = 3
-            title = "Additional items"
-
-        all_products = inventory.Product.objects.filter(
-            category__in=cats,
-        ).select_related("category")
-
-        with BatchController.batch(request.user):
-            available_products = set(ProductController.available_products(
-                request.user,
-                products=all_products,
-            ))
-
-            if len(available_products) == 0:
-                # We've filled in every category
-                attendee.completed_registration = True
-                attendee.save()
+        if sections and request.method == "POST":
+            for section in sections:
+                if section.form.errors:
+                    break
+            else:
+                # We've successfully processed everything
                 return next_step
 
-            for category in cats:
-                products = [
-                    i for i in available_products
-                    if i.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)
-                    # Add this to the list of things to show if the form
-                    # errors.
-                    request.session[SESSION_KEY].append(category.id)
-
-                    if request.method == "POST" and not products_form.errors:
-                        # This is only saved if we pass each form with no
-                        # errors, and if the form actually has products.
-                        attendee.guided_categories_complete.add(category)
-
-    if sections and request.method == "POST":
-        for section in sections:
-            if section.form.errors:
-                break
-        else:
-            attendee.save()
-            if SESSION_KEY in request.session:
-                del request.session[SESSION_KEY]
-            # We've successfully processed everything
-            return next_step
-
     data = {
-        "current_step": current_step,
+        "current_step": page_number,
         "sections": sections,
         "title": title,
-        "total_steps": 3,
+        "total_steps": TOTAL_PAGES,
     }
     return render(request, "registrasion/guided_registration.html", data)
 
 
+GUIDED_MODE_TICKETS_ONLY = 2
+GUIDED_MODE_ALL_ADDITIONAL = 3
+GUIDED_MODE_EXCLUDE_COMPLETE = 4
+
+
+@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_ALL_ADDITIONAL:
+        cats = cats.exclude(id=settings.TICKET_PRODUCT_CATEGORY)
+    elif mode == GUIDED_MODE_EXCLUDE_COMPLETE:
+        cats = cats.exclude(id=settings.TICKET_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 = set(ProductController.available_products(
+            request.user,
+            products=all_products,
+        ))
+
+        if len(available_products) == 0:
+            return []
+
+        has_errors = False
+
+        for category in cats:
+            products = [
+                i for i in available_products
+                if i.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,
+    )
+
+    return [voucher_section, profile_section]
+
+
 @login_required
 def review(request):
     ''' View for the review page. '''
@@ -399,6 +484,28 @@ def product_category(request, category_id):
     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

From 105e6988e58b23acdc82e9f866b33c4f85c953e2 Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Fri, 29 Sep 2017 23:42:38 +1000
Subject: [PATCH 08/40] Merge migrations with upstream

Find urlencode, wherever it may hide

Further work to using latest upstream registrasion
---
 pinaxcon/settings.py                             |  2 +-
 .../migrations/0007_merge_20170929_2331.py       | 16 ++++++++++++++++
 .../templatetags/registrasion_tags.py            |  5 ++++-
 vendor/registrasion/registrasion/util.py         |  2 +-
 4 files changed, 22 insertions(+), 3 deletions(-)
 create mode 100644 vendor/registrasion/registrasion/migrations/0007_merge_20170929_2331.py

diff --git a/pinaxcon/settings.py b/pinaxcon/settings.py
index 8dec25d8..6f2ff238 100644
--- a/pinaxcon/settings.py
+++ b/pinaxcon/settings.py
@@ -322,7 +322,7 @@ PROPOSAL_FORMS = {
 ATTENDEE_PROFILE_MODEL = "pinaxcon.registrasion.models.AttendeeProfile"
 ATTENDEE_PROFILE_FORM = "pinaxcon.registrasion.forms.ProfileForm"
 INVOICE_CURRENCY = "AUD"
-
+TICKET_PRODUCT_CATEGORY = 1
 ATTENDEE_PROFILE_FORM = "pinaxcon.registrasion.forms.ProfileForm"
 
 # CSRF custom error screen
diff --git a/vendor/registrasion/registrasion/migrations/0007_merge_20170929_2331.py b/vendor/registrasion/registrasion/migrations/0007_merge_20170929_2331.py
new file mode 100644
index 00000000..4630ccaf
--- /dev/null
+++ b/vendor/registrasion/registrasion/migrations/0007_merge_20170929_2331.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2017-09-29 13:31
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('registrasion', '0006_auto_20170702_2233'),
+        ('registrasion', '0006_auto_20170526_1624'),
+    ]
+
+    operations = [
+    ]
diff --git a/vendor/registrasion/registrasion/templatetags/registrasion_tags.py b/vendor/registrasion/registrasion/templatetags/registrasion_tags.py
index 4c72431b..e87be9e8 100644
--- a/vendor/registrasion/registrasion/templatetags/registrasion_tags.py
+++ b/vendor/registrasion/registrasion/templatetags/registrasion_tags.py
@@ -5,7 +5,10 @@ from registrasion.controllers.item import ItemController
 from django import template
 from django.conf import settings
 from django.db.models import Sum
-from urllib import urlencode  # TODO: s/urllib/six.moves.urllib/
+try:
+    from urllib import urlencode
+except ImportError:
+    from urllib.parse import urlencode
 
 register = template.Library()
 
diff --git a/vendor/registrasion/registrasion/util.py b/vendor/registrasion/registrasion/util.py
index b5fa0620..98079c7e 100644
--- a/vendor/registrasion/registrasion/util.py
+++ b/vendor/registrasion/registrasion/util.py
@@ -12,7 +12,7 @@ def generate_access_code():
 
     length = 6
     # all upper-case letters + digits 1-9 (no 0 vs O confusion)
-    chars = string.uppercase + string.digits[1:]
+    chars = string.ascii_uppercase + string.digits[1:]
     # 6 chars => 35 ** 6 = 1838265625 (should be enough for anyone)
     return get_random_string(length=length, allowed_chars=chars)
 

From e546b7d8147055329e1558555a521c35f9f79beb Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Fri, 29 Sep 2017 09:25:01 +1000
Subject: [PATCH 09/40] Fix qhasuery modification so that conditions are
 combined

Borrowed from the pyconau-2017 fork

To explain the impact of this - without this patch, if a user has
their invoice refunded, they are able to buy a new ticket; but
t-shirts, dinner tickets and so on do not become available to them
again because they are listed has already been in a cart for them.

Applying the patch now correctly checks to see if they currently have
a ticket.

From 731eee0a4c42a5013ee312b1ff50548e4d89a2ff Mon Sep 17 00:00:00 2001
From: Richard Jones <r1chardj0n3s@gmail.com>
Date: Sun, 4 Jun 2017 13:22:34 +1000
Subject: [PATCH] Fix query modification so that conditions are combined

Previously it was checking if the user has a product from the category
in a cart, and if there is no cart that is released (refunded).
Not *if the user has a product in a cart that is not released*.

This patch combines them. In the absence of a __ne operation in the
joining syntax, a double equality check is needed.

Signed-off-by: Richard Jones <r1chardj0n3s@gmail.com>
---
 .../registrasion/controllers/conditions.py         | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/vendor/registrasion/registrasion/controllers/conditions.py b/vendor/registrasion/registrasion/controllers/conditions.py
index 87fb9cfb..e86a9d71 100644
--- a/vendor/registrasion/registrasion/controllers/conditions.py
+++ b/vendor/registrasion/registrasion/controllers/conditions.py
@@ -152,16 +152,16 @@ class CategoryConditionController(IsMetByFilter, ConditionController):
         product from a category invoking that item's condition in one of their
         carts. '''
 
+        active = commerce.Cart.STATUS_ACTIVE
+        paid = commerce.Cart.STATUS_PAID
         in_user_carts = Q(
-            enabling_category__product__productitem__cart__user=user
-        )
-        released = commerce.Cart.STATUS_RELEASED
-        in_released_carts = Q(
-            enabling_category__product__productitem__cart__status=released
+            enabling_category__product__productitem__cart__user=user,
+            enabling_category__product__productitem__cart__status=active
+        ) | Q(
+            enabling_category__product__productitem__cart__user=user,
+            enabling_category__product__productitem__cart__status=paid
         )
         queryset = queryset.filter(in_user_carts)
-        queryset = queryset.exclude(in_released_carts)
-
         return queryset
 
 

From 19e4185cd9433c8f743c32dde8aee09455db3982 Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sat, 30 Sep 2017 01:48:53 +1000
Subject: [PATCH 10/40] Revert "NotImplmented refund"

This reverts commit a162559a05f51965d18c73f6d8b9fd4d17d8b254.
---
 vendor/registripe/views.py | 1 -
 1 file changed, 1 deletion(-)

diff --git a/vendor/registripe/views.py b/vendor/registripe/views.py
index daf685d0..50b8181a 100644
--- a/vendor/registripe/views.py
+++ b/vendor/registripe/views.py
@@ -170,7 +170,6 @@ def refund(request, credit_note_id):
 
 
 def process_refund(cn, form):
-    raise NotImplementedError("Does not actually refund a user")
     payment = form.cleaned_data["payment"]
     charge = payment.charge
 

From 3001324d5e9400e39dfe1a0ef087b43857422086 Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sat, 30 Sep 2017 01:49:37 +1000
Subject: [PATCH 11/40] git subrepo pull vendor/registrasion

subrepo:
  subdir:   "vendor/registrasion"
  merged:   "3545a80"
upstream:
  origin:   "git@gitlab.com:tchaypo/registrasion.git"
  branch:   "lca2018"
  commit:   "3545a80"
git-subrepo:
  version:  "0.3.1"
  origin:   "???"
  commit:   "???"
---
 vendor/registrasion/.gitrepo                  |   4 +-
 .../registrasion/contrib/badger.py            | 386 ++++++++++++++++++
 .../registrasion/controllers/cart.py          |   8 +-
 vendor/registrasion/registrasion/forms.py     |  57 ++-
 .../registrasion/generate_badges.py           |   1 +
 .../registrasion/models/people.py             |   2 +-
 .../registrasion/reporting/reports.py         |  10 +
 .../registrasion/reporting/views.py           | 195 ++++++---
 .../templatetags/registrasion_tags.py         |   5 +-
 vendor/registrasion/registrasion/urls.py      |   8 +-
 vendor/registrasion/registrasion/views.py     | 166 +++++++-
 11 files changed, 773 insertions(+), 69 deletions(-)
 create mode 100644 vendor/registrasion/registrasion/contrib/badger.py
 create mode 120000 vendor/registrasion/registrasion/generate_badges.py

diff --git a/vendor/registrasion/.gitrepo b/vendor/registrasion/.gitrepo
index c14eed14..0dfb8b08 100644
--- a/vendor/registrasion/.gitrepo
+++ b/vendor/registrasion/.gitrepo
@@ -6,6 +6,6 @@
 [subrepo]
 	remote = git@gitlab.com:tchaypo/registrasion.git
 	branch = lca2018
-	commit = 7cf314adae9f8de1519db63828f55a10aa09f0ee
-	parent = 7c5c6c02f20ddcbc6c54b3065311880fce586192
+	commit = 3545a809e8e14014963c670709b6d0273c0e354a
+	parent = 19e4185cd9433c8f743c32dde8aee09455db3982
 	cmdver = 0.3.1
diff --git a/vendor/registrasion/registrasion/contrib/badger.py b/vendor/registrasion/registrasion/contrib/badger.py
new file mode 100644
index 00000000..3049ccd2
--- /dev/null
+++ b/vendor/registrasion/registrasion/contrib/badger.py
@@ -0,0 +1,386 @@
+'''
+Generate Conference Badges
+==========================
+
+Nearly all of the code in this was written by Richard Jones for the 2016 conference.
+That code relied on the user supplying the attendee data in a CSV file, which Richard's
+code then processed.
+
+The main (and perhaps only real) difference, here, is that the attendee data are taken
+directly from the database.  No CSV file is required.
+
+This is now a library with functions / classes referenced by the generate_badges
+management command, and by the tickets/badger and tickets/badge API functions.
+'''
+import sys
+import os
+import csv
+from lxml import etree
+import tempfile
+from copy import deepcopy
+import subprocess
+
+import pdb
+
+from django.core.management.base import BaseCommand
+
+from django.contrib.auth.models import User, Group
+from pinaxcon.registrasion.models import AttendeeProfile
+from registrasion.controllers.cart import CartController
+from registrasion.controllers.invoice import InvoiceController
+from registrasion.models import Voucher
+from registrasion.models import Attendee
+from registrasion.models import Product
+from registrasion.models import Invoice
+from symposion.speakers.models import Speaker
+
+# A few unicode encodings ...
+GLYPH_PLUS = '+'
+GLYPH_GLASS = u'\ue001'
+GLYPH_DINNER = u'\ue179'
+GLYPH_SPEAKER = u'\ue122'
+GLYPH_SPRINTS = u'\ue254'
+GLYPH_CROWN = u'\ue211'
+GLYPH_SNOWMAN = u'\u2603'
+GLYPH_STAR = u'\ue007'
+GLYPH_FLASH = u'\ue162'
+GLYPH_EDU = u'\ue233'
+
+# Some company names are too long to fit on the badge, so, we
+# define abbreviations here.
+overrides = {
+ "Optiver Pty. Ltd.": "Optiver",
+ "IRESS Market Tech": "IRESS",
+ "The Bureau of Meteorology": "BoM",
+ "Google Australia": "Google",
+ "Facebook Inc.": "Facebook",
+ "Rhapsody Solutions Pty Ltd": "Rhapsody Solutions",
+ "PivotNine Pty Ltd": "PivotNine",
+ "SEEK Ltd.": "SEEK",
+ "UNSW Australia": "UNSW",
+ "Dev Demand Co": "Dev Demand",
+ "Cascode Labs Pty Ltd": "Cascode Labs",
+ "CyberHound Pty Ltd": "CyberHound",
+ "Self employed Contractor": "",
+ "Data Processors Pty Lmt": "Data Processors",
+ "Bureau of Meterology": "BoM",
+ "Google Australia Pty Ltd": "Google",
+ # "NSW Rural Doctors Network": "",
+ "Sense of Security Pty Ltd": "Sense of Security",
+ "Hewlett Packard Enterprose": "HPE",
+ "Hewlett Packard Enterprise": "HPE",
+ "CISCO SYSTEMS INDIA PVT LTD": "CISCO",
+ "The University of Melbourne": "University of Melbourne",
+ "Peter MacCallum Cancer Centre": "Peter Mac",
+ "Commonwealth Bank of Australia": "CBA",
+ "VLSCI, University of Melbourne": "VLSCI",
+ "Australian Bureau of Meteorology": "BoM",
+ "Bureau of Meteorology": "BoM",
+ "Australian Synchrotron | ANSTO": "Australian Synchrotron",
+ "Bureau of Meteorology, Australia": "BoM",
+ "QUT Digital Media Research Centre": "QUT",
+ "Dyn - Dynamic Network Services Inc": "Dyn",
+ "The Australian National University": "ANU",
+ "Murdoch Childrens Research Institute": "MCRI",
+ "Centenary Institute, University of Sydney": "Centenary Institute",
+ "Synchrotron Light Source Australia Pty Ltd": "Australian Synchrotron",
+ "Australian Communication and Media Authority": "ACMA",
+ "Dept. of Education - Camden Haven High School": "Camden Haven High School",
+ "Australian Government - Bureau of Meteorology": "BoM",
+ "The Walter and Eliza Hall Institute of Medical Research": "WEHI",
+ "Dept. Parliamentary Services, Australian Parliamentary Library": "Dept. Parliamentary Services",
+}
+
+
+def text_size(text, prev=9999):
+    '''
+    Calculate the length of a text string as it relates to font size.
+    '''
+    n = len(text)
+    size = int(min(48, max(28, 28 + 30 * (1 - (n-8) / 11.))))
+    return min(prev, size)
+
+
+def set_text(soup, text_id, text, resize=None):
+    '''
+    Set the text value of an element (via beautiful soup calls).
+    '''
+    elem = soup.find(".//*[@id='%s']/{http://www.w3.org/2000/svg}tspan" % text_id)
+    if elem is None:
+        raise ValueError('could not find tag id=%s' % text_id)
+    elem.text = text
+    if resize:
+        style = elem.get('style')
+        elem.set('style', style.replace('font-size:60px', 'font-size:%dpx' % resize))
+
+
+def set_colour(soup, slice_id, colour):
+    '''
+    Set colour of an element (using beautiful soup calls).
+    '''
+    elem = soup.find(".//*[@id='%s']" % slice_id)
+    if elem is None:
+        raise ValueError('could not find tag id=%s' % slice_id)
+    style = elem.get('style')
+    elem.set('style', style.replace('fill:#316a9a', 'fill:#%s' % colour))
+
+Volunteers = Group.objects.filter(name='Conference volunteers').first().user_set.all()
+Organisers = Group.objects.filter(name='Conference organisers').first().user_set.all()
+
+def is_volunteer(attendee):
+    '''
+    Returns True if attendee is in the Conference volunteers group.
+    False otherwise.
+    '''
+    return attendee.user in Volunteers
+
+def is_organiser(attendee):
+    '''
+    Returns True if attendee is in the Conference volunteers group.
+    False otherwise.
+    '''
+    return attendee.user in Organisers
+
+
+def svg_badge(soup, data, n):
+    '''
+    Do the actual "heavy lifting" to create the badge SVG
+    '''
+
+    # Python2/3 compat ...
+    try:
+        xx = filter(None, [1, 2, None, 3])[2]
+        filter_None = lambda lst: filter(None, lst)
+    except (TypeError,):
+        filter_None = lambda lst: list(filter(None, lst))
+
+    side = 'lr'[n]
+    for tb in 'tb':
+        part = tb + side
+        lines = [data['firstname'], data['lastname']]
+        if data['promote_company']:
+            lines.append(data['company'])
+        lines.extend([data['line1'], data['line2']])
+        lines = filter_None(lines)[:4]
+
+        lines.extend('' for n in range(4-len(lines)))
+        prev = 9999
+        for m, line in enumerate(lines):
+            size = text_size(line, prev)
+            set_text(soup, 'line-%s-%s' % (part, m), line, size)
+            prev = size
+
+        lines = []
+        if data['organiser']:
+            lines.append('Organiser')
+            set_colour(soup, 'colour-' + part, '319a51')
+        elif data['volunteer']:
+            lines.append('Volunteer')
+            set_colour(soup, 'colour-' + part, '319a51')
+        if data['speaker']:
+            lines.append('Speaker')
+
+        special = bool(lines)
+
+        if 'Friday Only' in data['ticket']:
+            # lines.append('Friday Only')
+            set_colour(soup, 'colour-' + part, 'a83f3f')
+
+        if 'Contributor' in data['ticket']:
+            lines.append('Contributor')
+        elif 'Professional' in data['ticket'] and not data['organiser']:
+            lines.append('Professional')
+        elif 'Sponsor' in data['ticket'] and not data['organiser']:
+            lines.append('Sponsor')
+        elif 'Enthusiast' in data['ticket'] and not data['organiser']:
+            lines.append('Enthusiast')
+        elif data['ticket'] == 'Speaker' and not data['speaker']:
+            lines.append('Speaker')
+        elif not special:
+            if data['ticket']:
+                lines.append(data['ticket'])
+            elif data['friday']:
+                lines.append('Friday Only')
+                set_colour(soup, 'colour-' + part, 'a83f3f')
+            else:
+                lines.append('Tutorial Only')
+                set_colour(soup, 'colour-' + part, 'a83f3f')
+
+        if data['friday'] and data['ticket'] and not data['organiser']:
+            lines.append('Fri, Sat and Sun')
+            if not data['volunteer']:
+                set_colour(soup, 'colour-' + part, '71319a')
+
+        if len(lines) > 3:
+            raise ValueError('lines = %s' % (lines,))
+
+        for n in range(3 - len(lines)):
+            lines.insert(0, '')
+        for m, line in enumerate(lines):
+            size = text_size(line)
+            set_text(soup, 'tags-%s-%s' % (part, m), line, size)
+
+        icons = []
+        if data['sprints']:
+            icons.append(GLYPH_SPRINTS)
+        if data['tutorial']:
+            icons.append(GLYPH_EDU)
+
+        set_text(soup, 'icons-' + part, ' '.join(icons))
+        set_text(soup, 'shirt-' + side, '; '.join(data['shirts']))
+        set_text(soup, 'email-' + side, data['email'])
+
+
+def collate(options):
+    # If specific usernames were given on the command line, just use those.
+    # Otherwise, use the entire list of attendees.
+    users = User.objects.filter(invoice__status=Invoice.STATUS_PAID)
+    if options['usernames']:
+        users = users.filter(username__in=options['usernames'])
+
+    # Iterate through the attendee list to generate the badges.
+    for n, user in enumerate(users.distinct()):
+        ap = user.attendee.attendeeprofilebase.attendeeprofile
+        data = dict()
+
+        at_nm = ap.name.split()
+        if at_nm[0].lower() in 'mr dr ms mrs miss'.split():
+            at_nm[0] = at_nm[0] + ' ' + at_nm[1]
+            del at_nm[1]
+        if at_nm:
+            data['firstname'] = at_nm[0]
+            data['lastname'] = ' '.join(at_nm[1:])
+        else:
+            print ("ERROR:", ap.attendee.user, 'has no name')
+            continue
+
+        data['line1'] = ap.free_text_1
+        data['line2'] = ap.free_text_2
+
+        data['email'] = user.email
+        data['over18'] = ap.of_legal_age
+        speaker = Speaker.objects.filter(user=user).first()
+        if speaker is None:
+            data['speaker'] = False
+        else:
+            data['speaker'] = bool(speaker.proposals.filter(result__status='accepted'))
+
+        data['paid'] = data['friday'] = data['sprints'] = data['tutorial'] = False
+        data['shirts'] = []
+        data['ticket'] = ''
+
+        # look over all the invoices, yes
+        for inv in Invoice.objects.filter(user_id=ap.attendee.user.id):
+            if not inv.is_paid:
+                continue
+            cart = inv.cart
+            if cart is None:
+                continue
+            data['paid'] = True
+            if cart.productitem_set.filter(product__category__name__startswith="Specialist Day").exists():
+                data['friday'] = True
+            if cart.productitem_set.filter(product__category__name__startswith="Sprint Ticket").exists():
+                data['sprints'] = True
+            if cart.productitem_set.filter(product__category__name__contains="Tutorial").exists():
+                data['tutorial'] = True
+            t = cart.productitem_set.filter(product__category__name__startswith="Conference Ticket")
+            if t.exists():
+                product = t.first().product.name
+                if 'SOLD OUT' not in product:
+                    data['ticket'] = product
+            elif cart.productitem_set.filter(product__category__name__contains="Specialist Day Only").exists():
+                data['ticket'] = 'Specialist Day Only'
+
+            data['shirts'].extend(ts.product.name for ts in cart.productitem_set.filter(
+                product__category__name__startswith="T-Shirt"))
+
+        if not data['paid']:
+            print ("INFO:", ap.attendee.user, 'not paid!')
+            continue
+
+        if not data['ticket'] and not (data['friday'] or data['tutorial']):
+            print ("ERROR:", ap.attendee.user, 'no conference ticket!')
+            continue
+
+        data['company'] = overrides.get(ap.company, ap.company).strip()
+
+        data['volunteer'] = is_volunteer(ap.attendee)
+        data['organiser'] = is_organiser(ap.attendee)
+
+        if 'Specialist Day Only' in data['ticket']:
+            data['ticket'] = 'Friday Only'
+
+        if 'Conference Organiser' in data['ticket']:
+            data['ticket'] = ''
+
+        if 'Conference Volunteer' in data['ticket']:
+            data['ticket'] = ''
+
+        data['promote_company'] = (
+            data['organiser'] or data['volunteer'] or data['speaker'] or
+            'Sponsor' in data['ticket'] or
+            'Contributor' in data['ticket'] or
+            'Professional' in data['ticket']
+        )
+
+        yield data
+
+
+def generate_stats(options):
+    stats = {
+        'firstname': [],
+        'lastname': [],
+        'company': [],
+    }
+    for badge in collate(options):
+        stats['firstname'].append((len(badge['firstname']), badge['firstname']))
+        stats['lastname'].append((len(badge['lastname']), badge['lastname']))
+        if badge['promote_company']:
+            stats['company'].append((len(badge['company']), badge['company']))
+
+    stats['firstname'].sort()
+    stats['lastname'].sort()
+    stats['company'].sort()
+
+    for l, s in stats['firstname']:
+        print ('%2d %s' % (l, s))
+    for l, s in stats['lastname']:
+        print ('%2d %s' % (l, s))
+    for l, s in stats['company']:
+        print ('%2d %s' % (l, s))
+
+
+def generate_badges(options):
+    names = list()
+
+    orig = etree.parse(options['template'])
+    tree = deepcopy(orig)
+    root = tree.getroot()
+
+    for n, data in enumerate(collate(options)):
+        svg_badge(root, data, n % 2)
+        if n % 2:
+            name = os.path.abspath(
+                os.path.join(options['out_dir'], 'badge-%d.svg' % n))
+            tree.write(name)
+            names.append(name)
+            tree = deepcopy(orig)
+            root = tree.getroot()
+
+    if not n % 2:
+        name = os.path.abspath(
+            os.path.join(options['out_dir'], 'badge-%d.svg' % n))
+        tree.write(name)
+        names.append(name)
+
+    return 0
+
+class InvalidTicketChoiceError(Exception):
+    '''
+    Exception thrown when they chosen ticket isn't valid.  This
+    happens either if the ticket choice is 0 (default: Chose a ticket),
+    or is greater than the index if the last ticket choice in the
+    dropdown list.
+    '''
+    def __init__(self, message="Please choose a VALID ticket."):
+        super(InvalidTicketChoiceError, self).__init__(message,)
diff --git a/vendor/registrasion/registrasion/controllers/cart.py b/vendor/registrasion/registrasion/controllers/cart.py
index bb772ce0..4bd416e9 100644
--- a/vendor/registrasion/registrasion/controllers/cart.py
+++ b/vendor/registrasion/registrasion/controllers/cart.py
@@ -9,7 +9,7 @@ import datetime
 import functools
 import itertools
 
-from django.core.exceptions import ObjectDoesNotExist
+from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
 from django.core.exceptions import ValidationError
 from django.db import transaction
 from django.db.models import Max
@@ -64,6 +64,12 @@ class CartController(object):
                 time_last_updated=timezone.now(),
                 reservation_duration=datetime.timedelta(),
             )
+        except MultipleObjectsReturned:
+            # Get the one that looks "newest".
+            existing = commerce.Cart.objects.filter(
+                user=user,
+                status=commerce.Cart.STATUS_ACTIVE,
+            ).order_by('-time_last_updated').first()
         return cls(existing)
 
     def _fail_if_cart_is_not_active(self):
diff --git a/vendor/registrasion/registrasion/forms.py b/vendor/registrasion/registrasion/forms.py
index 3e0beaaa..879d6d5c 100644
--- a/vendor/registrasion/registrasion/forms.py
+++ b/vendor/registrasion/registrasion/forms.py
@@ -263,7 +263,7 @@ class _CheckboxProductsForm(_ProductsForm):
     def set_fields(cls, category, products):
         for product in products:
             field = forms.BooleanField(
-                label='%s -- %s' % (product.name, product.price),
+                label='%s -- $%s' % (product.name, product.price),
                 required=False,
             )
             cls.base_fields[cls.field_name(product)] = field
@@ -521,3 +521,58 @@ class InvoiceEmailForm(InvoicesWithProductAndStatusForm):
         choices=ACTION_CHOICES,
         initial=ACTION_PREVIEW,
     )
+
+
+from registrasion.contrib.badger import InvalidTicketChoiceError
+
+def ticket_selection():
+   return list(enumerate(['!!! NOT A VALID TICKET !!!'] + \
+                         [p.name for p in inventory.Product.objects.\
+                            filter(category__name__contains="Ticket").\
+                            exclude(name__contains="Organiser").order_by('id')]))
+
+
+class TicketSelectionField(forms.ChoiceField):
+
+    def validate(self, value):
+        super(TicketSelectionField, self).validate(value)
+
+        result = int(self.to_python(value))
+        if result <= 0 or result > len(list(self.choices)):
+            raise InvalidTicketChoiceError()
+
+
+
+class BadgeForm(forms.Form):
+    '''
+    A form for creating one-off badges at rego desk.
+    '''
+    required_css_class = 'label-required'
+
+    name = forms.CharField(label="Name", max_length=60, required=True)
+    email = forms.EmailField(label="Email", max_length=60, required=False)
+    company = forms.CharField(label="Company", max_length=60, required=False)
+    free_text_1 = forms.CharField(label="Free Text", max_length=60, required=False)
+    free_text_2 = forms.CharField(label="Free Text", max_length=60, required=False)
+
+    ticket = TicketSelectionField(label="Select a Ticket", choices=ticket_selection)
+
+    paid = forms.BooleanField(label="Paid", required=False)
+    over18 = forms.BooleanField(label="Over 18", required=False)
+    speaker = forms.BooleanField(label="Speaker", required=False)
+    tutorial = forms.BooleanField(label="Tutorial Ticket", required=False)
+    friday = forms.BooleanField(label="Specialist Day", required=False)
+    sprints = forms.BooleanField(label="Sprints", required=False)
+
+
+    def is_valid(self):
+        valid = super(BadgeForm, self).is_valid()
+
+        if not valid:
+            return valid
+
+        if self.data['ticket'] == '0':  # Invalid ticket type!
+            self.add_error('ticket', 'Please select a VALID ticket type.')
+            return False
+
+        return True
diff --git a/vendor/registrasion/registrasion/generate_badges.py b/vendor/registrasion/registrasion/generate_badges.py
new file mode 120000
index 00000000..cc490dd1
--- /dev/null
+++ b/vendor/registrasion/registrasion/generate_badges.py
@@ -0,0 +1 @@
+../../../website/pinaxcon/registrasion/management/commands/generate_badges.py
\ No newline at end of file
diff --git a/vendor/registrasion/registrasion/models/people.py b/vendor/registrasion/registrasion/models/people.py
index 64d0ac40..561c3bb4 100644
--- a/vendor/registrasion/registrasion/models/people.py
+++ b/vendor/registrasion/registrasion/models/people.py
@@ -43,7 +43,7 @@ class Attendee(models.Model):
         db_index=True,
     )
     completed_registration = models.BooleanField(default=False)
-    guided_categories_complete = models.ManyToManyField("category")
+    guided_categories_complete = models.ManyToManyField("category", blank=True)
 
 
 class AttendeeProfileBase(models.Model):
diff --git a/vendor/registrasion/registrasion/reporting/reports.py b/vendor/registrasion/registrasion/reporting/reports.py
index d58219f9..f63bacff 100644
--- a/vendor/registrasion/registrasion/reporting/reports.py
+++ b/vendor/registrasion/registrasion/reporting/reports.py
@@ -75,6 +75,8 @@ class _ReportTemplateWrapper(object):
     def rows(self):
         return self.report.rows(self.content_type)
 
+    def count(self):
+        return self.report.count()
 
 class BasicReport(Report):
 
@@ -118,6 +120,9 @@ class ListReport(BasicReport):
                 for i, cell in enumerate(row)
             ]
 
+    def count(self):
+        return len(self._data)
+
 
 class QuerysetReport(BasicReport):
 
@@ -158,6 +163,9 @@ class QuerysetReport(BasicReport):
             ]
 
 
+    def count(self):
+        return self._queryset.count()
+
 class Links(Report):
 
     def __init__(self, title, links):
@@ -182,6 +190,8 @@ class Links(Report):
                 self._linked_text(content_type, url, link_text)
             ]
 
+    def count(self):
+        return len(self._links)
 
 def report_view(title, form_type=None):
     ''' Decorator that converts a report view function into something that
diff --git a/vendor/registrasion/registrasion/reporting/views.py b/vendor/registrasion/registrasion/reporting/views.py
index fbae7906..8c38351a 100644
--- a/vendor/registrasion/registrasion/reporting/views.py
+++ b/vendor/registrasion/registrasion/reporting/views.py
@@ -13,10 +13,12 @@ from django.db.models import F, Q
 from django.db.models import Count, Max, Sum
 from django.db.models import Case, When, Value
 from django.db.models.fields.related import RelatedField
+from django.db.models.fields import CharField
 from django.shortcuts import render
 
 from registrasion.controllers.cart import CartController
 from registrasion.controllers.item import ItemController
+from registrasion.models import conditions
 from registrasion.models import commerce
 from registrasion.models import people
 from registrasion import util
@@ -30,6 +32,8 @@ from .reports import ListReport
 from .reports import QuerysetReport
 from .reports import report_view
 
+import bleach
+
 
 def CURRENCY():
     return models.DecimalField(decimal_places=2)
@@ -240,6 +244,83 @@ def group_by_cart_status(queryset, order, values):
     return values
 
 
+@report_view("Limits")
+def limits(request, form):
+    ''' Shows the summary of sales against stock limits. '''
+
+    line_items = commerce.ProductItem.objects.filter(
+        cart__status=commerce.Invoice.STATUS_PAID,
+    ).values(
+        "product", "product__name",
+    ).annotate(
+        total_quantity=Sum("quantity")
+    )
+
+    quantities = collections.defaultdict(int)
+    for line_item in line_items.all():
+        quantities[line_item['product__name']] += line_item['total_quantity']
+
+    limits = conditions.TimeOrStockLimitFlag.objects.all().order_by("-limit")
+
+    headings = ["Product", "Quantity"]
+
+    reports = []
+    for limit in limits:
+        data = []
+        total = 0
+        for product in limit.products.all():
+            if product.name in quantities:
+                total += quantities[product.name]
+                data.append([product.name, quantities[product.name]])
+        if limit.limit:
+            data.append(['(TOTAL)', '%s/%s' % (total, limit.limit)])
+        else:
+            data.append(['(TOTAL)', total])
+
+        description = limit.description
+        extras = []
+        if limit.start_time:
+            extras.append('Starts: %s' % (limit.start_time))
+
+        if limit.end_time:
+            extras.append('Ends: %s' % (limit.end_time))
+
+        if extras:
+            description += ' (' + ', '.join(extras) + ')'
+
+        reports.append(ListReport(description, headings, data))
+
+    # now get discount items
+    discount_items = conditions.DiscountBase.objects.select_subclasses()
+
+    data = []
+    for discount in discount_items.all():
+        quantity = 0
+        for item in discount.discountitem_set.filter(cart__status=2):
+            quantity += item.quantity
+
+        description = discount.description
+        extras = []
+        if getattr(discount, 'start_time', None):
+            extras.append('Starts: %s' % (discount.start_time))
+
+        if getattr(discount, 'end_time', None):
+            extras.append('Ends: %s' % (discount.end_time))
+
+        if extras:
+            description += ' (' + ', '.join(extras) + ')'
+
+        if getattr(discount, 'limit', None):
+            data.append([description, '%s/%s' % (quantity, discount.limit)])
+        else:
+            data.append([description, quantity])
+
+    headings = ["Discount", "Quantity"]
+    reports.append(ListReport('Discounts', headings, data))
+
+    return reports
+
+
 @report_view("Product status", form_type=forms.ProductAndCategoryForm)
 def product_status(request, form):
     ''' Summarises the inventory status of the given items, grouping by
@@ -329,7 +410,7 @@ def product_line_items(request, form):
         "user",
         "user__attendee",
         "user__attendee__attendeeprofilebase"
-    ).order_by("issue_time")
+    ).order_by("issue_time").distinct()
 
     headings = [
         'Invoice', 'Invoice Date', 'Attendee', 'Qty', 'Product', 'Status'
@@ -386,7 +467,7 @@ def paid_invoices_by_date(request, form):
     )
 
     # Zero-value invoices will have no payments, so they're paid at issue time
-    zero_value_invoices = invoices.filter(value=0)
+    zero_value_invoices = invoices.filter(value=0).distinct()
 
     times = itertools.chain(
         (line["max_time"] for line in invoice_max_time),
@@ -464,18 +545,19 @@ def attendee(request, form, user_id=None):
     if user_id is None:
         return attendee_list(request)
 
-    attendee = people.Attendee.objects.get(user__id=user_id)
-    name = attendee.attendeeprofilebase.attendee_name()
-
     reports = []
 
     profile_data = []
     try:
+        attendee = people.Attendee.objects.get(user__id=user_id)
+        name = attendee.attendeeprofilebase.attendee_name()
+
         profile = people.AttendeeProfileBase.objects.get_subclass(
             attendee=attendee
         )
         fields = profile._meta.get_fields()
     except people.AttendeeProfileBase.DoesNotExist:
+        name = attendee.user.username
         fields = []
 
     exclude = set(["attendeeprofilebase_ptr", "id"])
@@ -489,11 +571,17 @@ def attendee(request, form, user_id=None):
 
         if isinstance(field, models.ManyToManyField):
             value = ", ".join(str(i) for i in value.all())
+        elif isinstance(field, CharField):
+            value = bleach.clean(value)
 
         profile_data.append((field.verbose_name, value))
 
     cart = CartController.for_user(attendee.user)
-    reservation = cart.cart.reservation_duration + cart.cart.time_last_updated
+    try:
+        reservation = cart.cart.reservation_duration + cart.cart.time_last_updated
+    except AttributeError:  # No reservation_duration set -- default to 24h
+        reservation = datetime.datetime.now() + datetime.timedelta(hours=24)
+
     profile_data.append(("Current cart reserved until", reservation))
 
     reports.append(ListReport("Profile", ["", ""], profile_data))
@@ -515,53 +603,65 @@ def attendee(request, form, user_id=None):
     reports.append(Links("Actions for " + name, links))
 
     # Paid and pending  products
-    ic = ItemController(attendee.user)
-    reports.append(ListReport(
-        "Paid Products",
-        ["Product", "Quantity"],
-        [(pq.product, pq.quantity) for pq in ic.items_purchased()],
-    ))
-    reports.append(ListReport(
-        "Unpaid Products",
-        ["Product", "Quantity"],
-        [(pq.product, pq.quantity) for pq in ic.items_pending()],
-    ))
+    try:
+        ic = ItemController(attendee.user)
+        reports.append(ListReport(
+            "Paid Products",
+            ["Product", "Quantity"],
+            [(pq.product, pq.quantity) for pq in ic.items_purchased()],
+        ))
+        reports.append(ListReport(
+            "Unpaid Products",
+            ["Product", "Quantity"],
+            [(pq.product, pq.quantity) for pq in ic.items_pending()],
+        ))
+    except AttributeError:
+        pass
 
     # Invoices
-    invoices = commerce.Invoice.objects.filter(
-        user=attendee.user,
-    )
-    reports.append(QuerysetReport(
-        "Invoices",
-        ["id", "get_status_display", "value"],
-        invoices,
-        headings=["Invoice ID", "Status", "Value"],
-        link_view=views.invoice,
-    ))
+    try:
+        invoices = commerce.Invoice.objects.filter(
+            user=attendee.user,
+        )
+        reports.append(QuerysetReport(
+            "Invoices",
+            ["id", "get_status_display", "value"],
+            invoices,
+            headings=["Invoice ID", "Status", "Value"],
+            link_view=views.invoice,
+        ))
+    except AttrbuteError:
+        pass
 
     # Credit Notes
-    credit_notes = commerce.CreditNote.objects.filter(
-        invoice__user=attendee.user,
-    ).select_related("invoice", "creditnoteapplication", "creditnoterefund")
+    try:
+        credit_notes = commerce.CreditNote.objects.filter(
+            invoice__user=attendee.user,
+        ).select_related("invoice", "creditnoteapplication", "creditnoterefund")
 
-    reports.append(QuerysetReport(
-        "Credit Notes",
-        ["id", "status", "value"],
-        credit_notes,
-        link_view=views.credit_note,
-    ))
+        reports.append(QuerysetReport(
+            "Credit Notes",
+            ["id", "status", "value"],
+            credit_notes,
+            link_view=views.credit_note,
+        ))
+    except AttributeError:
+        pass
 
     # All payments
-    payments = commerce.PaymentBase.objects.filter(
-        invoice__user=attendee.user,
-    ).select_related("invoice")
+    try:
+        payments = commerce.PaymentBase.objects.filter(
+            invoice__user=attendee.user,
+        ).select_related("invoice")
 
-    reports.append(QuerysetReport(
-        "Payments",
-        ["invoice__id", "id", "reference", "amount"],
-        payments,
-        link_view=views.invoice,
-    ))
+        reports.append(QuerysetReport(
+            "Payments",
+            ["invoice__id", "id", "reference", "amount"],
+            payments,
+            link_view=views.invoice,
+        ))
+    except AttributeError:
+        pass
 
     return reports
 
@@ -678,6 +778,7 @@ def attendee_data(request, form, user_id=None):
     category = product + "__category"
     category_name = category + "__name"
 
+
     if by_category:
         grouping_fields = (category, category_name)
         order_by = (category, )
@@ -712,7 +813,7 @@ def attendee_data(request, form, user_id=None):
                     return None
         else:
             def display_field(value):
-                return value
+                return bleach.clean(value)
 
         status_count = lambda status: Case(When(  # noqa
                 attendee__user__cart__status=status,
@@ -759,7 +860,7 @@ def attendee_data(request, form, user_id=None):
         if isinstance(field_type, models.ManyToManyField):
             return [str(i) for i in attr.all()] or ""
         else:
-            return attr
+            return bleach.clean(attr)
 
     headings = ["User ID", "Name", "Email", "Product", "Item Status"]
     headings.extend(field_names)
diff --git a/vendor/registrasion/registrasion/templatetags/registrasion_tags.py b/vendor/registrasion/registrasion/templatetags/registrasion_tags.py
index e87be9e8..ccebe20b 100644
--- a/vendor/registrasion/registrasion/templatetags/registrasion_tags.py
+++ b/vendor/registrasion/registrasion/templatetags/registrasion_tags.py
@@ -10,6 +10,8 @@ try:
 except ImportError:
     from urllib.parse import urlencode
 
+from operator import attrgetter
+
 register = template.Library()
 
 
@@ -46,8 +48,9 @@ def missing_categories(context):
     for product, quantity in items:
         categories_held.add(product.category)
 
-    return categories_available - categories_held
+    missing = categories_available - categories_held
 
+    return sorted(set(i for i in missing), key=attrgetter("order"))
 
 @register.assignment_tag(takes_context=True)
 def available_credit(context):
diff --git a/vendor/registrasion/registrasion/urls.py b/vendor/registrasion/registrasion/urls.py
index 6afdd5f0..72553877 100644
--- a/vendor/registrasion/registrasion/urls.py
+++ b/vendor/registrasion/registrasion/urls.py
@@ -6,7 +6,7 @@ from django.conf.urls import url
 from .views import (
     amend_registration,
     badge,
-    badges,
+    badger,
     checkout,
     credit_note,
     edit_profile,
@@ -26,7 +26,8 @@ from .views import (
 public = [
     url(r"^amend/([0-9]+)$", amend_registration, name="amend_registration"),
     url(r"^badge/([0-9]+)$", badge, name="badge"),
-    url(r"^badges$", badges, name="badges"),
+    url(r"^badger/([A-Za-z0-9]+)$", badger, name="badger"),
+    url(r"^badger/", badger, name="badger"),
     url(r"^category/([0-9]+)$", product_category, name="product_category"),
     url(r"^checkout$", checkout, name="checkout"),
     url(r"^checkout/([0-9]+)$", checkout, name="checkout"),
@@ -42,9 +43,9 @@ public = [
         name="invoice_access"),
     url(r"^invoice_mailout$", invoice_mailout, name="invoice_mailout"),
     url(r"^profile$", edit_profile, name="attendee_edit"),
-    url(r"^register$", guided_registration, name="guided_registration"),
     url(r"^review$", review, name="review"),
     url(r"^voucher$", voucher_code, name="voucher_code"),
+    url(r"^register$", guided_registration, name="guided_registration"),
     url(r"^register/([0-9]+)$", guided_registration,
         name="guided_registration"),
 ]
@@ -56,6 +57,7 @@ reports = [
     url(r"^attendee_data/?$", rv.attendee_data, name="attendee_data"),
     url(r"^attendee/([0-9]*)$", rv.attendee, name="attendee"),
     url(r"^credit_notes/?$", rv.credit_notes, name="credit_notes"),
+    url(r"^limits/?$", rv.limits, name="reconciliation"),
     url(r"^manifest/?$", rv.manifest, name="manifest"),
     url(
         r"^product_line_items/?$",
diff --git a/vendor/registrasion/registrasion/views.py b/vendor/registrasion/registrasion/views.py
index e16313f2..ac78d842 100644
--- a/vendor/registrasion/registrasion/views.py
+++ b/vendor/registrasion/registrasion/views.py
@@ -1,5 +1,6 @@
 import datetime
 import zipfile
+import os
 
 from . import forms
 from . import util
@@ -32,6 +33,16 @@ from django.shortcuts import redirect
 from django.shortcuts import render
 from django.template import Context, Template, loader
 
+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",
@@ -544,11 +555,13 @@ def _handle_products(request, category, products, prefix):
         # 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)
+            carts = commerce.Cart.objects.filter(user=request.user,
+                                                 status=commerce.Cart.STATUS_ACTIVE)
             items = commerce.ProductItem.objects.filter(
                 product__category=category,
-                cart=carts,
+                cart=current_cart.cart,
             )
+
             if len(items) == 0:
                 products_form.add_error(
                     None,
@@ -1076,24 +1089,42 @@ def invoice_mailout(request):
 
     return render(request, "registrasion/invoice_mailout.html", data)
 
+def _get_badge_template_name():
+    return os.path.join(settings.PROJECT_ROOT, 'pinaxcon', 'templates',
+                        settings.BADGER_DEFAULT_SVG)
 
 @user_passes_test(_staff_only)
 def badge(request, user_id):
-    ''' Renders a single user's badge (SVG). '''
+    '''
+    Renders a single user's badge (SVG).
+
+    This does little more than call Richard Jones' collate and svg_badge
+    functions found in generate_badges.
+    '''
 
     user_id = int(user_id)
     user = User.objects.get(pk=user_id)
 
-    rendered = render_badge(user)
-    response = HttpResponse(rendered)
+    # This will fail spectacularly -- will put exception handling in later ...
+    user_data = list(collate({'usernames': [user.username]}))[0]
 
+    orig = etree.parse(_get_badge_template_name())
+    tree = deepcopy(orig)
+    root = tree.getroot()
+
+    svg_badge(root, user_data, 0)
+
+    response = HttpResponse(etree.tostring(root))
     response["Content-Type"] = "image/svg+xml"
     response["Content-Disposition"] = 'inline; filename="badge.svg"'
     return response
 
 
 def badges(request):
-    ''' Either displays a form containing a list of users with badges to
+    '''
+    *** NOT USED FOR PYCONAU 2017 MELBOURNE  (I.e., not supported in badger module.) ***
+
+    Either displays a form containing a list of users with badges to
     render, or returns a .zip file containing their badges. '''
 
     category = request.GET.getlist("category", [])
@@ -1128,12 +1159,121 @@ def badges(request):
     return render(request, "registrasion/badges.html", data)
 
 
-def render_badge(user):
-    ''' Renders a single user's badge. '''
+def collate_from_form(form):
+    '''
+    Does what collate does, but using form data as its input source
+    rather than User record.
+    '''
+        # Build the thing we'll pass to svg_badge() later.
+    data = dict()
 
-    data = {
-        "user": user,
-    }
+    # Get the name bits ...
+    at_nm = form.data['name'].split()
+    if at_nm[0].lower() in 'mr dr ms mrs miss'.split():
+        at_nm[0] = at_nm[0] + ' ' + at_nm[1]
+        del at_nm[1]
+    if at_nm:
+        data['firstname'] = at_nm[0]
+        data['lastname'] = ''.join(at_nm[1:])
+    else:  # Can't happen -- form validator will check for this.
+        pass
 
-    t = loader.get_template('registrasion/badge.svg')
-    return t.render(data)
+    # Free text -- only one line ... come on!
+    data['line1'] = form.data['free_text_1']
+    data['line2'] = form.data['free_text_2']
+
+    # Email ...
+    data['email'] = form.data['email']
+
+    # Don't think we want to allow ad hoc organiser tickets ...
+    data['organiser'] = False
+
+    # Punt on shirts for now ...
+    data['shirts'] = list()
+
+    # Lots booleans ...
+    for key in ['over18', 'paid', 'friday', 'speaker', 'tutorial',
+                'sprints', 'company',]:
+        data[key] = form.data.get(key, False)
+
+    # This will throw InvalidTicketChoiceError if the ticket
+    # choice isn't found in the ticket list or is the
+    # "Plese select a valid tickt" choice.  (I.e., they forgot
+    # to choose a ticket.)
+    data['ticket'] = ticket_selection()[int(form.data['ticket'])][1]
+
+    data['volunteer'] = data['ticket'].find("Volunteer") >= 0
+
+    if 'Specialist Day Only' in data['ticket']:
+        data['ticket'] = 'Friday Only'
+        data['friday'] = True
+
+    if 'Conference Organiser' in data['ticket']:
+        data['ticket'] = ''
+
+    if 'Conference Volunteer' in data['ticket']:
+        data['ticket'] = ''
+
+    data['promote_company'] = (
+        data['organiser'] or data['volunteer'] or data['speaker'] or
+        'Sponsor' in data['ticket'] or
+        'Contributor' in data['ticket'] or
+        'Professional' in data['ticket']
+    )
+
+    return data
+
+
+@user_passes_test(_staff_only)
+def badger(request, username=None):
+    '''
+    Renders a single user's badge from data supplied on
+    a form rather than from Attendee data.
+
+    If *username* is provided in the URL, an attempt
+    will be made to look up this user and fill in the
+    badge details from the User and Attendee records.
+    '''
+
+    if username is not None:
+
+        # We have a username.  Try to populate our badge data
+        # from User/Attendee model.
+        try:
+            data = collate({'usernames': [username,]}).next()
+        except: # No matching User record (probably) ... just put up a blank form
+            return render(request, settings.BADGER_DEFAULT_FORM, {'form': BadgeForm})
+    else:
+        form = BadgeForm(request.POST)
+
+        if len(form.data) == 0:  # Empty or request to put up the form.
+            return render(request, settings.BADGER_DEFAULT_FORM, {'form': BadgeForm})
+
+        try:
+            if form.is_valid():
+               data = collate_from_form(form)
+
+        except InvalidTicketChoiceError:
+            form.add_error('ticket', 'Please select a VALID ticket type!')
+            return render(request, settings.BADGER_DEFAULT_FORM, {'form': form})
+
+        except TypeError as e:
+            form.add_error(e.message)
+            return render(request, settings.BADGER_DEFAULT_FORM, {'form': form})
+
+
+    # We should have valid data if we get this far.
+    # Fill in the template and return the resulting SVG object.
+    orig = etree.parse(_get_badge_template_name())
+    tree = deepcopy(orig)
+    root = tree.getroot()
+
+    # Generate the badge (svg)
+    svg_badge(root, data, 0)
+
+    # Ship it back to the user...
+    response = HttpResponse(etree.tostring(root))
+
+    response["Content-Type"] = "image/svg+xml"
+    response["Content-Disposition"] = 'inline; filename="badge.svg"'
+    return response

From 5789cbd05e06742f4f7d5dfaf02c92cc8be6bfad Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sat, 30 Sep 2017 02:06:10 +1000
Subject: [PATCH 12/40] git subrepo init vendor/registripe

subrepo:
  subdir:   "vendor/registripe"
  merged:   "5ebd581"
upstream:
  origin:   "none"
  branch:   "master"
  commit:   "5ebd581"
git-subrepo:
  version:  "0.3.1"
  origin:   "???"
  commit:   "???"
---
 vendor/registripe/.gitrepo | 11 +++++++++++
 1 file changed, 11 insertions(+)
 create mode 100644 vendor/registripe/.gitrepo

diff --git a/vendor/registripe/.gitrepo b/vendor/registripe/.gitrepo
new file mode 100644
index 00000000..a3d0d85b
--- /dev/null
+++ b/vendor/registripe/.gitrepo
@@ -0,0 +1,11 @@
+; DO NOT EDIT (unless you know what you are doing)
+;
+; This subdirectory is a git "subrepo", and this file is maintained by the
+; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
+;
+[subrepo]
+	remote = none
+	branch = master
+	commit = 5ebd58154006e63d390c5feadafb9a19769c7455
+	parent = 3001324d5e9400e39dfe1a0ef087b43857422086
+	cmdver = 0.3.1

From 6eaddfc1d8dd08dfeaaf7101a827974c977f23f4 Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sat, 30 Sep 2017 02:28:49 +1000
Subject: [PATCH 13/40] Give registripe subrepo a parent

---
 vendor/registripe/.gitrepo | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/vendor/registripe/.gitrepo b/vendor/registripe/.gitrepo
index a3d0d85b..78fa28bf 100644
--- a/vendor/registripe/.gitrepo
+++ b/vendor/registripe/.gitrepo
@@ -4,8 +4,8 @@
 ; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme
 ;
 [subrepo]
-	remote = none
-	branch = master
+	remote = git@gitlab.com:tchaypo/registrasion-stripe.git
+	branch = lca2018
 	commit = 5ebd58154006e63d390c5feadafb9a19769c7455
-	parent = 3001324d5e9400e39dfe1a0ef087b43857422086
+	parent = 5789cbd05e06742f4f7d5dfaf02c92cc8be6bfad
 	cmdver = 0.3.1

From 660690c5cf82ea3aff47f77a562fa973a0699232 Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sat, 30 Sep 2017 02:29:49 +1000
Subject: [PATCH 14/40] git subrepo pull vendor/registripe

Also update vendored_requirements to make sure we pull this in.

subrepo:
  subdir:   "vendor/registripe"
  merged:   "9fc3645"
upstream:
  origin:   "git@gitlab.com:tchaypo/registrasion-stripe.git"
  branch:   "lca2018"
  commit:   "9fc3645"
git-subrepo:
  version:  "0.3.1"
  origin:   "???"
  commit:   "???"
---
 vendor/registripe/.gitignore                  |  89 +++++++
 vendor/registripe/.gitrepo                    |   4 +-
 vendor/registripe/README.md                   |   2 +
 vendor/registripe/forms.py                    | 223 ------------------
 .../registripe/{ => registripe}/__init__.py   |   0
 vendor/registripe/registripe/admin.py         |   3 +
 vendor/registripe/{ => registripe}/apps.py    |   0
 vendor/registripe/registripe/forms.py         | 120 ++++++++++
 .../migrations/0001_initial.py                |   0
 .../migrations/0002_stripecreditnoterefund.py |   0
 .../{ => registripe}/migrations/__init__.py   |   0
 vendor/registripe/{ => registripe}/models.py  |   1 -
 .../stripe/credit_card_payment.html           |  13 +
 .../stripe/credit_card_payment_.html          |  55 +++++
 .../registrasion/stripe/js/form_handler.js    |  68 ++++++
 .../registrasion/stripe/link_to_payment.html  |   6 +
 .../registrasion/stripe/link_to_payment_.html |   7 +
 .../registrasion/stripe/link_to_refunds.html  |   6 +
 .../registrasion/stripe/link_to_refunds_.html |   9 +
 .../templates/registrasion/stripe/refund.html |   6 +
 .../registrasion/stripe/refund_.html          |  24 ++
 vendor/registripe/registripe/tests.py         |   3 +
 vendor/registripe/{ => registripe}/urls.py    |   5 +-
 vendor/registripe/{ => registripe}/views.py   |  23 +-
 vendor/registripe/requirements.txt            |   4 +
 vendor/registripe/setup.py                    |  35 +++
 vendored_requirements.txt                     |   1 +
 27 files changed, 474 insertions(+), 233 deletions(-)
 create mode 100644 vendor/registripe/.gitignore
 create mode 100644 vendor/registripe/README.md
 delete mode 100644 vendor/registripe/forms.py
 rename vendor/registripe/{ => registripe}/__init__.py (100%)
 create mode 100644 vendor/registripe/registripe/admin.py
 rename vendor/registripe/{ => registripe}/apps.py (100%)
 create mode 100644 vendor/registripe/registripe/forms.py
 rename vendor/registripe/{ => registripe}/migrations/0001_initial.py (100%)
 rename vendor/registripe/{ => registripe}/migrations/0002_stripecreditnoterefund.py (100%)
 rename vendor/registripe/{ => registripe}/migrations/__init__.py (100%)
 rename vendor/registripe/{ => registripe}/models.py (99%)
 create mode 100644 vendor/registripe/registripe/templates/registrasion/stripe/credit_card_payment.html
 create mode 100644 vendor/registripe/registripe/templates/registrasion/stripe/credit_card_payment_.html
 create mode 100644 vendor/registripe/registripe/templates/registrasion/stripe/js/form_handler.js
 create mode 100644 vendor/registripe/registripe/templates/registrasion/stripe/link_to_payment.html
 create mode 100644 vendor/registripe/registripe/templates/registrasion/stripe/link_to_payment_.html
 create mode 100644 vendor/registripe/registripe/templates/registrasion/stripe/link_to_refunds.html
 create mode 100644 vendor/registripe/registripe/templates/registrasion/stripe/link_to_refunds_.html
 create mode 100644 vendor/registripe/registripe/templates/registrasion/stripe/refund.html
 create mode 100644 vendor/registripe/registripe/templates/registrasion/stripe/refund_.html
 create mode 100644 vendor/registripe/registripe/tests.py
 rename vendor/registripe/{ => registripe}/urls.py (67%)
 rename vendor/registripe/{ => registripe}/views.py (89%)
 create mode 100644 vendor/registripe/requirements.txt
 create mode 100644 vendor/registripe/setup.py

diff --git a/vendor/registripe/.gitignore b/vendor/registripe/.gitignore
new file mode 100644
index 00000000..72364f99
--- /dev/null
+++ b/vendor/registripe/.gitignore
@@ -0,0 +1,89 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*,cover
+.hypothesis/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# IPython Notebook
+.ipynb_checkpoints
+
+# pyenv
+.python-version
+
+# celery beat schedule file
+celerybeat-schedule
+
+# dotenv
+.env
+
+# virtualenv
+venv/
+ENV/
+
+# Spyder project settings
+.spyderproject
+
+# Rope project settings
+.ropeproject
diff --git a/vendor/registripe/.gitrepo b/vendor/registripe/.gitrepo
index 78fa28bf..1053e9ea 100644
--- a/vendor/registripe/.gitrepo
+++ b/vendor/registripe/.gitrepo
@@ -6,6 +6,6 @@
 [subrepo]
 	remote = git@gitlab.com:tchaypo/registrasion-stripe.git
 	branch = lca2018
-	commit = 5ebd58154006e63d390c5feadafb9a19769c7455
-	parent = 5789cbd05e06742f4f7d5dfaf02c92cc8be6bfad
+	commit = 9fc364586b2246801979da690bdf2387b8f53e42
+	parent = 6eaddfc1d8dd08dfeaaf7101a827974c977f23f4
 	cmdver = 0.3.1
diff --git a/vendor/registripe/README.md b/vendor/registripe/README.md
new file mode 100644
index 00000000..f85b1c25
--- /dev/null
+++ b/vendor/registripe/README.md
@@ -0,0 +1,2 @@
+# registrasion-stripe
+Provides Credit Card processing for Registrasion using the Stripe API.
diff --git a/vendor/registripe/forms.py b/vendor/registripe/forms.py
deleted file mode 100644
index 3816c5de..00000000
--- a/vendor/registripe/forms.py
+++ /dev/null
@@ -1,223 +0,0 @@
-import copy
-from registripe import models
-
-from django import forms
-from django.core.urlresolvers import reverse
-from django.db.models import F, Q
-from django.forms import widgets
-from django.utils import timezone
-
-from django_countries import countries
-from django_countries.fields import LazyTypedChoiceField
-from django_countries.widgets import CountrySelectWidget
-
-
-class NoRenderWidget(forms.widgets.HiddenInput):
-
-    def render(self, name, value, attrs=None):
-        return "<!-- no widget: " + name + " -->"
-
-
-def secure_striped(field):
-    ''' Calls stripe() with secure=True. '''
-    return striped(field, True)
-
-
-def striped(field, secure=False):
-
-    oldwidget = field.widget
-    field.widget = StripeWidgetProxy(oldwidget, secure)
-    return field
-
-
-class StripeWidgetProxy(widgets.Widget):
-
-    def __init__(self, underlying, secure=False):
-        self.underlying = underlying
-        self.secure = secure
-
-    def __deepcopy__(self, memo):
-        copy_underlying = copy.deepcopy(self.underlying, memo)
-        return type(self)(copy_underlying, self.secure)
-
-    def __getattribute__(self, attr):
-        spr = super(StripeWidgetProxy, self).__getattribute__
-        if attr in ("underlying", "render", "secure", "__deepcopy__"):
-            return spr(attr)
-        else:
-            return getattr(self.underlying, attr)
-
-    def render(self, name, value, attrs=None):
-
-        if not attrs:
-            attrs = {}
-
-        attrs["data-stripe"] = name
-
-        if self.secure:
-            name = ""
-
-        return self.underlying.render(name, value, attrs=attrs)
-
-
-class CreditCardForm(forms.Form):
-
-    required_css_class = 'label-required'
-
-    def _media(self):
-        js = (
-            'https://js.stripe.com/v2/',
-            reverse("registripe_pubkey"),
-        )
-
-        return forms.Media(js=js)
-
-    media = property(_media)
-
-    number = secure_striped(forms.CharField(
-        required=False,
-        label="Credit card Number",
-        help_text="Your credit card number, with or without spaces.",
-        max_length=255,
-    ))
-    exp_month = secure_striped(forms.IntegerField(
-        required=False,
-        label="Card expiry month",
-        min_value=1,
-        max_value=12,
-    ))
-    exp_year = secure_striped(forms.IntegerField(
-        required=False,
-        label="Card expiry year",
-        help_text="The expiry year for your card in 4-digit form",
-        min_value=timezone.now().year,
-    ))
-    cvc = secure_striped(forms.CharField(
-        required=False,
-        min_length=3,
-        max_length=4,
-    ))
-
-    stripe_token = forms.CharField(
-        max_length=255,
-        # required=True,
-        widget=NoRenderWidget(),
-    )
-
-    name = striped(forms.CharField(
-        required=True,
-        label="Cardholder name",
-        help_text="The cardholder's name, as it appears on the credit card",
-        max_length=255,
-    ))
-    address_line1 = striped(forms.CharField(
-        required=True,
-        label="Cardholder account address, line 1",
-        max_length=255,
-    ))
-    address_line2 = striped(forms.CharField(
-        required=False,
-        label="Cardholder account address, line 2",
-        max_length=255,
-    ))
-    address_city = striped(forms.CharField(
-        required=True,
-        label="Cardholder account city",
-        max_length=255,
-    ))
-    address_state = striped(forms.CharField(
-        required=True,
-        max_length=255,
-        label="Cardholder account state or province",
-    ))
-    address_zip = striped(forms.CharField(
-        required=True,
-        max_length=255,
-        label="Cardholder account postal code",
-    ))
-    address_country = striped(LazyTypedChoiceField(
-        label="Cardholder account country",
-        choices=countries,
-        widget=CountrySelectWidget,
-    ))
-
-
-class StripeRefundForm(forms.Form):
-
-    required_css_class = 'label-required'
-
-    def __init__(self, *args, **kwargs):
-        '''
-
-        Arguments:
-            user (User): The user whose charges we should filter to.
-            min_value (Decimal): The minimum value of the charges we should
-                show (currently, credit notes can only be cashed out in full.)
-
-        '''
-        user = kwargs.pop('user', None)
-        min_value = kwargs.pop('min_value', None)
-        super(StripeRefundForm, self).__init__(*args, **kwargs)
-
-        payment_field = self.fields['payment']
-        qs = payment_field.queryset
-
-        if user:
-            qs = qs.filter(
-                charge__customer__user=user,
-            )
-
-        if min_value is not None:
-            # amount >= amount_to_refund + amount_refunded
-            # No refunds yet
-            q1 = (
-                Q(charge__amount_refunded__isnull=True) &
-                Q(charge__amount__gte=min_value)
-            )
-            # There are some refunds
-            q2 = (
-                Q(charge__amount_refunded__isnull=False) &
-                Q(charge__amount__gte=(
-                    F("charge__amount_refunded") + min_value))
-            )
-            qs = qs.filter(q1 | q2)
-
-        payment_field.queryset = qs
-
-    payment = forms.ModelChoiceField(
-        required=True,
-        queryset=models.StripePayment.objects.all(),
-    )
-
-
-'''{
-From stripe.js details:
-
-Card details:
-
-The first argument to createToken is a JavaScript object containing credit
-card data entered by the user. It should contain the following required
-members:
-
-number: card number as a string without any separators
-(e.g., "4242424242424242")
-exp_month: two digit number representing the card's expiration month
-(e.g., 12)
-exp_year: two or four digit number representing the card's expiration year
-(e.g., 2017)
-(The expiration date can also be passed as a single string.)
-
-cvc: optional, but we highly recommend you provide it to help prevent fraud.
-This is the card's security code, as a string (e.g., "123").
-The following fields are entirely optional and cannot result in a token
-creation failure:
-
-name: cardholder name
-address_line1: billing address line 1
-address_line2: billing address line 2
-address_city: billing address city
-address_state: billing address state
-address_zip: billing postal code as a string (e.g., "94301")
-address_country: billing address country
-}
-'''
diff --git a/vendor/registripe/__init__.py b/vendor/registripe/registripe/__init__.py
similarity index 100%
rename from vendor/registripe/__init__.py
rename to vendor/registripe/registripe/__init__.py
diff --git a/vendor/registripe/registripe/admin.py b/vendor/registripe/registripe/admin.py
new file mode 100644
index 00000000..8c38f3f3
--- /dev/null
+++ b/vendor/registripe/registripe/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/vendor/registripe/apps.py b/vendor/registripe/registripe/apps.py
similarity index 100%
rename from vendor/registripe/apps.py
rename to vendor/registripe/registripe/apps.py
diff --git a/vendor/registripe/registripe/forms.py b/vendor/registripe/registripe/forms.py
new file mode 100644
index 00000000..6438618a
--- /dev/null
+++ b/vendor/registripe/registripe/forms.py
@@ -0,0 +1,120 @@
+import copy
+from registripe import models
+
+from django import forms
+from django.core.urlresolvers import reverse
+import functools
+
+from django import forms
+from django.core.urlresolvers import reverse
+from django.core.exceptions import ValidationError
+from django.db.models import F, Q
+from django.forms import widgets
+from django.utils import timezone
+
+from django_countries import countries
+from django_countries.fields import LazyTypedChoiceField
+from django_countries.widgets import CountrySelectWidget
+
+from pinax.stripe import models as pinax_stripe_models
+
+
+class StripeCardElement(forms.widgets.TextInput):
+
+    def render(self, name, value, attrs=None):
+        element = '''
+            <div class="registrasion-stripe-element" id='%s' style='"-moz-appearance: textfield; -webkit-appearance: textfield;     appearance: field;"'>Please wait.</div>''' % (name, )
+
+        script = '''
+            <script type='text/javascript'>
+                window.addEventListener('load', function(event){
+                    stripeify('%s');
+                });
+            </script>''' % (name)
+        return element + script
+
+
+class StripeTokenWidget(forms.widgets.HiddenInput):
+
+    def render(self, name, value, attrs=None):
+
+        return '''
+            <div class='registrasion-stripe-token' style='display:none;'
+            data-input-id='%s'
+            ></div>
+        ''' % (name, )
+
+
+class CreditCardForm(forms.Form):
+
+    required_css_class = 'label-required'
+
+    def _media(self):
+        js = (
+            'https://js.stripe.com/v3/',
+            reverse("registripe_form_handler"),
+        )
+
+        return forms.Media(js=js)
+
+    media = property(_media)
+
+    card = forms.CharField(
+        required=False,
+        label="Credit card",
+        max_length=255,
+        widget=StripeCardElement()
+    )
+
+    stripe_token = forms.CharField(
+        max_length=255,
+        #required=True,
+        widget=StripeTokenWidget(),
+    )
+
+
+class StripeRefundForm(forms.Form):
+
+    def __init__(self, *args, **kwargs):
+        '''
+
+        Arguments:
+            user (User): The user whose charges we should filter to.
+            min_value (Decimal): The minimum value of the charges we should
+                show (currently, credit notes can only be cashed out in full.)
+
+        '''
+        user = kwargs.pop('user', None)
+        min_value = kwargs.pop('min_value', None)
+        super(StripeRefundForm, self).__init__(*args, **kwargs)
+
+        payment_field = self.fields['payment']
+        qs = payment_field.queryset
+
+        if user:
+            qs = qs.filter(
+                charge__customer__user=user,
+            )
+
+        if min_value is not None:
+            # amount >= amount_to_refund + amount_refunded
+            # No refunds yet
+            q1 = (
+                Q(charge__amount_refunded__isnull=True) &
+                Q(charge__amount__gte=min_value)
+            )
+            # There are some refunds
+            q2 = (
+                Q(charge__amount_refunded__isnull=False) &
+                Q(charge__amount__gte=(
+                    F("charge__amount_refunded") + min_value)
+                )
+            )
+            qs = qs.filter(q1 | q2)
+
+        payment_field.queryset = qs
+
+    payment = forms.ModelChoiceField(
+        required=True,
+        queryset=models.StripePayment.objects.all(),
+    )
diff --git a/vendor/registripe/migrations/0001_initial.py b/vendor/registripe/registripe/migrations/0001_initial.py
similarity index 100%
rename from vendor/registripe/migrations/0001_initial.py
rename to vendor/registripe/registripe/migrations/0001_initial.py
diff --git a/vendor/registripe/migrations/0002_stripecreditnoterefund.py b/vendor/registripe/registripe/migrations/0002_stripecreditnoterefund.py
similarity index 100%
rename from vendor/registripe/migrations/0002_stripecreditnoterefund.py
rename to vendor/registripe/registripe/migrations/0002_stripecreditnoterefund.py
diff --git a/vendor/registripe/migrations/__init__.py b/vendor/registripe/registripe/migrations/__init__.py
similarity index 100%
rename from vendor/registripe/migrations/__init__.py
rename to vendor/registripe/registripe/migrations/__init__.py
diff --git a/vendor/registripe/models.py b/vendor/registripe/registripe/models.py
similarity index 99%
rename from vendor/registripe/models.py
rename to vendor/registripe/registripe/models.py
index be3aab6b..6adb65a6 100644
--- a/vendor/registripe/models.py
+++ b/vendor/registripe/registripe/models.py
@@ -9,7 +9,6 @@ class StripePayment(commerce.PaymentBase):
 
     charge = models.ForeignKey(Charge)
 
-
 class StripeCreditNoteRefund(commerce.CreditNoteRefund):
 
     charge = models.ForeignKey(Charge)
diff --git a/vendor/registripe/registripe/templates/registrasion/stripe/credit_card_payment.html b/vendor/registripe/registripe/templates/registrasion/stripe/credit_card_payment.html
new file mode 100644
index 00000000..06ff0ec0
--- /dev/null
+++ b/vendor/registripe/registripe/templates/registrasion/stripe/credit_card_payment.html
@@ -0,0 +1,13 @@
+{% extends "registrasion/stripe/credit_card_payment_.html" %}
+
+{% comment %}
+  Blocks that you can override:
+
+  - balance_due
+
+  You MUST include the following in your template:
+  - {{ form.media.js }}
+  - the payment form, where the form's id="payent-form"
+  - there is a div with id="payment-errors"
+
+{% endcomment %}
diff --git a/vendor/registripe/registripe/templates/registrasion/stripe/credit_card_payment_.html b/vendor/registripe/registripe/templates/registrasion/stripe/credit_card_payment_.html
new file mode 100644
index 00000000..10a94acf
--- /dev/null
+++ b/vendor/registripe/registripe/templates/registrasion/stripe/credit_card_payment_.html
@@ -0,0 +1,55 @@
+{% extends "registrasion/base.html" %}
+{% load registrasion_tags %}
+
+{% block title %}Credit card payment for invoice #{{ invoice.id}}{% endblock %}
+{% block heading %}Credit card payment for invoice #{{ invoice.id}}{% endblock %}
+{% block lede %}
+  Credit card payments are processed by <a href="https://stripe.com">Stripe</a>.
+  We do not store any of your credit card data locally, but instead Stripe will securely tokenise your card details. To allow this, you must allow JavaScript from <code>js.stripe.com</code>.
+{% endblock %}
+
+{% block content %}
+
+  {% block balance_due_outer %}
+    <div class="alert alert-info">
+      <p>
+        {% block balance_due %}
+          You have ${{ invoice.balance_due }} remaining to pay on this invoice.
+        {% endblock %}
+      </p>
+    </div>
+  {% endblock %}
+
+  <div class="panel panel-primary">
+    <div class="panel-heading">
+      <h3 class="panel-title">Card details</h3>
+    </div>
+    <form id="payment-form" method="post">
+
+      <div class="panel-body">
+        {% csrf_token %}
+        {% include "registrasion/form.html" %}
+      </div>
+
+      <div class="panel-footer">
+        <input id="pay" class="btn btn-primary" type="submit" value="Pay {{ invoice.balance_due }}" />
+        <a class="btn btn-default" href='{% url "invoice" invoice.id invoice.user.attendee.access_code %}'>Return to invoice</a>
+      </div>
+
+    </form>
+  </div>
+
+  {% block form_scripts %}
+    {% block add_form_control_class_script %}
+      <script>
+        elements = document.getElementsByClassName("registrasion-stripe-element");
+        Array.prototype.forEach.call(elements, function(element) {
+          element.setAttribute("class", "{% block form_control_class %}form-control{% endblock %}");
+        });
+      </script>
+    {% endblock %}
+
+    {{ form.media.js }}
+  {% endblock %}
+
+{% endblock %}
diff --git a/vendor/registripe/registripe/templates/registrasion/stripe/js/form_handler.js b/vendor/registripe/registripe/templates/registrasion/stripe/js/form_handler.js
new file mode 100644
index 00000000..057da37f
--- /dev/null
+++ b/vendor/registripe/registripe/templates/registrasion/stripe/js/form_handler.js
@@ -0,0 +1,68 @@
+var stripe = Stripe('{{ PINAX_STRIPE_PUBLIC_KEY }}');
+var elements = stripe.elements();
+
+function stripeify(elementId) {
+  var element = elements.create(elementId);
+  element.mount('#' + elementId);
+
+  var htmlElement = document.getElementById(elementId);
+  var errors = elementId + "-errors";
+  htmlElement.insertAdjacentHTML("afterend", "<div id='" + errors + "' role='alert' class='help-block'></div>");
+  var displayError = document.getElementById(errors);
+
+  //Handle real-time validation errors from the card Element.
+  element.addEventListener('change', function(event) {
+    toggleErrorMessage(displayError, event.error);
+  });
+
+  // Create a token or display an error when the form is submitted.
+  var paymentForm = document.getElementById('payment-form');
+  paymentForm.addEventListener('submit', function(event) {
+    event.preventDefault();
+
+    stripe.createToken(element).then(function(result) {
+      if (result.error) {
+        // Inform the user if there was an error
+        toggleErrorMessage(displayError, result.error);
+      } else {
+        // Send the token to your server
+        stripeTokenHandler(result.token);
+      }
+    });
+  });
+}
+
+function toggleErrorMessage(errorElement, maybeError) {
+  errorClass = inputErrorClassName();
+  if (maybeError) {
+    errorElement.textContent = maybeError.message;
+    errorElement.parentNode.classList.add(errorClass);
+  } else {
+    errorElement.textContent = '';
+    errorElement.parentNode.classList.remove(errorClass);
+  }
+}
+
+
+function inputErrorClassName() {
+  return {% block form_control_error_class %}"has-error"{% endblock %};
+}
+
+
+function stripeTokenHandler(token) {
+  // Insert the token ID into the form so it gets submitted to the server
+
+  var form = document.getElementById('payment-form');
+  tokenHolder = form.getElementsByClassName('registrasion-stripe-token')[0];
+  inputId = tokenHolder.dataset.inputId;
+
+  var hiddenInput = document.createElement('input');
+  hiddenInput.setAttribute('type', 'hidden');
+  hiddenInput.setAttribute('name', inputId);
+  hiddenInput.setAttribute('value', token.id);
+
+  tokenHolder.appendChild(hiddenInput);
+
+  // Submit the form
+  form.submit();
+}
diff --git a/vendor/registripe/registripe/templates/registrasion/stripe/link_to_payment.html b/vendor/registripe/registripe/templates/registrasion/stripe/link_to_payment.html
new file mode 100644
index 00000000..da7989f3
--- /dev/null
+++ b/vendor/registripe/registripe/templates/registrasion/stripe/link_to_payment.html
@@ -0,0 +1,6 @@
+{% extends "registrasion/stripe/link_to_payment_.html" %}
+
+{% comment %}
+  Blocks that you can override:
+
+{% endcomment %}
diff --git a/vendor/registripe/registripe/templates/registrasion/stripe/link_to_payment_.html b/vendor/registripe/registripe/templates/registrasion/stripe/link_to_payment_.html
new file mode 100644
index 00000000..b7fd0166
--- /dev/null
+++ b/vendor/registripe/registripe/templates/registrasion/stripe/link_to_payment_.html
@@ -0,0 +1,7 @@
+{% comment %}
+  This is used in the default credit_note.html file to display Stripe funcationality if the app is loaded.
+{% endcomment %}
+
+{% block content %}
+  <a class="btn btn-primary" href='{% url "registripe_card" invoice_id access_code %}'>Pay this invoice with Stripe</a>
+{% endblock %}
diff --git a/vendor/registripe/registripe/templates/registrasion/stripe/link_to_refunds.html b/vendor/registripe/registripe/templates/registrasion/stripe/link_to_refunds.html
new file mode 100644
index 00000000..ce10eefd
--- /dev/null
+++ b/vendor/registripe/registripe/templates/registrasion/stripe/link_to_refunds.html
@@ -0,0 +1,6 @@
+{% extends "registrasion/stripe/link_to_refunds_.html" %}
+
+{% comment %}
+  Blocks that you can override:
+
+{% endcomment %}
diff --git a/vendor/registripe/registripe/templates/registrasion/stripe/link_to_refunds_.html b/vendor/registripe/registripe/templates/registrasion/stripe/link_to_refunds_.html
new file mode 100644
index 00000000..ddaec0f6
--- /dev/null
+++ b/vendor/registripe/registripe/templates/registrasion/stripe/link_to_refunds_.html
@@ -0,0 +1,9 @@
+{% comment %}
+  This is used in the default invoice.html file to display Stripe funcationality if the app is loaded.
+{% endcomment %}
+
+{% block content %}
+  <h3>Stripe Refund</h3>
+
+  <p><a href="{% url 'registripe_refund' credit_note_id %}">View Stripe refund options</a></p>
+{% endblock %}
diff --git a/vendor/registripe/registripe/templates/registrasion/stripe/refund.html b/vendor/registripe/registripe/templates/registrasion/stripe/refund.html
new file mode 100644
index 00000000..0fe0c091
--- /dev/null
+++ b/vendor/registripe/registripe/templates/registrasion/stripe/refund.html
@@ -0,0 +1,6 @@
+{% extends "registrasion/stripe/refund_.html" %}
+
+{% comment %}
+  Blocks that you can override:
+
+{% endcomment %}
diff --git a/vendor/registripe/registripe/templates/registrasion/stripe/refund_.html b/vendor/registripe/registripe/templates/registrasion/stripe/refund_.html
new file mode 100644
index 00000000..035d6ca9
--- /dev/null
+++ b/vendor/registripe/registripe/templates/registrasion/stripe/refund_.html
@@ -0,0 +1,24 @@
+{% extends "registrasion/base.html" %}
+{% load registrasion_tags %}
+
+{% block title %}Stripe refunds for credit note #{{ credit_note.id}}{% endblock %}
+{% block heading %}Stripe refunds for credit note #{{ credit_note.id}}{% endblock %}
+{% block lede %}
+  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.
+{% endblock %}
+
+
+{% block content %}
+
+  <p>This credit note is valued at ${{ credit_note.value }}.</p>
+
+  <h3>Available refunds</h3>
+
+  <form method="post">
+    {% csrf_token %}
+    {% include "registrasion/form.html" %}
+    <input id="submit" class="btn btn-primary" type="submit" value="Refund {{ credit_note.value }} to payment source" />
+  </form>
+{% endblock %}
diff --git a/vendor/registripe/registripe/tests.py b/vendor/registripe/registripe/tests.py
new file mode 100644
index 00000000..7ce503c2
--- /dev/null
+++ b/vendor/registripe/registripe/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/vendor/registripe/urls.py b/vendor/registripe/registripe/urls.py
similarity index 67%
rename from vendor/registripe/urls.py
rename to vendor/registripe/registripe/urls.py
index 40e6b144..99cf3897 100644
--- a/vendor/registripe/urls.py
+++ b/vendor/registripe/registripe/urls.py
@@ -7,9 +7,8 @@ from pinax.stripe.views import Webhook
 
 urlpatterns = [
     url(r"^card/([0-9]*)/$", views.card, name="registripe_card"),
-    url(r"^card/([0-9]*)/([0-9A-Za-z]*)/$", views.card,
-        name="registripe_card"),
-    url(r"^pubkey/$", views.pubkey_script, name="registripe_pubkey"),
+    url(r"^card/([0-9]*)/([0-9A-Za-z]*)/$", views.card, name="registripe_card"),
+    url(r"^form_handler.js", views.form_handler, name="registripe_form_handler"),
     url(r"^refund/([0-9]*)/$", views.refund, name="registripe_refund"),
     url(r"^webhook/$", Webhook.as_view(), name="pinax_stripe_webhook"),
 ]
diff --git a/vendor/registripe/views.py b/vendor/registripe/registripe/views.py
similarity index 89%
rename from vendor/registripe/views.py
rename to vendor/registripe/registripe/views.py
index 50b8181a..a3f7fb29 100644
--- a/vendor/registripe/views.py
+++ b/vendor/registripe/registripe/views.py
@@ -1,5 +1,7 @@
 from registripe import forms
 from registripe import models
+import forms
+import models
 
 from django.core.exceptions import ValidationError
 from django.conf import settings
@@ -14,6 +16,8 @@ from registrasion.controllers.credit_note import CreditNoteController
 from registrasion.controllers.invoice import InvoiceController
 
 from pinax.stripe import actions
+from pinax.stripe.actions import refunds as pinax_stripe_actions_refunds
+from registrasion.models import commerce
 
 from stripe.error import StripeError
 
@@ -28,8 +32,19 @@ def _staff_only(user):
     return user.is_staff
 
 
-def pubkey_script(request):
-    ''' Returns a JS snippet that sets the Stripe public key for Stripe.js. '''
+def form_handler(request):
+    ''' Renders a js file to process Stripe payments. '''
+
+    data = {
+        "PINAX_STRIPE_PUBLIC_KEY": settings.PINAX_STRIPE_PUBLIC_KEY,
+    }
+
+    return render(
+        request,
+        "registrasion/stripe/js/form_handler.js",
+        data,
+        content_type="text/javascript",
+    )
 
     script_template = "Stripe.setPublishableKey('%s');"
     script = script_template % settings.PINAX_STRIPE_PUBLIC_KEY
@@ -65,7 +80,7 @@ def card(request, invoice_id, access_code=None):
 
     if request.POST and form.is_valid():
         try:
-            inv.validate_allowed_to_pay()
+            inv.validate_allowed_to_pay()  # Verify that we're allowed to do this.
             process_card(request, form, inv)
             return to_invoice
         except StripeError as e:
@@ -185,7 +200,7 @@ def process_refund(cn, form):
             "the credit note."
         )
 
-    refund = actions.refunds.create(charge, to_refund) # noqa
+    refund = actions.refunds.create(charge, to_refund)
 
     models.StripeCreditNoteRefund.objects.create(
         parent=cn.credit_note,
diff --git a/vendor/registripe/requirements.txt b/vendor/registripe/requirements.txt
new file mode 100644
index 00000000..13955911
--- /dev/null
+++ b/vendor/registripe/requirements.txt
@@ -0,0 +1,4 @@
+django-countries==4.0
+pinax-stripe==3.2.1
+requests>=2.11.1
+stripe==1.38.0
diff --git a/vendor/registripe/setup.py b/vendor/registripe/setup.py
new file mode 100644
index 00000000..c803f16d
--- /dev/null
+++ b/vendor/registripe/setup.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+import os
+from setuptools import setup, find_packages
+
+import registripe
+
+
+def read_file(filename):
+    """Read a file into a string."""
+    path = os.path.abspath(os.path.dirname(__file__))
+    filepath = os.path.join(path, filename)
+    try:
+        return open(filepath).read()
+    except IOError:
+        return ''
+
+setup(
+    name="registrasion-stripe",
+    author="Christopher Neugebauer",
+    author_email="_@chrisjrn.com",
+    version=registripe.__version__,
+    description="Stripe-based payments for the Registrasion conference registration package.",
+    url="http://github.com/chrisjrn/registrasion-stripe/",
+    packages=find_packages(),
+    include_package_data=True,
+    classifiers=(
+        "Development Status :: 3 - Alpha",
+        "Programming Language :: Python",
+        "Framework :: Django",
+        "Intended Audience :: Developers",
+        "Natural Language :: English",
+        "License :: OSI Approved :: Apache Software License",
+    ),
+    install_requires=read_file("requirements.txt").splitlines(),
+)
diff --git a/vendored_requirements.txt b/vendored_requirements.txt
index 99e409a2..8b01002b 100644
--- a/vendored_requirements.txt
+++ b/vendored_requirements.txt
@@ -1 +1,2 @@
 vendor/registrasion
+vendor/registripe

From 953dafa7c354daa5ddd530d19760cd55df24c8f3 Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sat, 30 Sep 2017 02:36:55 +1000
Subject: [PATCH 15/40] Remove generate_badges.py symlink.

---
 vendor/registrasion/registrasion/generate_badges.py | 1 -
 1 file changed, 1 deletion(-)
 delete mode 120000 vendor/registrasion/registrasion/generate_badges.py

diff --git a/vendor/registrasion/registrasion/generate_badges.py b/vendor/registrasion/registrasion/generate_badges.py
deleted file mode 120000
index cc490dd1..00000000
--- a/vendor/registrasion/registrasion/generate_badges.py
+++ /dev/null
@@ -1 +0,0 @@
-../../../website/pinaxcon/registrasion/management/commands/generate_badges.py
\ No newline at end of file

From 01de499a72ccb2797d9a9d9d7fcd3c281f40a199 Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sat, 30 Sep 2017 02:56:02 +1000
Subject: [PATCH 16/40] Need a later version of django-countries to support
 django v1.11

---
 vendor/registripe/requirements.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/vendor/registripe/requirements.txt b/vendor/registripe/requirements.txt
index 13955911..e695d17b 100644
--- a/vendor/registripe/requirements.txt
+++ b/vendor/registripe/requirements.txt
@@ -1,4 +1,4 @@
-django-countries==4.0
+django-countries>=4.0
 pinax-stripe==3.2.1
 requests>=2.11.1
 stripe==1.38.0

From 359640cfa1ac616c70a5fdbf85e413dadb83bd20 Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sat, 30 Sep 2017 03:02:40 +1000
Subject: [PATCH 17/40] Remove extraneous imports

---
 vendor/registripe/registripe/views.py | 2 --
 1 file changed, 2 deletions(-)

diff --git a/vendor/registripe/registripe/views.py b/vendor/registripe/registripe/views.py
index a3f7fb29..4deca61a 100644
--- a/vendor/registripe/registripe/views.py
+++ b/vendor/registripe/registripe/views.py
@@ -1,7 +1,5 @@
 from registripe import forms
 from registripe import models
-import forms
-import models
 
 from django.core.exceptions import ValidationError
 from django.conf import settings

From 50145c03feec0e49bed4b7a42b438eaff4291741 Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sat, 30 Sep 2017 10:51:43 +1000
Subject: [PATCH 18/40] Copy stripe js template into pinaxcon

Shouldnt be neccessary, template loader should be finding the template provided by the installed app.

but it's not, and I want rego to go live today, so here we go.

One day when things are nice this commit can be reverted and I will be crying a little less on the inside.
---
 .../registrasion/stripe/js/form_handler.js    | 68 +++++++++++++++++++
 1 file changed, 68 insertions(+)
 create mode 100644 pinaxcon/templates/registrasion/stripe/js/form_handler.js

diff --git a/pinaxcon/templates/registrasion/stripe/js/form_handler.js b/pinaxcon/templates/registrasion/stripe/js/form_handler.js
new file mode 100644
index 00000000..057da37f
--- /dev/null
+++ b/pinaxcon/templates/registrasion/stripe/js/form_handler.js
@@ -0,0 +1,68 @@
+var stripe = Stripe('{{ PINAX_STRIPE_PUBLIC_KEY }}');
+var elements = stripe.elements();
+
+function stripeify(elementId) {
+  var element = elements.create(elementId);
+  element.mount('#' + elementId);
+
+  var htmlElement = document.getElementById(elementId);
+  var errors = elementId + "-errors";
+  htmlElement.insertAdjacentHTML("afterend", "<div id='" + errors + "' role='alert' class='help-block'></div>");
+  var displayError = document.getElementById(errors);
+
+  //Handle real-time validation errors from the card Element.
+  element.addEventListener('change', function(event) {
+    toggleErrorMessage(displayError, event.error);
+  });
+
+  // Create a token or display an error when the form is submitted.
+  var paymentForm = document.getElementById('payment-form');
+  paymentForm.addEventListener('submit', function(event) {
+    event.preventDefault();
+
+    stripe.createToken(element).then(function(result) {
+      if (result.error) {
+        // Inform the user if there was an error
+        toggleErrorMessage(displayError, result.error);
+      } else {
+        // Send the token to your server
+        stripeTokenHandler(result.token);
+      }
+    });
+  });
+}
+
+function toggleErrorMessage(errorElement, maybeError) {
+  errorClass = inputErrorClassName();
+  if (maybeError) {
+    errorElement.textContent = maybeError.message;
+    errorElement.parentNode.classList.add(errorClass);
+  } else {
+    errorElement.textContent = '';
+    errorElement.parentNode.classList.remove(errorClass);
+  }
+}
+
+
+function inputErrorClassName() {
+  return {% block form_control_error_class %}"has-error"{% endblock %};
+}
+
+
+function stripeTokenHandler(token) {
+  // Insert the token ID into the form so it gets submitted to the server
+
+  var form = document.getElementById('payment-form');
+  tokenHolder = form.getElementsByClassName('registrasion-stripe-token')[0];
+  inputId = tokenHolder.dataset.inputId;
+
+  var hiddenInput = document.createElement('input');
+  hiddenInput.setAttribute('type', 'hidden');
+  hiddenInput.setAttribute('name', inputId);
+  hiddenInput.setAttribute('value', token.id);
+
+  tokenHolder.appendChild(hiddenInput);
+
+  // Submit the form
+  form.submit();
+}

From 4e183317d0cc0cb2dfcb67c5a0facb7d4d593b0a Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sat, 30 Sep 2017 11:01:23 +1000
Subject: [PATCH 19/40] Add requirement for lxml

---
 vendor/registrasion/requirements/base.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/vendor/registrasion/requirements/base.txt b/vendor/registrasion/requirements/base.txt
index a87391f9..c45a3a46 100644
--- a/vendor/registrasion/requirements/base.txt
+++ b/vendor/registrasion/requirements/base.txt
@@ -1,2 +1,3 @@
 django-nested-admin==2.2.6
 #symposion==1.0b2.dev3
+lxml==4.0.0

From 35b75b6f963cf2356820e791122dafbd0c96f89a Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sat, 30 Sep 2017 11:01:39 +1000
Subject: [PATCH 20/40] Badger should fail gracefully if auth_groups hasn't
 been populated

Let's say you've just installed symposion for the first time, and
you're running the intial `./manage.py migrate`

In that circumstance, there isn't an auth_group table. Naturally this
means you get Some Errors when trying to look for a particular group.

This change handles that error and drives on.
---
 vendor/registrasion/registrasion/contrib/badger.py | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/vendor/registrasion/registrasion/contrib/badger.py b/vendor/registrasion/registrasion/contrib/badger.py
index 3049ccd2..b00f01ea 100644
--- a/vendor/registrasion/registrasion/contrib/badger.py
+++ b/vendor/registrasion/registrasion/contrib/badger.py
@@ -25,6 +25,7 @@ import pdb
 from django.core.management.base import BaseCommand
 
 from django.contrib.auth.models import User, Group
+from django.db.utils import OperationalError
 from pinaxcon.registrasion.models import AttendeeProfile
 from registrasion.controllers.cart import CartController
 from registrasion.controllers.invoice import InvoiceController
@@ -124,8 +125,13 @@ def set_colour(soup, slice_id, colour):
     style = elem.get('style')
     elem.set('style', style.replace('fill:#316a9a', 'fill:#%s' % colour))
 
-Volunteers = Group.objects.filter(name='Conference volunteers').first().user_set.all()
-Organisers = Group.objects.filter(name='Conference organisers').first().user_set.all()
+## It's possible that this script will be run before the database has been populated
+try:
+    Volunteers = Group.objects.filter(name='Conference volunteers').first().user_set.all()
+    Organisers = Group.objects.filter(name='Conference organisers').first().user_set.all()
+except (OperationalError, AttributeError):
+    Volunteers = []
+    Organisers = []
 
 def is_volunteer(attendee):
     '''

From 271d9e05cd874cf730d0059a25f15f6dc3cadcc9 Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sat, 30 Sep 2017 13:24:14 +1000
Subject: [PATCH 21/40] Style stripe payment form

It's rough but it at least outlines the box for credit card details
---
 static/src/css/app.css         |   26 +
 static/src/lca2018/css/app.css | 1724 --------------------------------
 2 files changed, 26 insertions(+), 1724 deletions(-)
 delete mode 100644 static/src/lca2018/css/app.css

diff --git a/static/src/css/app.css b/static/src/css/app.css
index 97289099..754519a8 100644
--- a/static/src/css/app.css
+++ b/static/src/css/app.css
@@ -107,3 +107,29 @@ div.system-message p.system-message-title {
     font-size: 12px;
     padding: 5px 0 0 12px;
 }
+
+/**
+ * The CSS shown here will not be introduced in the Quickstart guide, but shows
+ * how you can use CSS to style your Element's container.
+ */
+.StripeElement {
+  background-color: white;
+  padding: 8px 12px;
+  border-radius: 4px;
+  border: 3px solid transparent;
+  box-shadow: 1px 3px 5px 1px #e6ebf1;
+  -webkit-transition: box-shadow 150ms ease;
+  transition: box-shadow 150ms ease;
+}
+
+.StripeElement--focus {
+  box-shadow: 1px 3px 5px 1px #cfd7df;
+}
+
+.StripeElement--invalid {
+  border-color: #fa755a;
+}
+
+.StripeElement--webkit-autofill {
+  background-color: #fefde5 !important;
+}
diff --git a/static/src/lca2018/css/app.css b/static/src/lca2018/css/app.css
deleted file mode 100644
index 679ce47b..00000000
--- a/static/src/lca2018/css/app.css
+++ /dev/null
@@ -1,1724 +0,0 @@
-@charset "UTF-8";
-/**
- * Media Queries - *Try* and make everything fit within these - use @from/to/between
- */
-/**
- * Font definitions
- */
-/**
- * Transitions
- */
-/**
- * Padding
- * Usage: padding: $padding-rythm*2
- */
-/*
-* Colours
-*/
-/**
- * Purpose-based colors
- */
-/**
- * z-index stack
- */
-/**
- * Misc
- */
-/* ---------------------------------------------------------------------------
-
-	Content-first media queries
-	===========================
-
-	Taken from Dominic Whittle with permission by Jonny Scholes
-
-	@TODO make these media query wrappers work as described.
-
-	from( n ) { ... }
-		Styles elements from (and inclusive) of n.
-		Useful for adding complexity as viewport size increases.
-
-	to( n ) { ... }
-		Styles elements up to but not including n.
-		Effectively, max-width n-1
-		Useful for the occasional small screen only style.
-
-	n must be unitless CSS pixels; e.g., 768 or 1024
-	It gets converted to em.
-
-	from-to( x, y ) { ... }
-
-
-	All take an additional $legacy parameter.
-
-
-	// Examples
-
-	@include to( $BreakpointSmall ) {
-		// max-width( 320/16em )
-		.nav {}
-	}
-
-	@include from-to( $BreakpointMedium, 1280, legacy ) {
-		// min-width( 320/16em ), max-width( 1280-1  )
-		.nav {}
-	}
-
-	@include from( 1280 ) {
-		// min-width 1280
-		.nav {}
-	}
---------------------------------------------------------------------------- */
-.panel--content, .l-speaker-page, .l-header, .l-content-page--richtext, .l-404, .styleguide {
-  max-width: 1746px;
-  margin: 0 40px;
-}
-
-@media (min-width: 48em) {
-  .panel--content, .l-speaker-page, .l-header, .l-content-page--richtext, .l-404, .styleguide {
-    margin: 0 70px;
-  }
-}
-
-@media (min-width: 64em) {
-  .panel--content, .l-speaker-page, .l-header, .l-content-page--richtext, .l-404, .styleguide {
-    margin: 0 160px;
-  }
-}
-
-@media (min-width: 80em) {
-  .panel--content, .l-speaker-page, .l-header, .l-content-page--richtext, .l-404, .styleguide {
-    margin: 0 200px;
-  }
-}
-
-@media (min-width: 1946px) {
-  .panel--content, .l-speaker-page, .l-header, .l-content-page--richtext, .l-404, .styleguide {
-    margin: 0 auto;
-  }
-}
-
-.panel--content, .l-speaker-page {
-  display: -ms-flexbox;
-  display: flex;
-  -ms-flex-align: start;
-  align-items: flex-start;
-  -ms-flex-wrap: wrap;
-  flex-wrap: wrap;
-  -ms-flex-direction: column;
-  flex-direction: column;
-}
-
-@media (min-width: 48em) {
-  .panel--content, .l-speaker-page {
-    -ms-flex-direction: row;
-    flex-direction: row;
-    -ms-flex-pack: justify;
-    justify-content: space-between;
-  }
-}
-
-.panel--1-3, .l-speaker-page--portrait, .l-footer--logos {
-  -ms-flex-order: 1;
-  order: 1;
-  width: 100%;
-}
-
-@media (min-width: 48em) {
-  .panel--1-3, .l-speaker-page--portrait, .l-footer--logos {
-    -ms-flex-order: 0;
-    order: 0;
-    width: auto;
-    -ms-flex-preferred-size: calc((100% / 3) - 1.25rem * 2);
-    flex-basis: calc((100% / 3) - 1.25rem * 2);
-  }
-}
-
-.panel--2-3, .l-speaker-page--content, .l-footer--text {
-  -ms-flex-order: 2;
-  order: 2;
-  width: 100%;
-}
-
-@media (min-width: 48em) {
-  .panel--2-3, .l-speaker-page--content, .l-footer--text {
-    -ms-flex-order: 0;
-    order: 0;
-    width: auto;
-    -ms-flex-preferred-size: calc(((100% / 3) * 2) - 1.25rem * 2);
-    flex-basis: calc(((100% / 3) * 2) - 1.25rem * 2);
-  }
-}
-
-/*! normalize.css v2.1.2 | MIT License | git.io/normalize */
-/* ==========================================================================
-   HTML5 display definitions
-   ========================================================================== */
-/**
- * Correct `block` display not defined in IE 8/9.
- */
-article,
-aside,
-details,
-figcaption,
-figure,
-footer,
-header,
-hgroup,
-main,
-nav,
-section,
-summary {
-  display: block;
-}
-
-/**
- * Correct `inline-block` display not defined in IE 8/9.
- */
-audio,
-canvas,
-video {
-  display: inline-block;
-}
-
-/**
- * Prevent modern browsers from displaying `audio` without controls.
- * Remove excess height in iOS 5 devices.
- */
-audio:not([controls]) {
-  display: none;
-  height: 0;
-}
-
-/**
- * Address styling not present in IE 8/9.
- */
-[hidden] {
-  display: none;
-}
-
-/* ==========================================================================
-   Base
-   ========================================================================== */
-/**
- * 1. Set default font family to sans-serif.
- * 2. Prevent iOS text size adjust after orientation change, without disabling
- *    user zoom.
- */
-html {
-  font-family: sans-serif;
-  /* 1 */
-  -ms-text-size-adjust: 100%;
-  /* 2 */
-  -webkit-text-size-adjust: 100%;
-  /* 2 */
-}
-
-/**
- * Remove default margin.
- */
-body {
-  margin: 0;
-}
-
-/* ==========================================================================
-   Links
-   ========================================================================== */
-/**
- * Address `outline` inconsistency between Chrome and other browsers.
- */
-a:focus {
-  outline: none;
-}
-
-/**
- * Improve readability when focused and also mouse hovered in all browsers.
- */
-a:active,
-a:hover {
-  outline: 0;
-}
-
-/* ==========================================================================
-   Typography
-   ========================================================================== */
-/**
- * Address variable `h1` font-size and margin within `section` and `article`
- * contexts in Firefox 4+, Safari 5, and Chrome.
- */
-h1 {
-  font-size: 2em;
-  margin: 0.67em 0;
-}
-
-/**
- * Address styling not present in IE 8/9, Safari 5, and Chrome.
- */
-abbr[title] {
-  border-bottom: 1px dotted;
-}
-
-/**
- * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
- */
-b,
-strong {
-  font-weight: bold;
-}
-
-/**
- * Address styling not present in Safari 5 and Chrome.
- */
-dfn {
-  font-style: italic;
-}
-
-/**
- * Address differences between Firefox and other browsers.
- */
-hr {
-  box-sizing: content-box;
-  height: 0;
-}
-
-/**
- * Address styling not present in IE 8/9.
- */
-mark {
-  background: #ff0;
-  color: #000;
-}
-
-/**
- * Correct font family set oddly in Safari 5 and Chrome.
- */
-code,
-kbd,
-pre,
-samp {
-  font-family: monospace, serif;
-  font-size: 1em;
-}
-
-/**
- * Improve readability of pre-formatted text in all browsers.
- */
-pre {
-  white-space: pre-wrap;
-}
-
-/**
- * Set consistent quote types.
- */
-q {
-  quotes: "\201C" "\201D" "\2018" "\2019";
-}
-
-/**
- * Address inconsistent and variable font size in all browsers.
- */
-small {
-  font-size: 80%;
-}
-
-/**
- * Prevent `sub` and `sup` affecting `line-height` in all browsers.
- */
-sub,
-sup {
-  font-size: 75%;
-  line-height: 0;
-  position: relative;
-  vertical-align: baseline;
-}
-
-sup {
-  top: -0.5em;
-}
-
-sub {
-  bottom: -0.25em;
-}
-
-/* ==========================================================================
-   Embedded content
-   ========================================================================== */
-/**
- * Remove border when inside `a` element in IE 8/9.
- */
-img {
-  border: 0;
-}
-
-/**
- * Correct overflow displayed oddly in IE 9.
- */
-svg:not(:root) {
-  overflow: hidden;
-}
-
-/* ==========================================================================
-   Figures
-   ========================================================================== */
-/**
- * Address margin not present in IE 8/9 and Safari 5.
- */
-figure {
-  margin: 0;
-}
-
-/* ==========================================================================
-   Forms
-   ========================================================================== */
-/**
- * Define consistent border, margin, and padding.
- */
-fieldset {
-  border: 1px solid #c0c0c0;
-  margin: 0 2px;
-  padding: 0.35em 0.625em 0.75em;
-}
-
-/**
- * 1. Correct `color` not being inherited in IE 8/9.
- * 2. Remove padding so people aren't caught out if they zero out fieldsets.
- */
-legend {
-  border: 0;
-  /* 1 */
-  padding: 0;
-  /* 2 */
-}
-
-/**
- * 1. Correct font family not being inherited in all browsers.
- * 2. Correct font size not being inherited in all browsers.
- * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
- */
-button,
-input,
-select,
-textarea {
-  font-family: inherit;
-  /* 1 */
-  font-size: 100%;
-  /* 2 */
-  margin: 0;
-  /* 3 */
-}
-
-/**
- * Address Firefox 4+ setting `line-height` on `input` using `!important` in
- * the UA stylesheet.
- */
-button,
-input {
-  line-height: normal;
-}
-
-/**
- * Address inconsistent `text-transform` inheritance for `button` and `select`.
- * All other form control elements do not inherit `text-transform` values.
- * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+.
- * Correct `select` style inheritance in Firefox 4+ and Opera.
- */
-button,
-select {
-  text-transform: none;
-}
-
-/**
- * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
- *    and `video` controls.
- * 2. Correct inability to style clickable `input` types in iOS.
- * 3. Improve usability and consistency of cursor style between image-type
- *    `input` and others.
- */
-button,
-html input[type="button"],
-input[type="reset"],
-input[type="submit"] {
-  -webkit-appearance: button;
-  /* 2 */
-  cursor: pointer;
-  /* 3 */
-}
-
-/**
- * Re-set default cursor for disabled elements.
- */
-button[disabled],
-html input[disabled] {
-  cursor: default;
-}
-
-/**
- * 1. Address box sizing set to `content-box` in IE 8/9.
- * 2. Remove excess padding in IE 8/9.
- */
-input[type="checkbox"],
-input[type="radio"] {
-  box-sizing: border-box;
-  /* 1 */
-  padding: 0;
-  /* 2 */
-}
-
-/**
- * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
- * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
- *    (include `-moz` to future-proof).
- */
-input[type="search"] {
-  -webkit-appearance: textfield;
-  /* 1 */
-  /* 2 */
-  box-sizing: content-box;
-}
-
-/**
- * Remove inner padding and search cancel button in Safari 5 and Chrome
- * on OS X.
- */
-input[type="search"]::-webkit-search-cancel-button,
-input[type="search"]::-webkit-search-decoration {
-  -webkit-appearance: none;
-}
-
-/**
- * Remove inner padding and border in Firefox 4+.
- */
-button::-moz-focus-inner,
-input::-moz-focus-inner {
-  border: 0;
-  padding: 0;
-}
-
-/**
- * 1. Remove default vertical scrollbar in IE 8/9.
- * 2. Improve readability and alignment in all browsers.
- */
-textarea {
-  overflow: auto;
-  /* 1 */
-  vertical-align: top;
-  /* 2 */
-}
-
-/* ==========================================================================
-   Tables
-   ========================================================================== */
-/**
- * Remove most spacing between table cells.
- */
-table {
-  border-collapse: collapse;
-  border-spacing: 0;
-  margin-top: 1em;
-  margin-bottom: 1em;
-}
-
-/* ==========================================================================
-   Custom Defaults
-   ========================================================================== */
-*,
-*:before,
-*:after {
-  box-sizing: border-box;
-}
-
-.form-field {
-  border: 1px solid #3ab1c9;
-}
-
-.form-field input[type='text'], .form-field input[type='tel'], .form-field input[type='number'], .form-field input[type='password'], .form-field input[type='email'], textarea {
-  color: #0c486c;
-  padding: 10px;
-  width: 100%;
-  border: 0;
-}
-
-.form-field input[type='text']:focus::-webkit-input-placeholder, .form-field input[type='tel']:focus::-webkit-input-placeholder, .form-field input[type='number']:focus::-webkit-input-placeholder, .form-field input[type='password']:focus::-webkit-input-placeholder, .form-field input[type='email']:focus::-webkit-input-placeholder {
-  opacity: 0.2;
-}
-
-.form-field input[type='text']:focus::-moz-placeholder, .form-field input[type='tel']:focus::-moz-placeholder, .form-field input[type='number']:focus::-moz-placeholder, .form-field input[type='password']:focus::-moz-placeholder, .form-field input[type='email']:focus::-moz-placeholder {
-  opacity: 0.2;
-}
-
-.form-field input[type='text']:focus:-ms-input-placeholder, .form-field input[type='tel']:focus:-ms-input-placeholder, .form-field input[type='number']:focus:-ms-input-placeholder, .form-field input[type='password']:focus:-ms-input-placeholder, .form-field input[type='email']:focus:-ms-input-placeholder {
-  opacity: 0.2;
-}
-
-.form-field input[type='text']:focus::placeholder, .form-field input[type='tel']:focus::placeholder, .form-field input[type='number']:focus::placeholder, .form-field input[type='password']:focus::placeholder, .form-field input[type='email']:focus::placeholder {
-  opacity: 0.2;
-}
-
-.form-field input[type='text']::-webkit-input-placeholder, .form-field input[type='tel']::-webkit-input-placeholder, .form-field input[type='number']::-webkit-input-placeholder, .form-field input[type='password']::-webkit-input-placeholder, .form-field input[type='email']::-webkit-input-placeholder {
-  opacity: 1;
-  color: #0c486c;
-}
-
-.form-field input[type='text']::-moz-placeholder, .form-field input[type='tel']::-moz-placeholder, .form-field input[type='number']::-moz-placeholder, .form-field input[type='password']::-moz-placeholder, .form-field input[type='email']::-moz-placeholder {
-  opacity: 1;
-  color: #0c486c;
-}
-
-.form-field input[type='text']:-ms-input-placeholder, .form-field input[type='tel']:-ms-input-placeholder, .form-field input[type='number']:-ms-input-placeholder, .form-field input[type='password']:-ms-input-placeholder, .form-field input[type='email']:-ms-input-placeholder {
-  opacity: 1;
-  color: #0c486c;
-}
-
-.form-field input[type='text']::placeholder, .form-field input[type='tel']::placeholder, .form-field input[type='number']::placeholder, .form-field input[type='password']::placeholder, .form-field input[type='email']::placeholder {
-  opacity: 1;
-  color: #0c486c;
-}
-
-.form-field + .form-field {
-  margin-top: 20px;
-  margin-top: 1.25rem;
-}
-
-body {
-  font-size: 16px;
-  font-family: "Roboto", sans-serif;
-  line-height: 1.4;
-  color: #0c486c;
-  font-size: 4vw;
-}
-
-@media (min-width: 25em) {
-  body {
-    font-size: 2.2vw;
-  }
-}
-
-@media (min-width: 64em) {
-  body {
-    font-size: 2vw;
-  }
-}
-
-@media (min-width: 80em) {
-  body {
-    font-size: 28px;
-  }
-}
-
-.lede {
-  margin: 30px 0;
-  margin: 1.875rem 0;
-  font-size: 4.2vw;
-}
-
-@media (min-width: 25em) {
-  .lede {
-    font-size: 3vw;
-  }
-}
-
-@media (min-width: 64em) {
-  .lede {
-    font-size: 2vw;
-    margin: 3.375rem 0;
-  }
-}
-
-@media (min-width: 80em) {
-  .lede {
-    font-size: 28px;
-  }
-}
-
-h1,
-h2,
-h3,
-h4,
-h5,
-h6 {
-  font-family: "Titillium Web", sans-serif;
-  font-weight: 900;
-  text-transform: uppercase;
-  margin: 0;
-  line-height: 0.9;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-}
-
-a {
-  color: inherit;
-  text-decoration: none;
-  border-bottom: 1px solid #3ab1c9;
-  transition: color 300ms;
-}
-
-a:hover, a:focus, a:active {
-  color: #3ab1c9;
-}
-
-ul,
-ol {
-  padding: 0;
-  /* margin: 0; */
-}
-
-ul > li,
-ol > li {
-  list-style-position: outside;
-  margin-left: 1em;
-}
-
-blockquote {
-  margin-left: 0;
-  padding-left: 20px;
-  padding-left: 1.25rem;
-  font-style: italic;
-  border-left: 1px solid #3ab1c9;
-}
-
-table th {
-  font-family: "Titillium Web", sans-serif;
-  font-weight: 900;
-  text-transform: uppercase;
-}
-
-.track-name {
-  margin-top: 0;
-  margin-bottom: 0;
-  font-weight: bold;
-  text-transform: none;
-}
-
-table th, table td {
-  padding: 10px;
-  padding: 0.625rem;
-  text-align: left;
-}
-
-table th:not(:first-of-type), table td:not(:first-of-type) {
-  border-left: 1px solid #0c486c;
-}
-
-table tr:not(:last-of-type) {
-  border-bottom: 1px solid #0c486c;
-}
-
-table.alt th {
-  font-family: "Titillium Web", sans-serif;
-  font-weight: 900;
-  text-transform: uppercase;
-}
-
-table.alt th, table.alt td {
-  padding: 10px;
-  padding: 0.625rem;
-  text-align: left;
-}
-
-table.alt th:not(:first-of-type), table.alt td:not(:first-of-type) {
-  border-left: 1px solid #3ab1c9;
-}
-
-table.alt tr:not(:last-of-type) {
-  border-bottom: 1px solid #3ab1c9;
-}
-
-/*
-* Hide only visually, but have it available for screen readers: h5bp.com/v
-*/
-.visuallyhidden {
-  border: 0;
-  clip: rect(0 0 0 0);
-  height: 1px;
-  margin: -1px;
-  overflow: hidden;
-  padding: 0 !important;
-  position: absolute !important;
-  width: 1px;
-}
-
-.visuallyhidden.focusable:active, .visuallyhidden.focusable:focus {
-  clip: auto;
-  height: auto;
-  margin: 0;
-  overflow: visible;
-  position: static;
-  width: auto;
-}
-
-.btn {
-  display: inline-block;
-  background: transparent;
-  color: #0c486c;
-  text-decoration: none;
-  border: 1px solid #3ab1c9;
-  padding: 10px 20px;
-  padding: 0.625rem 1.25rem;
-  border-radius: 0.625rem;
-  transition: all 300ms ease-in-out;
-  line-height: normal;
-}
-
-.btn:hover, .btn:focus, .btn:active {
-  color: #3ab1c9;
-  border-color: #3ab1c9;
-  cursor: pointer;
-}
-
-.btn__white {
-  color: white;
-  border-color: white;
-}
-
-.btn__white:hover {
-  background-color: white;
-  border-color: white;
-  color: #0c486c;
-}
-
-@media (min-width: 48em) {
-  .btn {
-    border: 3px solid #0c486c;
-  }
-  .btn__white {
-    border-color: white;
-  }
-}
-
-@media (min-width: 64em) {
-  .btn {
-    padding: 0.625rem 2.5rem;
-  }
-}
-
-.btn__compact {
-  padding: 0.625rem 1.25rem;
-  border: 1px solid #3ab1c9;
-}
-
-.btn__active {
-  border: solid #3ab1c9;
-}
-
-
-.btn-group {
-  display: -ms-flexbox;
-  display: flex;
-  -ms-flex-align: center;
-  align-items: center;
-}
-
-.btn-group > .btn,
-.btn-group > .btn-svg {
-  display: inline-block;
-  margin-right: 20px;
-  margin-right: 1.25rem;
-}
-
-.btn-svg {
-  width: 2em;
-  height: 2em;
-  position: relative;
-  border-bottom: 0;
-  display: block;
-}
-
-.btn-svg > svg {
-  position: absolute;
-  left: 0;
-  top: 0;
-  height: 100%;
-  width: 100%;
-}
-
-.btn-svg__alt {
-  color: #3ab1c9;
-}
-
-.btn-svg__alt:hover {
-  color: #0c486c;
-}
-
-.select {
-  position: relative;
-}
-
-.select select {
-  outline: 0;
-  border: 0;
-  width: 100%;
-  background-color: white;
-  color: #0c486c;
-  padding: 10px;
-  padding-right: 35px;
-  appearance: none;
-  -moz-appearance: none;
-  -webkit-appearance: none;
-}
-
-.select select:active {
-  background-color: whitesmoke;
-}
-
-.select select::-ms-expand {
-  display: none;
-}
-
-.select:after {
-  content: ' ';
-  display: block;
-  position: absolute;
-  border-right: 1px solid #3ab1c9;
-  border-bottom: 1px solid #3ab1c9;
-  right: 15px;
-  top: 9px;
-  width: 15px;
-  height: 15px;
-  transform: rotate(45deg);
-}
-
-.boolean-group {
-  border: 0;
-  list-style: none;
-  margin-bottom: 0;
-}
-
-.boolean-group--row {
-  display: block;
-}
-
-.boolean-group--row + .boolean-group--row {
-  margin-top: 6.666px;
-  margin-top: 0.41667rem;
-}
-
-.boolean-group--row > label {
-  position: relative;
-  padding-left: 17px;
-  display: block;
-  vertical-align: top;
-}
-
-.boolean-group--row > label:before {
-  content: '';
-  width: 12px;
-  height: 12px;
-  display: block;
-  position: absolute;
-  border: 1px solid #3ab1c9;
-  top: calc(50% - 12px / 2);
-  left: 0;
-}
-
-.boolean-group--row > label:after {
-  transition: all 300ms ease-in-out;
-  background-color: #0c486c;
-  content: '';
-  display: block;
-  position: absolute;
-  width: 0;
-  height: 0;
-  left: 6px;
-  top: calc(50% - 12px / 2 + 4px * 1.5);
-}
-
-.boolean-group--row > input {
-  display: none;
-}
-
-.boolean-group--row > input:checked ~ label:after {
-  width: calc(12px - 4px);
-  height: calc(12px - 4px);
-  left: 2px;
-  top: calc(50% - 12px / 2 + (4px / 2));
-}
-
-.boolean-group--row > input[type="radio"] ~ label:before, .boolean-group--row > input[type="radio"] ~ label:after {
-  border-radius: 100%;
-}
-
-.panel {
-  padding: 40px 0;
-  padding: 2.5rem 0;
-  /*
-	*	Tabbed panel styles
-	*/
-}
-
-.panel--content {
-  position: relative;
-  z-index: 1;
-}
-
-.panel--section-title {
-  margin: 4px 0;
-  margin: 0.25rem 0;
-}
-
-.panel--1-2 {
-  margin: 20px 0;
-  margin: 1.25rem 0;
-  width: 100%;
-}
-
-.panel__bg {
-  background-color: #0c486c;
-  color: white;
-  position: relative;
-}
-
-.panel--bg {
-  position: absolute;
-  opacity: 0.3;
-  background-position: center;
-  background-size: cover;
-  background-blend-mode: multiply;
-  width: 100%;
-  height: 100%;
-  top: 0;
-  left: 0;
-}
-
-.panel--tabs {
-  display: block;
-}
-
-.panel--tab-title {
-  margin: 20px 0;
-  margin: 1.25rem 0;
-}
-
-.panel--tab-content {
-  display: none;
-  -ms-flex-item-align: start;
-  align-self: flex-start;
-}
-
-.panel--tab-content > *:first-child {
-  margin-top: 0;
-}
-
-.panel--tab-content.is-active {
-  display: block;
-}
-
-.panel--tab-controls {
-  margin-bottom: 20px;
-  margin-bottom: 1.25rem;
-  display: block;
-  width: 100%;
-}
-
-.panel--tab-controls-title {
-  margin-top: 0;
-}
-
-.panel--tab-switch {
-  margin-right: 20px;
-  margin-right: 1.25rem;
-  border-bottom: 0;
-}
-
-.panel--tab-switch:hover, .panel--tab-switch.is-active {
-  cursor: pointer;
-  border-bottom: 1px solid #3ab1c9;
-}
-
-.panel__compact {
-  padding-top: 10px 0;
-  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;
-}
-
-.panel__compact .panel--content {
-  -ms-flex-align: start;
-  -ms-grid-row-align: flex-start;
-  align-items: flex-start;
-}
-
-.panel__compact .panel--1-3 {
-  width: 100%;
-  margin-bottom: 2em;
-
-}
-
-@media (min-width: 48em) {
-  .panel {
-    padding: 0;
-  }
-  .panel--section-title {
-    margin: 3.75rem 0;
-  }
-  .panel--1-2 {
-    margin: 0.625rem 0;
-    -ms-flex-preferred-size: 46%;
-    flex-basis: 46%;
-  }
-  .panel--1-3, .panel--2-3, .panel--1-2 {
-    margin: 0;
-  }
-  .panel--content {
-    height: 700px;
-    -ms-flex-align: center;
-    -ms-grid-row-align: center;
-    align-items: center;
-  }
-  .panel__compact {
-    padding-top: 5rem;
-  }
-
-  .panel__compact.panel__first {
-    padding-top: 0rem;
-  }
-
-  .panel__compact.panel__last {
-    padding-bottom: 2.5rem;
-  }
-  .panel__compact .panel--content {
-    height: auto;
-  }
-}
-
-.illustration {
-  width: 35%;
-  padding-top: 35%;
-  position: relative;
-  border-radius: 100%;
-  box-shadow: 0px 0px 26px rgba(0, 0, 0, 0.5);
-  margin-bottom: 20px;
-  margin-bottom: 1.25rem;
-}
-
-.illustration > svg {
-  display: block;
-  position: absolute;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-}
-
-.illustration > img {
-  display: block;
-  position: absolute;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-}
-
-@media (min-width: 48em) {
-  .illustration {
-    width: 100%;
-    padding-top: 100%;
-    margin-bottom: 0;
-    box-shadow: 0px 0px 51px rgba(0, 0, 0, 0.5);
-  }
-}
-
-.link-list {
-  list-style: none;
-  margin-top: 20px;
-  margin-top: 1.25rem;
-}
-
-.link-list--item + .link-list--item {
-  margin-top: 10px;
-  margin-top: 0.625rem;
-}
-
-.link-list--link {
-  font-weight: 600;
-}
-
-@media (min-width: 48em) {
-  .link-list {
-    margin-top: 0;
-  }
-  .link-list--item + .link-list--item {
-    margin-top: 1.25rem;
-  }
-}
-
-@media (min-width: 48em) {
-  .right-floating-image {
-    width: 30%;
-    float: right;
-    margin: 4rem;
-    margin-right: 0;
-  }
-}
-
-.portrait {
-  position: relative;
-  overflow: hidden;
-  width: 35%;
-  border-radius: 100%;
-  box-shadow: 0px 0px 26px rgba(0, 0, 0, 0.5);
-  margin-bottom: 20px;
-  margin-bottom: 1.25rem;
-}
-
-.portrait:before {
-  content: "";
-  display: block;
-  padding-top: 100%;
-}
-
-.portrait--img {
-  position: absolute;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  width: 100%;
-  height: 100%;
-  background-size: cover;
-  background-position: center;
-}
-
-@media (min-width: 48em) {
-  .portrait {
-    width: 100%;
-    margin-bottom: 0;
-    box-shadow: 0px 0px 51px rgba(0, 0, 0, 0.5);
-  }
-}
-
-.menu-dropdown {
-  position: absolute;
-  left: 0;
-  padding-top: 20px;
-  padding-top: 1.25rem;
-  display: none;
-  z-index: 50;
-}
-
-.menu-dropdown:before {
-  position: absolute;
-  content: '';
-  background: linear-gradient(45deg, #89d0de 50%, transparent 50%);
-  width: 20px;
-  width: 1.25rem;
-  height: 20px;
-  height: 1.25rem;
-  top: 0;
-  left: 0;
-}
-
-.menu-dropdown__last {
-  right: 0;
-  left: auto;
-}
-
-.menu-dropdown__last:before {
-  background: linear-gradient(-45deg, #89d0de 50%, transparent 50%);
-  left: auto;
-  right: 0;
-}
-
-.menu-dropdown .link-list {
-  font-size: 0.8em;
-  background-color: #89d0de;
-  padding-top: 20px;
-  padding-top: 1.25rem;
-}
-
-.menu-dropdown .link-list--item {
-  margin: 0;
-  display: block;
-}
-
-.menu-dropdown .link-list--item:hover {
-  background-color: #0c486c;
-  color: white;
-}
-
-.menu-dropdown .link-list--link {
-  font-weight: normal;
-  border: 0;
-  padding: 6.666px 40px 6.666px 20px;
-  padding: 0.41667rem 2.5rem 0.41667rem 1.25rem;
-  width: 100%;
-  height: 100%;
-  display: block;
-  white-space: nowrap;
-}
-
-.menu-dropdown .link-list--link:hover {
-  color: white;
-}
-
-.mobile-menu {
-  position: absolute;
-  width: 100%;
-  height: auto;
-  background-color: white;
-  left: 0;
-  top: 0;
-  bottom: 0;
-  opacity: 0;
-  will-change: opacity;
-  transition: all 300ms ease-in-out;
-  pointer-events: none;
-  z-index: 100;
-  display: block;
-  margin-bottom: -150%;
-}
-
-.mobile-menu--list {
-  margin: auto;
-  text-align: center;
-  margin-top: 20%;
-  list-style: none;
-}
-
-.mobile-menu--item > a {
-  font-size: 2em;
-  border: 0;
-}
-
-.mobile-menu--item__primary > a {
-  font-size: 2.5em;
-  font-family: "Titillium Web", sans-serif;
-  font-weight: 900;
-  text-transform: uppercase;
-}
-
-.mobile-menu.is-active {
-  opacity: 1;
-  pointer-events: all;
-}
-
-@media (min-width: 48em) {
-  .mobile-menu {
-    display: none !important;
-  }
-}
-
-.l-header {
-  display: -ms-flexbox;
-  display: flex;
-  -ms-flex-pack: justify;
-  justify-content: space-between;
-  margin-top: 50px;
-  margin-top: 3.125rem;
-}
-
-.l-header--logo {
-  display: inline-block;
-  width: 65%;
-}
-
-.l-header--logo > svg {
-  display: block;
-  width: 100%;
-  height: 100%;
-}
-
-.l-header--logo > a {
-  border: 0;
-}
-
-.l-header--menu-opener {
-  float: right;
-  width: 22px;
-  height: 22px;
-  position: relative;
-  -ms-flex-item-align: start;
-  align-self: flex-start;
-  border: 0;
-  z-index: 101;
-}
-
-.l-header--menu-opener:after, .l-header--menu-opener:before {
-  transition: all 300ms ease-in-out;
-  content: '';
-  width: 100%;
-  height: 4.4px;
-  display: block;
-  position: absolute;
-  left: 0;
-  transform-origin: center;
-  background-color: #0c486c;
-}
-
-.l-header--menu-opener:before {
-  top: 0;
-  box-shadow: 0 8.8px #0c486c;
-}
-
-.l-header--menu-opener:after {
-  bottom: 0;
-}
-
-.l-header--menu-opener.is-active:before {
-  transform: translateY(8.8px) rotate(45deg);
-  box-shadow: 0 8.8px rgba(12, 72, 108, 0);
-}
-
-.l-header--menu-opener.is-active:after {
-  transform: translateY(-8.8px) rotate(-45deg);
-}
-
-.l-header--links {
-  height: 100%;
-  -ms-flex-item-align: start;
-  align-self: flex-start;
-  display: none;
-  list-style: none;
-  white-space: nowrap;
-}
-
-.l-header--nav {
-  display: inline-block;
-  margin-right: 10px;
-  margin-right: 0.625rem;
-  vertical-align: top;
-  position: relative;
-}
-
-.l-header--nav:hover > .menu-dropdown {
-  display: block;
-}
-
-.l-header--nav > a {
-  vertical-align: top;
-  border: 0;
-  font-size: 0.8em;
-}
-
-@media (min-width: 25em) {
-  .l-header--logo {
-    width: 200px;
-    margin-right: 2.5rem;
-  }
-}
-
-@media (min-width: 48em) {
-  .l-header {
-    /*margin-top: 6.25rem;  /* issue #44. We don't like this. */
-  }
-  .l-header--menu-opener {
-    display: none !important;
-  }
-  .l-header--links {
-    display: inline-block;
-  }
-  .l-header--logo {
-    width: 255px;
-  }
-}
-
-@media (min-width: 64em) {
-  .l-header--logo {
-    width: 300px;
-  }
-  .l-header--nav {
-    margin-right: 1.25rem;
-  }
-}
-
-.l-speaker-page {
-  margin-top: 60px;
-  margin-top: 3.75rem;
-}
-
-.l-speaker-page--portrait {
-  width: 100%;
-}
-
-@media (min-width: 48em) {
-  .l-speaker-page {
-    -ms-flex-wrap: nowrap;
-    flex-wrap: nowrap;
-    margin-top: 5rem;
-  }
-  .l-speaker-page--content *:first-child {
-    margin-top: 0;
-  }
-  .l-speaker-page--portrait {
-    width: auto;
-  }
-}
-
-.l-footer {
-  display: -ms-flexbox;
-  display: flex;
-  -ms-flex-align: center;
-  align-items: center;
-  -ms-flex-wrap: wrap;
-  flex-wrap: wrap;
-  -ms-flex-direction: column;
-  flex-direction: column;
-  margin-top: 100px;
-  margin-top: 3.25rem;
-  padding: 50px 40px;
-  padding: 3.125rem 40px;
-  width: 100%;
-}
-
-.l-footer--logos {
-  display: -ms-flexbox;
-  display: flex;
-  -ms-flex-wrap: wrap;
-  flex-wrap: wrap;
-  -ms-flex-pack: distribute;
-  justify-content: space-around;
-  margin-top: 20px;
-  margin-top: 1.25rem;
-}
-
-.l-footer--logo {
-  max-width: 100%;
-  -ms-flex-preferred-size: 45%;
-  flex-basis: 45%;
-  margin-top: 1em;
-  margin-bottom: 1em;
-}
-
-.l-footer--logo > svg, .l-footer--logo > img {
-  display: block;
-  width: 100%;
-  height: 100%;
-}
-
-
-.l-footer__alt {
-  /* background-color: #0c486c; */
-  background-color: transparent;
-  color: white;
-}
-
-@media (min-width: 48em) {
-  .l-footer {
-    /*padding: 6.25rem 70px; /* issue #44 too much padding */
-    padding: 3.25rem 70px;
-    -ms-flex-direction: row;
-    flex-direction: row;
-    -ms-flex-pack: justify;
-    justify-content: space-between;
-  }
-  .l-footer--logos {
-    margin-top: 0;
-  }
-}
-
-@media (min-width: 64em) {
-  .l-footer {
-    /*padding: 6.25rem 160px; */
-    padding: 3.25rem 160px;
-  }
-}
-
-@media (min-width: 80em) {
-  .l-footer {
-    /*padding: 6.25rem 200px;*/
-    padding: 3.25rem 200px;
-  }
-  .l-footer--logo {
-    margin-bottom: 0;
-    -ms-flex-preferred-size: auto;
-    flex-basis: auto;
-  }
-}
-
-@media (min-width: 1946px) {
-  .l-footer {
-    padding: 3.25rem auto;
-    /*padding: 6.25rem auto;*/
-  }
-}
-
-.l-content-page {
-  margin-top: 80px;
-  margin-top: 5rem;
-}
-
-.l-content-page--image {
-  height: 200px;
-  width: 100%;
-  margin: 7vw 0;
-  background-size: cover;
-  background-position: center;
-}
-
-@media (min-width: 48em) {
-  .l-content-page--image {
-    height: 350px;
-  }
-}
-
-@media (min-width: 64em) {
-  .l-content-page--image {
-    height: 500px;
-  }
-}
-
-@media (min-width: 80em) {
-  .l-content-page--image {
-    height: 600px;
-  }
-}
-
-.l-404 {
-  margin-top: 80px;
-  margin-top: 5rem;
-}
-
-@media (min-width: 64em) {
-  .l-404--content {
-    max-width: 50%;
-  }
-}
-
-.styleguide > .black {
-  background-color: black;
-  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;
-}
-
-
-/* Schedule timetable for mobile */
-
-@media only screen and (max-width: 480px)  {
-
-  .calendar {
-    line-height: 1.0;
-  }
-
-  .calendar thead {
-    display: none;
-  }
-
-  .calendar tr,
-  .calendar td
-  {
-    display: block;
-    padding: 0.5ex;
-    font-size: small;
-  }
-
-  .calendar td,
-  .calendar td:not(:first-of-type) {
-    border: 0px;
-  }
-
-  .calendar .slot .title,
-  .calendar .slot .speaker,
-  .calendar .slot .room
-  {
-    display: block;
-    line-height: 1.2;
-  }
-
-  .calendar .slot .room
-  {
-    font-style: italic;
-  }
-}
-
-/* Schedule timetable needs some more specific font sizes */
-
-@media not screen and (max-width: 480px)  {
-
-  .calendar td {
-    font-size: small;
-  }
-
-  .slot {
-    padding-left: 0.625rem;
-    padding-top: 0.2rem;
-    padding-bottom: 0.625rem;
-    padding-right: 0.625rem;
-    vertical-align: middle;
-  }
-
-  .calendar th {
-    border-bottom: 1px solid #0c486c;
-    border-left: 0px !important;
-  }
-
-  .calendar .title {
-    display: block;
-    padding-bottom: 0.5vh;
-  }
-
-  .slot-shortbreak {
-    color: #fff ;
-  }
-
-  .slot-tutorial {
-    vertical-align: top;
-  }
-
-  .time {
-    font-size: xx-small;
-    line-height: 0.4;
-    vertical-align: middle;
-    padding-top: 0.25rem;
-  }
-
-  .calendar .slot .room {
-    display: none;
-  }
-}
-
-
-/* ------------------------------------------------------------------------------------------------
-
-Shame
-=====
-
-1. 	If it’s a hack, it goes in shame.css.
-2. 	Document all hacks fully:
-  a. 	What part of the codebase does it relate to?
-  b. 	Why was this needed?
-  c. 	How does this fix it?
-  d. 	How might you fix it properly, given more time?
-3. 	Do not blame the developer; if they explained why they had to do it then their reasons are
-probably (hopefully) valid.
-4. 	Try and clean shame.css up when you have some down time.
-
-via: http://csswizardry.com/2013/04/shame-css/
-
------------------------------------------------------------------------------------------------- */
-
-/*# sourceMappingURL=maps/app.css.map */

From 9d7be5f6dfcfb5f0bc94e78ec2395006528f871e Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sat, 30 Sep 2017 18:11:05 +1000
Subject: [PATCH 22/40] Update YesNoField coercion

I am not sure if this is related to a py2/py3 change, or if it's
because django 1.11 changed the way it handles booleans; but either
way, this works.
---
 pinaxcon/registrasion/forms.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pinaxcon/registrasion/forms.py b/pinaxcon/registrasion/forms.py
index b655f11c..7bb21f1e 100644
--- a/pinaxcon/registrasion/forms.py
+++ b/pinaxcon/registrasion/forms.py
@@ -9,7 +9,7 @@ class YesNoField(forms.TypedChoiceField):
         kwargs['required'] = True
         super(YesNoField, self).__init__(
             *args,
-            coerce=lambda x: x is True,
+            coerce=lambda x: x in ['True', 'Yes', True],
             choices=((None, '--------'), (False, 'No'), (True, 'Yes')),
             **kwargs
         )

From 4605ee0d98ce8604bf39468380720078fdab7306 Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sat, 30 Sep 2017 18:14:46 +1000
Subject: [PATCH 23/40] Require attendees to agree to Code of Conduct

---
 .../migrations/0007_auto_20170930_1610.py     | 26 +++++++++++++++++++
 pinaxcon/registrasion/models.py               | 18 +++++++++++++
 .../migrations/0008_auto_20170930_1843.py     | 20 ++++++++++++++
 3 files changed, 64 insertions(+)
 create mode 100644 pinaxcon/registrasion/migrations/0007_auto_20170930_1610.py
 create mode 100644 vendor/registrasion/registrasion/migrations/0008_auto_20170930_1843.py

diff --git a/pinaxcon/registrasion/migrations/0007_auto_20170930_1610.py b/pinaxcon/registrasion/migrations/0007_auto_20170930_1610.py
new file mode 100644
index 00000000..c5dafab2
--- /dev/null
+++ b/pinaxcon/registrasion/migrations/0007_auto_20170930_1610.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2017-09-30 06:10
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('pinaxcon_registrasion', '0006_auto_20170927_2301'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='attendeeprofile',
+            name='agreement',
+            field=models.BooleanField(default=False, help_text='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>.'),
+            preserve_default=False,
+        ),
+        migrations.AlterField(
+            model_name='attendeeprofile',
+            name='state',
+            field=models.CharField(blank=True, help_text='If your Country is Australia, you must list a state.', max_length=256, verbose_name='State/Territory/Province'),
+        ),
+    ]
diff --git a/pinaxcon/registrasion/models.py b/pinaxcon/registrasion/models.py
index d341738d..db28ba26 100644
--- a/pinaxcon/registrasion/models.py
+++ b/pinaxcon/registrasion/models.py
@@ -66,6 +66,12 @@ class AttendeeProfile(rego.AttendeeProfileBase):
                 "Please fill in line 1 before filling line 2",
             ))
 
+        if not self.agreement:
+            errors.append((
+                "agreement",
+                "Agreement to the terms and conditions and to the Code of Conduct is required.",
+            ))
+
         if errors:
             raise ValidationError(dict(errors))
 
@@ -138,6 +144,7 @@ class AttendeeProfile(rego.AttendeeProfileBase):
     state = models.CharField(
         max_length=256,
         verbose_name="State/Territory/Province",
+        help_text="If your Country is Australia, you must list a state.",
         blank=True,
     )
 
@@ -194,6 +201,17 @@ class AttendeeProfile(rego.AttendeeProfileBase):
         blank=True,
     )
 
+    agreement = models.BooleanField(
+        blank=False,
+        help_text="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>."
+    )
+
 
     past_lca = models.ManyToManyField(
         PastEvent,
diff --git a/vendor/registrasion/registrasion/migrations/0008_auto_20170930_1843.py b/vendor/registrasion/registrasion/migrations/0008_auto_20170930_1843.py
new file mode 100644
index 00000000..c4ed7ae7
--- /dev/null
+++ b/vendor/registrasion/registrasion/migrations/0008_auto_20170930_1843.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2017-09-30 08:43
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('registrasion', '0007_merge_20170929_2331'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='attendee',
+            name='guided_categories_complete',
+            field=models.ManyToManyField(blank=True, to='registrasion.Category'),
+        ),
+    ]

From e51ad76384f1227dc8d1ad54a2d7d0a6d9d849cb Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sat, 30 Sep 2017 18:15:28 +1000
Subject: [PATCH 24/40] Add a "Back" button to the guided_registration stages

"Next" is green, indicating that it's the default path, the way
forward. "Back" is available but blue.

For extra consistency, the initial "Get ticket" button is now also a btn-success
---
 pinaxcon/templates/dashboard.html                        | 2 +-
 pinaxcon/templates/registrasion/guided_registration.html | 5 ++++-
 vendor/registrasion/registrasion/views.py                | 4 ++++
 3 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/pinaxcon/templates/dashboard.html b/pinaxcon/templates/dashboard.html
index eab991c5..bee1c3c0 100644
--- a/pinaxcon/templates/dashboard.html
+++ b/pinaxcon/templates/dashboard.html
@@ -43,7 +43,7 @@
             </div>
             <div class="panel-body">
               <p>To attend the conference, you must register an attendee profile and purchase your ticket</p>
-              <a class="btn btn-lg btn-primary" role="button" href="{% url "guided_registration" %}">Get your ticket</a>
+              <a class="btn btn-lg btn-success" role="button" href="{% url "guided_registration" %}">Get your ticket</a>
             </div>
           </div>
         {% else %}
diff --git a/pinaxcon/templates/registrasion/guided_registration.html b/pinaxcon/templates/registrasion/guided_registration.html
index 01927a6b..d3b30b85 100644
--- a/pinaxcon/templates/registrasion/guided_registration.html
+++ b/pinaxcon/templates/registrasion/guided_registration.html
@@ -45,7 +45,10 @@
     {% endfor %}
 
     <div class="btn-group">
-      <input class="btn btn-primary" type="submit" value="Next Step" />
+      {% if current_step > 1 %}
+      <a class="btn btn-primary" role="button" href="{{ previous_step }}">Back</a>
+      {% endif %}
+      <input class="btn btn-success" type="submit" value="Next Step" />
     </div>
   </form>
 
diff --git a/vendor/registrasion/registrasion/views.py b/vendor/registrasion/registrasion/views.py
index ac78d842..4d6a078f 100644
--- a/vendor/registrasion/registrasion/views.py
+++ b/vendor/registrasion/registrasion/views.py
@@ -94,6 +94,7 @@ def guided_registration(request, page_number=None):
             with the following data::
 
                 {
+                    "previous_step": int(),  # Previous step
                     "current_step": int(),  # The current step in the
                                             # registration
                     "sections": sections,   # A list of
@@ -152,6 +153,8 @@ def guided_registration(request, page_number=None):
 
     page_number = int(page_number)
 
+    prev_step = page_number - 1
+
     next_step = redirect("guided_registration", page_number + 1)
 
     with BatchController.batch(request.user):
@@ -206,6 +209,7 @@ def guided_registration(request, page_number=None):
                 return next_step
 
     data = {
+        "previous_step": prev_step,
         "current_step": page_number,
         "sections": sections,
         "title": title,

From 287d412ccfc0faa481d4bfe0e11a0a3b409eebab Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sat, 30 Sep 2017 22:50:13 +1000
Subject: [PATCH 25/40] Catch TypeErrors from bleach

---
 vendor/registrasion/registrasion/reporting/views.py | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/vendor/registrasion/registrasion/reporting/views.py b/vendor/registrasion/registrasion/reporting/views.py
index 8c38351a..21f03a16 100644
--- a/vendor/registrasion/registrasion/reporting/views.py
+++ b/vendor/registrasion/registrasion/reporting/views.py
@@ -572,7 +572,10 @@ def attendee(request, form, user_id=None):
         if isinstance(field, models.ManyToManyField):
             value = ", ".join(str(i) for i in value.all())
         elif isinstance(field, CharField):
-            value = bleach.clean(value)
+            try:
+                value = bleach.clean(value)
+            except TypeError:
+                value = "Bad value for %s" % field.name
 
         profile_data.append((field.verbose_name, value))
 
@@ -813,7 +816,7 @@ def attendee_data(request, form, user_id=None):
                     return None
         else:
             def display_field(value):
-                return bleach.clean(value)
+                return bleach.clean(str(value))
 
         status_count = lambda status: Case(When(  # noqa
                 attendee__user__cart__status=status,
@@ -860,7 +863,10 @@ def attendee_data(request, form, user_id=None):
         if isinstance(field_type, models.ManyToManyField):
             return [str(i) for i in attr.all()] or ""
         else:
-            return bleach.clean(attr)
+            try:
+                return bleach.clean(attr)
+            except TypeError:
+                return "Bad value found for %s" % attr
 
     headings = ["User ID", "Name", "Email", "Product", "Item Status"]
     headings.extend(field_names)

From 68b6fe8f1e6ae69628a93db2f78d90d2675014d7 Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sat, 30 Sep 2017 23:40:16 +1000
Subject: [PATCH 26/40] Offset polution with correct spelling.

---
 pinaxcon/registrasion/management/commands/populate_inventory.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pinaxcon/registrasion/management/commands/populate_inventory.py b/pinaxcon/registrasion/management/commands/populate_inventory.py
index eb0011e0..dfc8597b 100644
--- a/pinaxcon/registrasion/management/commands/populate_inventory.py
+++ b/pinaxcon/registrasion/management/commands/populate_inventory.py
@@ -358,7 +358,7 @@ class Command(BaseCommand):
             inv.Product,
             ("name", "category",),
             category=self.extras,
-            name="Offset the carbon polution generated by your attendance, "
+            name="Offset the carbon pollution generated by your attendance, "
                  "thanks to fifteen trees.",
             price=Decimal("5.00"),
             reservation_duration=hours(1),

From 73964cd8201e7b8b1ab105239184dc4016030e55 Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sat, 30 Sep 2017 23:40:46 +1000
Subject: [PATCH 27/40] Update shirt details to match plans

---
 .../management/commands/populate_inventory.py      | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/pinaxcon/registrasion/management/commands/populate_inventory.py b/pinaxcon/registrasion/management/commands/populate_inventory.py
index dfc8597b..bb2cb210 100644
--- a/pinaxcon/registrasion/management/commands/populate_inventory.py
+++ b/pinaxcon/registrasion/management/commands/populate_inventory.py
@@ -110,8 +110,8 @@ class Command(BaseCommand):
         self.t_shirt = self.find_or_make(
             inv.Category,
             ("name",),
-            name="T-Shirt",
-            description="Commemorative conference t-shirts, featuring the "
+            name="Shirt",
+            description="Commemorative conference polo shirts, featuring the "
                         "linux.conf.au 2018 artwork.",
             required=False,
             render_type=inv.Category.RENDER_TYPE_ITEM_QUANTITY,
@@ -369,12 +369,12 @@ class Command(BaseCommand):
         ShirtGroup = namedtuple("ShirtGroup", ("prefix", "sizes"))
         shirt_names = {
             "mens": ShirtGroup(
-                "Men's/Straight Cut Size",
-                ("S", "M", "L", "XL", "2XL", "3XL", "5XL"),
+                "Men's/Straight Cut",
+                ("S", "M", "L", "XL", "2XL", "3XL", "4XL"),
             ),
             "womens": ShirtGroup(
                 "Women's Classic Fit",
-                ("XS", "S", "M", "L", "XL", "2XL"),
+                ("8", "10", "12", "14", "16", "18"),
             ),
         }
 
@@ -769,11 +769,11 @@ class Command(BaseCommand):
         ])
         free_category(ticket_staff_inclusions, self.penguin_dinner)
 
-        # Team & volunteer t-shirts, regardless of ticket type
+        # Team & volunteer shirts, regardless of ticket type
         staff_t_shirts = self.find_or_make(
             cond.GroupMemberDiscount,
             ("description", ),
-            description="T-shirts complimentary for staff and volunteers",
+            description="Shirts complimentary for staff and volunteers",
         )
         staff_t_shirts.group.set([
             self.group_team,

From c76c850c80e4c63c885a82e1f701455f5b7bd8f3 Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sun, 1 Oct 2017 11:32:12 +1100
Subject: [PATCH 28/40] Switch to the upstream registrasion docs

---
 docs/index.rst                           |   8 +-
 docs/registrasion/Makefile               | 231 ------------------
 docs/registrasion/README.rst             |  34 ---
 docs/registrasion/conf.py                | 297 -----------------------
 docs/registrasion/django_settings.py     | 122 ----------
 docs/registrasion/for-zookeepr-users.rst |  22 --
 docs/registrasion/index.rst              |  35 ---
 docs/registrasion/integration.rst        |  67 -----
 docs/registrasion/inventory.rst          | 158 ------------
 docs/registrasion/make.bat               | 281 ---------------------
 docs/registrasion/overview.rst           |  51 ----
 docs/registrasion/payments.rst           | 165 -------------
 docs/registrasion/views.rst              |  41 ----
 13 files changed, 1 insertion(+), 1511 deletions(-)
 delete mode 100644 docs/registrasion/Makefile
 delete mode 100644 docs/registrasion/README.rst
 delete mode 100644 docs/registrasion/conf.py
 delete mode 100644 docs/registrasion/django_settings.py
 delete mode 100644 docs/registrasion/for-zookeepr-users.rst
 delete mode 100644 docs/registrasion/index.rst
 delete mode 100644 docs/registrasion/integration.rst
 delete mode 100644 docs/registrasion/inventory.rst
 delete mode 100644 docs/registrasion/make.bat
 delete mode 100644 docs/registrasion/overview.rst
 delete mode 100644 docs/registrasion/payments.rst
 delete mode 100644 docs/registrasion/views.rst

diff --git a/docs/index.rst b/docs/index.rst
index 4002c9d2..d87b76d8 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -7,7 +7,7 @@ submission, reviews, scheduling and sponsor management.
 
 
 .. toctree::
-   :maxdepth: 2
+   :maxdepth: 3
 
    project
    conference
@@ -17,12 +17,6 @@ submission, reviews, scheduling and sponsor management.
    speakers
    schedule
    miniconfs
-
-.. include:: registrasion/README.rst
-
-.. toctree::
-   :maxdepth: 3
-
    registrasion/index.rst
 
 About
diff --git a/docs/registrasion/Makefile b/docs/registrasion/Makefile
deleted file mode 100644
index 88006e7a..00000000
--- a/docs/registrasion/Makefile
+++ /dev/null
@@ -1,231 +0,0 @@
-# Makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line.
-SPHINXOPTS    =
-SPHINXBUILD   = sphinx-build
-PAPER         =
-BUILDDIR      = _build
-
-# User-friendly check for sphinx-build
-ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
-	$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don\'t have Sphinx installed, grab it from http://sphinx-doc.org/)
-endif
-
-
-# Internal variables.
-PAPEROPT_a4     = -D latex_paper_size=a4
-PAPEROPT_letter = -D latex_paper_size=letter
-ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-# the i18n builder cannot share the environment and doctrees with the others
-I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-
-.PHONY: help
-help:
-	@echo "Please use \`make <target>' where <target> is one of"
-	@echo "  html       to make standalone HTML files"
-	@echo "  dirhtml    to make HTML files named index.html in directories"
-	@echo "  singlehtml to make a single large HTML file"
-	@echo "  pickle     to make pickle files"
-	@echo "  json       to make JSON files"
-	@echo "  htmlhelp   to make HTML files and a HTML help project"
-	@echo "  qthelp     to make HTML files and a qthelp project"
-	@echo "  applehelp  to make an Apple Help Book"
-	@echo "  devhelp    to make HTML files and a Devhelp project"
-	@echo "  epub       to make an epub"
-	@echo "  epub3      to make an epub3"
-	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
-	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
-	@echo "  latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
-	@echo "  text       to make text files"
-	@echo "  man        to make manual pages"
-	@echo "  texinfo    to make Texinfo files"
-	@echo "  info       to make Texinfo files and run them through makeinfo"
-	@echo "  gettext    to make PO message catalogs"
-	@echo "  changes    to make an overview of all changed/added/deprecated items"
-	@echo "  xml        to make Docutils-native XML files"
-	@echo "  pseudoxml  to make pseudoxml-XML files for display purposes"
-	@echo "  linkcheck  to check all external links for integrity"
-	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
-	@echo "  coverage   to run coverage check of the documentation (if enabled)"
-	@echo "  dummy      to check syntax errors of document sources"
-
-.PHONY: clean
-clean:
-	rm -rf $(BUILDDIR)/*
-
-.PHONY: html
-html:
-	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
-	@echo
-	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
-
-.PHONY: dirhtml
-dirhtml:
-	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
-	@echo
-	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
-
-.PHONY: singlehtml
-singlehtml:
-	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
-	@echo
-	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
-
-.PHONY: pickle
-pickle:
-	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
-	@echo
-	@echo "Build finished; now you can process the pickle files."
-
-.PHONY: json
-json:
-	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
-	@echo
-	@echo "Build finished; now you can process the JSON files."
-
-.PHONY: htmlhelp
-htmlhelp:
-	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
-	@echo
-	@echo "Build finished; now you can run HTML Help Workshop with the" \
-	      ".hhp project file in $(BUILDDIR)/htmlhelp."
-
-.PHONY: qthelp
-qthelp:
-	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
-	@echo
-	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
-	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
-	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Registrasion.qhcp"
-	@echo "To view the help file:"
-	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Registrasion.qhc"
-
-.PHONY: applehelp
-applehelp:
-	$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
-	@echo
-	@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
-	@echo "N.B. You won't be able to view it unless you put it in" \
-	      "~/Library/Documentation/Help or install it in your application" \
-	      "bundle."
-
-.PHONY: devhelp
-devhelp:
-	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
-	@echo
-	@echo "Build finished."
-	@echo "To view the help file:"
-	@echo "# mkdir -p $$HOME/.local/share/devhelp/Registrasion"
-	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Registrasion"
-	@echo "# devhelp"
-
-.PHONY: epub
-epub:
-	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
-	@echo
-	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
-
-.PHONY: epub3
-epub3:
-	$(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
-	@echo
-	@echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
-
-.PHONY: latex
-latex:
-	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
-	@echo
-	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
-	@echo "Run \`make' in that directory to run these through (pdf)latex" \
-	      "(use \`make latexpdf' here to do that automatically)."
-
-.PHONY: latexpdf
-latexpdf:
-	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
-	@echo "Running LaTeX files through pdflatex..."
-	$(MAKE) -C $(BUILDDIR)/latex all-pdf
-	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
-
-.PHONY: latexpdfja
-latexpdfja:
-	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
-	@echo "Running LaTeX files through platex and dvipdfmx..."
-	$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
-	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
-
-.PHONY: text
-text:
-	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
-	@echo
-	@echo "Build finished. The text files are in $(BUILDDIR)/text."
-
-.PHONY: man
-man:
-	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
-	@echo
-	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
-
-.PHONY: texinfo
-texinfo:
-	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
-	@echo
-	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
-	@echo "Run \`make' in that directory to run these through makeinfo" \
-	      "(use \`make info' here to do that automatically)."
-
-.PHONY: info
-info:
-	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
-	@echo "Running Texinfo files through makeinfo..."
-	make -C $(BUILDDIR)/texinfo info
-	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
-
-.PHONY: gettext
-gettext:
-	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
-	@echo
-	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
-
-.PHONY: changes
-changes:
-	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
-	@echo
-	@echo "The overview file is in $(BUILDDIR)/changes."
-
-.PHONY: linkcheck
-linkcheck:
-	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
-	@echo
-	@echo "Link check complete; look for any errors in the above output " \
-	      "or in $(BUILDDIR)/linkcheck/output.txt."
-
-.PHONY: doctest
-doctest:
-	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
-	@echo "Testing of doctests in the sources finished, look at the " \
-	      "results in $(BUILDDIR)/doctest/output.txt."
-
-.PHONY: coverage
-coverage:
-	$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
-	@echo "Testing of coverage in the sources finished, look at the " \
-	      "results in $(BUILDDIR)/coverage/python.txt."
-
-.PHONY: xml
-xml:
-	$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
-	@echo
-	@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
-
-.PHONY: pseudoxml
-pseudoxml:
-	$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
-	@echo
-	@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
-
-.PHONY: dummy
-dummy:
-	$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
-	@echo
-	@echo "Build finished. Dummy builder generates no files."
diff --git a/docs/registrasion/README.rst b/docs/registrasion/README.rst
deleted file mode 100644
index f8386edb..00000000
--- a/docs/registrasion/README.rst
+++ /dev/null
@@ -1,34 +0,0 @@
-Registrasion
-============
-
-Nick was here ...
-
-**Registra** (tion for Sympo) **sion**. A conference registration app for Django,
-letting conferences big and small sell tickets from within Symposion.
-
-Symposion
----------
-``symposion`` is an Open Source conference management solution built with Pinax
-apps for Django. For more information, see https://github.com/pinax/symposion.
-
-registrasion
-------------
-``registrasion`` is a registration package that you use alongside Symposion. It
-handles inventory management, as well as complex product inclusions, automatic
-calculation of discounts, and invoicing. Payment of invoices can be faciliated
-by manual filings of payments by staff, or through plugging in a payment app.
-
-Initial development of ``registrasion`` was funded with the generous support of
-the Python Software Foundation.
-
-Quickstart
-----------
-``registrasion`` is a Django app. You will need to create a Django project to
-customize and manage your Registrasion and Symposion installation. A
-demonstration app project with templates is available at
-https://github.com/chrisjrn/registrasion-demo
-
-Documentation
--------------
-The documentation for ``registrasion`` is available at
-http://registrasion.readthedocs.org/
diff --git a/docs/registrasion/conf.py b/docs/registrasion/conf.py
deleted file mode 100644
index 99d8902e..00000000
--- a/docs/registrasion/conf.py
+++ /dev/null
@@ -1,297 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Registrasion documentation build configuration file, created by
-# sphinx-quickstart on Thu Apr 21 11:29:51 2016.
-#
-# This file is execfile()d with the current directory set to its
-# containing dir.
-#
-# Note that not all possible configuration values are present in this
-# autogenerated file.
-#
-# All configuration values have a default; values that are commented out
-# serve to show the default.
-
-import sys
-import os
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
-
-# -- General configuration ------------------------------------------------
-
-# If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
-
-# Add any Sphinx extension module names here, as strings. They can be
-# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
-# ones.
-extensions = [
-    "sphinx.ext.autodoc",
-    "sphinx.ext.napoleon",
-]
-
-# Autodoc requires django to be ready to go, otherwise we can't import rego's
-# things...
-sys.path.insert(0, ".")
-os.environ["DJANGO_SETTINGS_MODULE"] = "django_settings"
-
-import django
-django.setup()
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
-
-# The suffix(es) of source filenames.
-# You can specify multiple suffix as a list of string:
-# source_suffix = ['.rst', '.md']
-source_suffix = '.rst'
-
-# The encoding of source files.
-#source_encoding = 'utf-8-sig'
-
-# The master toctree document.
-master_doc = 'index'
-
-# General information about the project.
-project = u'Registrasion'
-copyright = u'2016, Christopher Neugebauer'
-author = u'Christopher Neugebauer'
-
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The short X.Y version.
-version = u'0.1a1'
-# The full version, including alpha/beta/rc tags.
-release = u'0.1a1'
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-#
-# This is also used if you do content translation via gettext catalogs.
-# Usually you set "language" from the command line for these cases.
-language = None
-
-# There are two options for replacing |today|: either, you set today to some
-# non-false value, then it is used:
-#today = ''
-# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-# This patterns also effect to html_static_path and html_extra_path
-exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
-
-# The reST default role (used for this markup: `text`) to use for all
-# documents.
-#default_role = None
-
-# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
-
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
-#add_module_names = True
-
-# If true, sectionauthor and moduleauthor directives will be shown in the
-# output. They are ignored by default.
-#show_authors = False
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
-
-# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
-
-# If true, keep warnings as "system message" paragraphs in the built documents.
-#keep_warnings = False
-
-# If true, `todo` and `todoList` produce output, else they produce nothing.
-todo_include_todos = False
-
-
-# -- Options for HTML output ----------------------------------------------
-
-# The theme to use for HTML and HTML Help pages.  See the documentation for
-# a list of builtin themes.
-
-# on_rtd is whether we are on readthedocs.org, this line of code grabbed from docs.readthedocs.org
-on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
-
-if not on_rtd:  # only import and set the theme if we're building docs locally
-    import sphinx_rtd_theme
-    html_theme = 'sphinx_rtd_theme'
-    html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
-
-
-# The name for this set of Sphinx documents.
-# "<project> v<release> documentation" by default.
-#html_title = u'Registrasion v0.1a1'
-
-# A shorter title for the navigation bar.  Default is the same as html_title.
-#html_short_title = None
-
-# The name of an image file (relative to this directory) to place at the top
-# of the sidebar.
-#html_logo = None
-
-# The name of an image file (relative to this directory) to use as a favicon of
-# the docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
-# pixels large.
-#html_favicon = None
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
-
-# Add any extra paths that contain custom files (such as robots.txt or
-# .htaccess) here, relative to this directory. These files are copied
-# directly to the root of the documentation.
-#html_extra_path = []
-
-# If not None, a 'Last updated on:' timestamp is inserted at every page
-# bottom, using the given strftime format.
-# The empty string is equivalent to '%b %d, %Y'.
-#html_last_updated_fmt = None
-
-# If true, SmartyPants will be used to convert quotes and dashes to
-# typographically correct entities.
-#html_use_smartypants = True
-
-# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
-
-# Additional templates that should be rendered to pages, maps page names to
-# template names.
-#html_additional_pages = {}
-
-# If false, no module index is generated.
-#html_domain_indices = True
-
-# If false, no index is generated.
-#html_use_index = True
-
-# If true, the index is split into individual pages for each letter.
-#html_split_index = False
-
-# If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
-
-# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
-
-# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
-
-# If true, an OpenSearch description file will be output, and all pages will
-# contain a <link> tag referring to it.  The value of this option must be the
-# base URL from which the finished HTML is served.
-#html_use_opensearch = ''
-
-# This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
-
-# Language to be used for generating the HTML full-text search index.
-# Sphinx supports the following languages:
-#   'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
-#   'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh'
-#html_search_language = 'en'
-
-# A dictionary with options for the search language support, empty by default.
-# 'ja' uses this config value.
-# 'zh' user can custom change `jieba` dictionary path.
-#html_search_options = {'type': 'default'}
-
-# The name of a javascript file (relative to the configuration directory) that
-# implements a search results scorer. If empty, the default will be used.
-#html_search_scorer = 'scorer.js'
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = 'Registrasiondoc'
-
-# -- Options for LaTeX output ---------------------------------------------
-
-latex_elements = {
-# The paper size ('letterpaper' or 'a4paper').
-#'papersize': 'letterpaper',
-
-# The font size ('10pt', '11pt' or '12pt').
-#'pointsize': '10pt',
-
-# Additional stuff for the LaTeX preamble.
-#'preamble': '',
-
-# Latex figure (float) alignment
-#'figure_align': 'htbp',
-}
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title,
-#  author, documentclass [howto, manual, or own class]).
-latex_documents = [
-    (master_doc, 'Registrasion.tex', u'Registrasion Documentation',
-     u'Christopher Neugebauer', 'manual'),
-]
-
-# The name of an image file (relative to this directory) to place at the top of
-# the title page.
-#latex_logo = None
-
-# For "manual" documents, if this is true, then toplevel headings are parts,
-# not chapters.
-#latex_use_parts = False
-
-# If true, show page references after internal links.
-#latex_show_pagerefs = False
-
-# If true, show URL addresses after external links.
-#latex_show_urls = False
-
-# Documents to append as an appendix to all manuals.
-#latex_appendices = []
-
-# If false, no module index is generated.
-#latex_domain_indices = True
-
-
-# -- Options for manual page output ---------------------------------------
-
-# One entry per manual page. List of tuples
-# (source start file, name, description, authors, manual section).
-man_pages = [
-    (master_doc, 'registrasion', u'Registrasion Documentation',
-     [author], 1)
-]
-
-# If true, show URL addresses after external links.
-#man_show_urls = False
-
-
-# -- Options for Texinfo output -------------------------------------------
-
-# Grouping the document tree into Texinfo files. List of tuples
-# (source start file, target name, title, author,
-#  dir menu entry, description, category)
-texinfo_documents = [
-    (master_doc, 'Registrasion', u'Registrasion Documentation',
-     author, 'Registrasion', 'One line description of project.',
-     'Miscellaneous'),
-]
-
-# Documents to append as an appendix to all manuals.
-#texinfo_appendices = []
-
-# If false, no module index is generated.
-#texinfo_domain_indices = True
-
-# How to display URL addresses: 'footnote', 'no', or 'inline'.
-#texinfo_show_urls = 'footnote'
-
-# If true, do not generate a @detailmenu in the "Top" node's menu.
-#texinfo_no_detailmenu = False
diff --git a/docs/registrasion/django_settings.py b/docs/registrasion/django_settings.py
deleted file mode 100644
index 56a07eca..00000000
--- a/docs/registrasion/django_settings.py
+++ /dev/null
@@ -1,122 +0,0 @@
-"""
-Django settings for djangoenv project.
-
-Generated by 'django-admin startproject' using Django 1.9.5.
-
-For more information on this file, see
-https://docs.djangoproject.com/en/1.9/topics/settings/
-
-For the full list of settings and their values, see
-https://docs.djangoproject.com/en/1.9/ref/settings/
-"""
-
-import os
-
-# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
-BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-
-
-# Quick-start development settings - unsuitable for production
-# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
-
-# SECURITY WARNING: keep the secret key used in production secret!
-SECRET_KEY = 'z$cu8&jcnm#qa=xbrkss-4w8do+(pp16j*usmp9j&bg=)&1@-a'
-
-# SECURITY WARNING: don't run with debug turned on in production!
-DEBUG = True
-
-ALLOWED_HOSTS = []
-
-
-# Application definition
-
-INSTALLED_APPS = [
-    'django.contrib.admin',
-    'django.contrib.auth',
-    'django.contrib.contenttypes',
-    'django.contrib.sessions',
-    'django.contrib.messages',
-    'django.contrib.staticfiles',
-    'registrasion',
-]
-
-MIDDLEWARE_CLASSES = [
-    'django.middleware.security.SecurityMiddleware',
-    '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',
-    'django.middleware.clickjacking.XFrameOptionsMiddleware',
-]
-
-ROOT_URLCONF = 'djangoenv.urls'
-
-TEMPLATES = [
-    {
-        'BACKEND': 'django.template.backends.django.DjangoTemplates',
-        'DIRS': [],
-        'APP_DIRS': True,
-        'OPTIONS': {
-            'context_processors': [
-                'django.template.context_processors.debug',
-                'django.template.context_processors.request',
-                'django.contrib.auth.context_processors.auth',
-                'django.contrib.messages.context_processors.messages',
-            ],
-        },
-    },
-]
-
-WSGI_APPLICATION = 'djangoenv.wsgi.application'
-
-
-# Database
-# https://docs.djangoproject.com/en/1.9/ref/settings/#databases
-
-DATABASES = {
-    'default': {
-        'ENGINE': 'django.db.backends.sqlite3',
-        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
-    }
-}
-
-
-# Password validation
-# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
-
-AUTH_PASSWORD_VALIDATORS = [
-    {
-        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
-    },
-    {
-        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
-    },
-    {
-        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
-    },
-    {
-        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
-    },
-]
-
-
-# Internationalization
-# https://docs.djangoproject.com/en/1.9/topics/i18n/
-
-LANGUAGE_CODE = 'en-us'
-
-TIME_ZONE = 'UTC'
-
-USE_I18N = True
-
-USE_L10N = True
-
-USE_TZ = True
-
-
-# Static files (CSS, JavaScript, Images)
-# https://docs.djangoproject.com/en/1.9/howto/static-files/
-
-STATIC_URL = '/static/'
diff --git a/docs/registrasion/for-zookeepr-users.rst b/docs/registrasion/for-zookeepr-users.rst
deleted file mode 100644
index 28cd1932..00000000
--- a/docs/registrasion/for-zookeepr-users.rst
+++ /dev/null
@@ -1,22 +0,0 @@
-Registrasion for Zookeepr Keeprs
-================================
-
-Things that are the same
-------------------------
-* You have an inventory of products
-* Complete registrations are made up of multiple products
-* Products are split into categories
-* Products can be listed under ceilings
-* Products can be included for free by purchasing other items
- * e.g. a Professional Ticket includes a dinner ticket
-* Products can be enabled by user roles
- * e.g. Speakers Dinner tickets are visible to speakers
-* Vouchers can be used to discount products
-
-Things that are different
--------------------------
-* Ceilings can be used to apply discounts, so Early Bird ticket rates can be implemented by applying a ceiling-type discount, rather than duplicating the ticket type.
-* Vouchers can be used to enable products
- * e.g. Sponsor tickets do not appear until you supply a sponsor's voucher
-* Items may be enabled by having other specific items present
- * e.g. Extra accommodation nights do not appear until you purchase the main week worth of accommodation.
diff --git a/docs/registrasion/index.rst b/docs/registrasion/index.rst
deleted file mode 100644
index 75d0c4fa..00000000
--- a/docs/registrasion/index.rst
+++ /dev/null
@@ -1,35 +0,0 @@
-.. Registrasion documentation master file, created by
-   sphinx-quickstart on Thu Apr 21 11:29:51 2016.
-   You can adapt this file completely to your liking, but it should at least
-   contain the root `toctree` directive.
-
-Registrasion
-============
-
-Registra(tion for Sympo)sion.
-
-Registrasion is a conference registration package that goes well with the Symposion suite of conference management apps for Django. It's designed to manage the sorts of inventories that large conferences need to manage, build up complex tickets with multiple items, and handle payments using whatever payment gateway you happen to have access to
-
-Development of registrasion was commenced by Christopher Neugebauer in 2016, with the generous support of the Python Software Foundation.
-
-
-Contents:
----------
-.. toctree::
-   :maxdepth: 2
-
-   overview
-   integration
-   inventory
-   payments
-   for-zookeepr-users
-   views
-
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`search`
-
-.. * :ref:`modindex`
diff --git a/docs/registrasion/integration.rst b/docs/registrasion/integration.rst
deleted file mode 100644
index dc012359..00000000
--- a/docs/registrasion/integration.rst
+++ /dev/null
@@ -1,67 +0,0 @@
-Installing and integrating Registrasion
-=======================================
-
-Registrasion is a Django app. It does not provide any templates -- you'll need to develop these yourself. You can use the `registrasion-demo <https://github.com/chrisjrn/registrasion-demo>`_ project as a starting point.
-
-To use Registrasion for your own conference, you'll need to do a small amount of configuration and development work, in your own Django App.
-
-The configuration that you'll need to do is minimal. The first piece of development work is to define a model and form for your attendee profile, and the second is to implement a payment app.
-
-
-Installing Registrasion
------------------------
-
-Registrasion depends on an in-development version of Symposion. You'll need to add the following  line to your ``requirements.txt`` files::
-
-    registrasion==0.1.0
-    https://github.com/pinax/symposion/tarball/ad81810#egg=symposion
-
-And also to enable dependency links in pip::
-
-    pip install --process-dependency-links -r requirements.txt
-
-Symposion currently specifies Django version 1.9.2. Note that ``pip`` version 1.6 does not support ``--process-dependency-links``, so you'll need to use an earlier, or later version of ``pip``.
-
-
-Configuring your Django App
----------------------------
-
-In your Django ``settings.py`` file, you'll need to add the following to your ``INSTALLED_APPS``::
-
-  "registrasion",
-  "nested_admin",
-
-You will also need to configure ``symposion`` appropriately.
-
-
-Attendee profile
-----------------
-
-.. automodule:: registrasion.models.people
-
-Attendee profiles are where you ask for information such as what your attendee wants on their badge, and what the attendee's dietary and accessibility requirements are.
-
-Because every conference is different, Registrasion lets you define your own attendee profile model, and your own form for requesting this information. The only requirement is that you derive your model from ``AttendeeProfileBase``.
-
-.. autoclass :: AttendeeProfileBase
-    :members: name_field, invoice_recipient
-
-You specify how to find that model in your Django ``settings.py`` file::
-
-    ATTENDEE_PROFILE_MODEL = "democon.models.AttendeeProfile"
-
-When Registrasion asks the to edit their profile, a default form will be generated, showing all of the fields on the profile model.
-
-If you want to customise the profile editing form, you need to specify the location of that form in your ``settings.py`` file as well.
-
-    ATTENDEE_PROFILE_FORM = "democon.forms.AttendeeProfileForm"
-
-The only contract is that this form creates an instance of ``AttendeeProfileBase`` when saved, and that it can take an instance of your subclass on creation (so that your attendees can edit their profile).
-
-
-Payments
---------
-
-Registrasion does not implement its own credit card processing. You'll need to do that yourself. Registrasion *does* provide a mechanism for recording cheques and direct deposits, if you do end up taking registrations that way.
-
-See :ref:`payments_and_refunds` for a guide on how to correctly implement payments.
diff --git a/docs/registrasion/inventory.rst b/docs/registrasion/inventory.rst
deleted file mode 100644
index c9d6dec1..00000000
--- a/docs/registrasion/inventory.rst
+++ /dev/null
@@ -1,158 +0,0 @@
-
-Inventory Management
-====================
-
-Registrasion uses an inventory model to keep track of tickets, and the other various products that attendees of your conference might want to have, such as t-shirts and dinner tickets.
-
-All of the classes described herein are available through the Django Admin interface.
-
-Overview
---------
-
-The inventory model is split up into Categories and Products. Categories are used to group Products.
-
-Registrasion uses conditionals to build up complex tickets, or enable/disable specific items to specific users:
-
-Often, you will want to offer free items, such as t-shirts or dinner tickets to your attendees. Registrasion has a Discounts facility that lets you automatically offer free items to your attendees as part of their tickets. You can also automatically offer promotional discounts, such as Early Bird discounts.
-
-Sometimes, you may want to restrict parts of the conference to specific attendees, for example, you might have a Speakers Dinner to only speakers. Or you might want to restrict certain Products to attendees who have purchased other items, for example, you might want to sell Comfy Chairs to people who've bought VIP tickets. You can control showing and hiding specific products using Flags.
-
-
-.. automodule:: registrasion.models.inventory
-
-Categories
-----------
-
-Categories are logical groups of Products. Generally, you should keep like products in the same category, and use as many categories as you need.
-
-You will need at least one Category to be able to sell tickets to your attendees.
-
-Each category has the following attributes:
-
-.. autoclass :: Category
-
-
-Products
---------
-
-Products represent the different items that comprise a user's conference ticket.
-
-Each product has the following attributes:
-
-.. autoclass :: Product
-
-
-Vouchers
---------
-
-Vouchers are used to enable Discounts or Flags for people who enter a voucher
-code.
-
-.. autoclass :: Voucher
-
-If an attendee enters a voucher code, they have at least an hour to finalise
-their registration before the voucher becomes unreserved. Only as many people
-as allowed by ``limit`` are allowed to have a voucher reserved.
-
-
-.. automodule:: registrasion.models.conditions
-
-Discounts
----------
-
-Discounts serve multiple purposes: they can be used to build up complex tickets by automatically waiving the costs for sub-products; they can be used to offer freebie tickets to specific people, or people who hold voucher codes; or they can be used to enable short-term promotional discounts.
-
-Registrasion has several types of discounts, which enable themselves under specific conditions. We'll explain how these work later on, but first:
-
-Common features
-~~~~~~~~~~~~~~~
-Each discount type has the following common attributes:
-
-.. autoclass :: DiscountBase
-
-You can apply a discount to individual products, or to whole categories, or both. All of the products and categories affected by the discount are displayed on the discount's admin page.
-
-If you choose to specify individual products, you have these options:
-
-.. autoclass :: DiscountForProduct
-
-If you choose to specify whole categories, you have these options:
-
-.. autoclass :: DiscountForCategory
-
-Note that you cannot have a discount apply to both a category, and a product within that category.
-
-Product Inclusions
-~~~~~~~~~~~~~~~~~~
-Product inclusion discounts allow you to enable a discount when an attendee has selected a specific enabling Product.
-
-For example, if you want to give everyone with a ticket a free t-shirt, you can use a product inclusion to offer a 100% discount on the t-shirt category, if the attendee has selected one of your ticket Products.
-
-Once a discount has been enabled in one Invoice, it is available until the quantities are exhausted for that attendee.
-
-.. autoclass :: IncludedProductDiscount
-
-Time/stock limit discounts
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-These discounts allow you to offer a limited promotion that is automatically offered to all attendees. You can specify a time range for when the discount should be enabled, you can also specify a stock limit.
-
-.. autoclass :: TimeOrStockLimitDiscount
-
-Voucher discounts
-~~~~~~~~~~~~~~~~~
-Vouchers can be used to enable discounts.
-
-.. autoclass :: VoucherDiscount
-
-How discounts get applied
-~~~~~~~~~~~~~~~~~~~~~~~~~
-It's possible for multiple discounts to be available on any given Product. This means there need to be rules for how discounts get applied. It works like so:
-
-#. Take all of the Products that the user currently has selected, and sort them so that the most expensive comes first.
-#. Apply the highest-value discount line for the first Product, until either all such products have a discount applied, or the discount's Quantity has been exhausted for that user for that Product.
-#. Repeat until all products have been processed.
-
-In summary, the system greedily applies the highest-value discounts for each product. This may not provide a global optimum, but it'll do.
-
-As an example: say a user has a voucher available for a 100% discount of tickets, and there's a promotional discount for 15% off tickets. In this case, the 100% discount will apply, and the 15% discount will not be disturbed.
-
-
-Flags
------
-
-Flags are conditions that can be used to enable or disable Products or Categories, depending on whether conditions are met. They can be used to restrict specific products to specific people, or to place time limits on availability for products.
-
-Common Features
-~~~~~~~~~~~~~~~
-
-.. autoclass :: FlagBase
-
-
-Dependencies on products from category
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Category Dependency flags have their condition met if a product from the enabling category has been selected by the attendee. For example, if there is an *Accommodation* Category, this flag could be used to enable an *Accommodation Breakfast* category, allowing only attendees with accommodation to purchase breakfast.
-
-.. autoclass :: CategoryFlag
-
-
-Dependencies on products
-~~~~~~~~~~~~~~~~~~~~~~~~
-Product dependency flags have their condition met if one of the enabling products have been selected by the attendee.
-
-.. autoclass :: ProductFlag
-
-Time/stock limit flags
-~~~~~~~~~~~~~~~~~~~~~~
-These flags allow the products that they cover to be made available for a limited time, or to set a global ceiling on the products covered.
-
-These can be used to remove items from sale once a sales deadline has been met, or if a venue for a specific event has reached capacity.  If there are items that fall under multiple such groupings, it makes sense to set all of these flags to be ``DISABLE_IF_FALSE``.
-
-.. autoclass :: TimeOrStockLimitFlag
-
-If any of the attributes are omitted, then only the remaining attributes affect the availablility of the products covered. If there's no attributes set at all, then the grouping has no effect, but it can be used to group products for reporting purposes.
-
-Voucher flags
-~~~~~~~~~~~~~
-Vouchers can be used to enable flags.
-
-.. autoclass :: VoucherFlag
diff --git a/docs/registrasion/make.bat b/docs/registrasion/make.bat
deleted file mode 100644
index da9a651f..00000000
--- a/docs/registrasion/make.bat
+++ /dev/null
@@ -1,281 +0,0 @@
-@ECHO OFF
-
-REM Command file for Sphinx documentation
-
-if "%SPHINXBUILD%" == "" (
-	set SPHINXBUILD=sphinx-build
-)
-set BUILDDIR=_build
-set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
-set I18NSPHINXOPTS=%SPHINXOPTS% .
-if NOT "%PAPER%" == "" (
-	set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
-	set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
-)
-
-if "%1" == "" goto help
-
-if "%1" == "help" (
-	:help
-	echo.Please use `make ^<target^>` where ^<target^> is one of
-	echo.  html       to make standalone HTML files
-	echo.  dirhtml    to make HTML files named index.html in directories
-	echo.  singlehtml to make a single large HTML file
-	echo.  pickle     to make pickle files
-	echo.  json       to make JSON files
-	echo.  htmlhelp   to make HTML files and a HTML help project
-	echo.  qthelp     to make HTML files and a qthelp project
-	echo.  devhelp    to make HTML files and a Devhelp project
-	echo.  epub       to make an epub
-	echo.  epub3      to make an epub3
-	echo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter
-	echo.  text       to make text files
-	echo.  man        to make manual pages
-	echo.  texinfo    to make Texinfo files
-	echo.  gettext    to make PO message catalogs
-	echo.  changes    to make an overview over all changed/added/deprecated items
-	echo.  xml        to make Docutils-native XML files
-	echo.  pseudoxml  to make pseudoxml-XML files for display purposes
-	echo.  linkcheck  to check all external links for integrity
-	echo.  doctest    to run all doctests embedded in the documentation if enabled
-	echo.  coverage   to run coverage check of the documentation if enabled
-	echo.  dummy      to check syntax errors of document sources
-	goto end
-)
-
-if "%1" == "clean" (
-	for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
-	del /q /s %BUILDDIR%\*
-	goto end
-)
-
-
-REM Check if sphinx-build is available and fallback to Python version if any
-%SPHINXBUILD% 1>NUL 2>NUL
-if errorlevel 9009 goto sphinx_python
-goto sphinx_ok
-
-:sphinx_python
-
-set SPHINXBUILD=python -m sphinx.__init__
-%SPHINXBUILD% 2> nul
-if errorlevel 9009 (
-	echo.
-	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
-	echo.installed, then set the SPHINXBUILD environment variable to point
-	echo.to the full path of the 'sphinx-build' executable. Alternatively you
-	echo.may add the Sphinx directory to PATH.
-	echo.
-	echo.If you don't have Sphinx installed, grab it from
-	echo.http://sphinx-doc.org/
-	exit /b 1
-)
-
-:sphinx_ok
-
-
-if "%1" == "html" (
-	%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished. The HTML pages are in %BUILDDIR%/html.
-	goto end
-)
-
-if "%1" == "dirhtml" (
-	%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
-	goto end
-)
-
-if "%1" == "singlehtml" (
-	%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
-	goto end
-)
-
-if "%1" == "pickle" (
-	%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished; now you can process the pickle files.
-	goto end
-)
-
-if "%1" == "json" (
-	%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished; now you can process the JSON files.
-	goto end
-)
-
-if "%1" == "htmlhelp" (
-	%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished; now you can run HTML Help Workshop with the ^
-.hhp project file in %BUILDDIR%/htmlhelp.
-	goto end
-)
-
-if "%1" == "qthelp" (
-	%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished; now you can run "qcollectiongenerator" with the ^
-.qhcp project file in %BUILDDIR%/qthelp, like this:
-	echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Registrasion.qhcp
-	echo.To view the help file:
-	echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Registrasion.ghc
-	goto end
-)
-
-if "%1" == "devhelp" (
-	%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished.
-	goto end
-)
-
-if "%1" == "epub" (
-	%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished. The epub file is in %BUILDDIR%/epub.
-	goto end
-)
-
-if "%1" == "epub3" (
-	%SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished. The epub3 file is in %BUILDDIR%/epub3.
-	goto end
-)
-
-if "%1" == "latex" (
-	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
-	goto end
-)
-
-if "%1" == "latexpdf" (
-	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
-	cd %BUILDDIR%/latex
-	make all-pdf
-	cd %~dp0
-	echo.
-	echo.Build finished; the PDF files are in %BUILDDIR%/latex.
-	goto end
-)
-
-if "%1" == "latexpdfja" (
-	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
-	cd %BUILDDIR%/latex
-	make all-pdf-ja
-	cd %~dp0
-	echo.
-	echo.Build finished; the PDF files are in %BUILDDIR%/latex.
-	goto end
-)
-
-if "%1" == "text" (
-	%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished. The text files are in %BUILDDIR%/text.
-	goto end
-)
-
-if "%1" == "man" (
-	%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished. The manual pages are in %BUILDDIR%/man.
-	goto end
-)
-
-if "%1" == "texinfo" (
-	%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
-	goto end
-)
-
-if "%1" == "gettext" (
-	%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
-	goto end
-)
-
-if "%1" == "changes" (
-	%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.The overview file is in %BUILDDIR%/changes.
-	goto end
-)
-
-if "%1" == "linkcheck" (
-	%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Link check complete; look for any errors in the above output ^
-or in %BUILDDIR%/linkcheck/output.txt.
-	goto end
-)
-
-if "%1" == "doctest" (
-	%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Testing of doctests in the sources finished, look at the ^
-results in %BUILDDIR%/doctest/output.txt.
-	goto end
-)
-
-if "%1" == "coverage" (
-	%SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Testing of coverage in the sources finished, look at the ^
-results in %BUILDDIR%/coverage/python.txt.
-	goto end
-)
-
-if "%1" == "xml" (
-	%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished. The XML files are in %BUILDDIR%/xml.
-	goto end
-)
-
-if "%1" == "pseudoxml" (
-	%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
-	goto end
-)
-
-if "%1" == "dummy" (
-	%SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy
-	if errorlevel 1 exit /b 1
-	echo.
-	echo.Build finished. Dummy builder generates no files.
-	goto end
-)
-
-:end
diff --git a/docs/registrasion/overview.rst b/docs/registrasion/overview.rst
deleted file mode 100644
index 5f6b25fa..00000000
--- a/docs/registrasion/overview.rst
+++ /dev/null
@@ -1,51 +0,0 @@
-Overview
-========
-
-Registrasion's approach to handling conference registrations is to use a cart and inventory model, where the various things sold by the conference to attendees are handled as Products, which can be added to a Cart. Carts can be used to generate Invoices, and Invoices can then be paid.
-
-
-Guided registration
--------------------
-
-Unlike a generic e-commerce platform, Registrasion is designed for building up conference tickets.
-
-When they first attempt registration, attendees start off in a process called *guided mode*. Guided mode is multi-step form that takes users through a complete registration process:
-
-#. The attendee fills out their profile
-#. The attendee selects a ticket type
-#. The attendee selects additional products such as t-shirts and dinner tickets, which may be sold at a cost, or have waivers applied.
-#. The attendee is offered the opportunity to check out their cart, generating an invoice; or to enter amendments mode.
-
-For specifics on how guided mode works, see *code guide to be written*.
-
-
-Amendments mode
----------------
-
-Once attendees have reached the end of guided registration, they are permanently added to *amendments mode*. Amendments mode allows attendees to change their product selections in a given category, with one rule: once an invoice has been paid, product selections cannot be changed without voiding that invoice (and optionally releasing a Credit Note).
-
-Users can check out their current selections at any time, and generate an Invoice for their selections. That invoice can then be paid, and the attendee will then be making selections on a new cart.
-
-
-Invoices
---------
-
-When an attendee checks out their Cart, an Invoice is generated for their cart.
-
-An invoice is valid for as long as the items in the cart do not change, and remain generally available. If a user amends their cart after generating an invoice, the user will need to check out their cart again, and generate a new invoice.
-
-Once an invoice is paid, it is no longer possible for an invoice to be void, instead, it needs to have a refund generated.
-
-
-User-Attendee Model
--------------------
-
-Registrasion uses a User-Attendee model. This means that Registrasion expects each user account on the system to represent a single attendee at the conference. It also expects that the attendee themselves fill out the registration form.
-
-This means that each attendee has a confirmed e-mail address for conference-related communications. It's usually a good idea for the conference to make sure that their account sign-up page points this out, so that administrative assistants at companies don't end up being the ones getting communicated at.
-
-How do people get their employers to pay for their tickets?
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Registrasion provides a semi-private URL that allows anyone in possession of this URL to view that attendee's most recent invoice, and make payments against that invoice.
-
-A future release will add the ability to bulk-pay multiple invoices at once.
diff --git a/docs/registrasion/payments.rst b/docs/registrasion/payments.rst
deleted file mode 100644
index d0105afb..00000000
--- a/docs/registrasion/payments.rst
+++ /dev/null
@@ -1,165 +0,0 @@
-.. automodule:: registrasion.models.commerce
-.. _payments_and_refunds:
-
-Payments and Refunds
-====================
-
-Registrasion aims to support whatever payment platform you have available to use. Therefore, Registrasion uses a bare minimum payments model to track money within the system. It's the role of you, as a deployer of Registrasion, to implement a payment application that communicates with your own payment platform.
-
-Invoices may have multiple ``PaymentBase`` objects attached to them; each of these represent a payment against the invoice. Payments can be negative (and this represents a refund against the Invoice), however, this approach is not recommended for use by implementers.
-
-Registrasion also keeps track of money that is not currently attached to invoices through `credit notes`_. Credit notes may be applied to unpaid invoices *in full*, if there is an amount left over from the credit note, a new credit note will be created from that amount. Credit notes may also be released, at which point they're the responsibility of the payment application to create a refund.
-
-Finally, Registrasion provides a `manual payments`_ feature, which allows for staff members to manually report payments into the system. This is the only payment facility built into Registrasion, but it's not intended as a reference implementation.
-
-
-Invoice and payment access control
-----------------------------------
-
-Conferences are interesting: usually you want attendees to fill in their own registration so that they get their catering options right, so that they can personally agree to codes of conduct, and so that you can make sure that you're communicating key information directly with them.
-
-On the other hand, employees at companies often need for their employers to directly pay for their registration.
-
-Registrasion solves this problem by having attendees complete their own registration, and then providing an access URL that allows anyone who holds that URL to view their invoice and make payment.
-
-You can call ``InvoiceController.can_view`` to determine whether or not you're allowed to show the invoice. It returns true if the user is allowed to view the invoice::
-
-    InvoiceController.can_view(self, user=request.user, access_code="CODE")
-
-As a rule, you should call ``can_view`` before doing any operations that amend the status of an invoice. This includes taking payments or requesting refunds.
-
-The access code is unique for each attendee -- this means that every invoice that an attendee generates can be viewed with the same access code. This is useful if the user amends their registration between giving the URL to their employer, and their employer making payment.
-
-
-
-
-Making payments
----------------
-
-Making payments is a three-step process:
-
-#. Validate that the invoice is ready to be paid,
-#. Create a payment object for the amount that you are paying towards an invoice,
-#. Ask the invoice to calculate its status now that the payment has been made.
-
-Pre-validation
-~~~~~~~~~~~~~~
-Registrasion's ``InvoiceController`` has a ``validate_allowed_to_pay`` method, which performs all of the pre-payment checks (is the invoice still unpaid and non-void? has the registration been amended?).
-
-If the pre-payment check fails, ``InvoiceController`` will raise a Django ``ValidationError``.
-
-Our the ``demopay`` view from the ``registrasion-demo`` project implements pre-validation like so::
-
-    from registrasion.controllers.invoice import InvoiceController
-    from django.core.exceptions import ValidationError
-
-    invoice = InvoiceController.for_id_or_404(invoice.id)
-
-    try:
-        invoice.validate_allowed_to_pay()  # Verify that we're allowed to do this.
-    except ValidationError as ve:
-        messages.error(request, ve.message)
-        return REDIRECT_TO_INVOICE  # And display the validation message.
-
-In most cases, you don't engage your actual payment application until after pre-validation is finished, as this gives you an opportunity to bail out if the invoice isn't able to have funds applied to it.
-
-Applying payments
-~~~~~~~~~~~~~~~~~
-Payments in Registrasion are represented as subclasses of the ``PaymentBase`` model. ``PaymentBase`` looks like this:
-
-.. autoclass :: PaymentBase
-
-When you implement your own payment application, you'll need to subclass ``PaymentBase``, as this will allow you to add metadata that lets you link the Registrasion payment object with your payment platform's object.
-
-Generally, the ``reference`` field should be something that lets your end-users identify the payment on their credit card statement, and to provide to your team's tech support in case something goes wrong.
-
-Once you've subclassed ``PaymentBase``, applying a payment is really quite simple. In the ``demopay`` view, we have a subclass called ``DemoPayment``::
-
-    invoice = InvoiceController(some_invoice_model)
-
-    # Create the payment object
-    models.DemoPayment.objects.create(
-        invoice=invoice.invoice,
-        reference="Demo payment by user: " + request.user.username,
-        amount=invoice.invoice.value,
-    )
-
-Note that multiple payments can be provided against an ``Invoice``, however, payments that exceed the total value of the invoice will have credit notes generated.
-
-Updating an invoice's status
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-``InvoiceController`` has a method called ``update_status``. You should call ``update_status`` immediately after you create a ``PaymentBase`` object, as this keeps invoice and its payments synchronised::
-
-    invoice = InvoiceController(some_invoice_model)
-    invoice.update_status()
-
-Calling ``update_status`` collects the ``PaymentBase`` objects that are attached to the ``Invoice``, and will update the status of the invoice:
-
-* If an invoice is ``VOID``, it will remain void.
-* If an invoice is ``UNPAID`` and it now has ``PaymentBase`` objects whose value meets or exceed's the invoice's value, the invoice becomes ``PAID``.
-* If an invoice is ``UNPAID`` and it now has ``PaymentBase`` objects whose values sum to zero, the invoice becomes ``VOID``.
-* If an invoice is ``PAID`` and it now has ``PaymentBase`` objects of less than the invoice's value, the invoice becomes ``REFUNDED``.
-
-When your invoice becomes ``PAID`` for the first time, if there's a cart of inventory items attached to it, that cart becomes permanently reserved -- that is, all of the items within it are no longer available for other users to purchase. If an invoice becomes ``REFUNDED``, the items in the cart are released, which means that they are available for anyone to purchase again.
-
-If you overpay an invoice, or pay into an invoice that should not have funds attached, a credit note for the residual payments will also be issued.
-
-In general, although this means you *can* use negative payments to take an invoice into a *REFUNDED* state, it's still much more sensible to use the credit notes facility, as this makes sure that any leftover funds remain tracked in the system.
-
-
-Credit Notes
-------------
-
-When you refund an invoice, often you're doing so in order to make a minor amendment to items that the attendee has purchased. In order to make it easy to transfer funds from a refunded invoice to a new invoice, Registrasion provides an automatic credit note facility.
-
-Credit notes are created when you mark an invoice as refunded, but they're also created if you overpay an invoice, or if you direct money into an invoice that can no longer take payment.
-
-Once created, Credit Notes act as a payment that can be put towards other invoices, or that can be cashed out, back to the original payment platform. Credits can only be applied or cashed out in full.
-
-This means that it's easy to track funds that aren't accounted for by invoices -- it's just the sum of the credit notes that haven't been applied to new invoices, or haven't been cashed out.
-
-We recommend using credit notes to track all of your refunds for consistency; it also allows you to invoice for cancellation fees and the like.
-
-Creating credit notes
-~~~~~~~~~~~~~~~~~~~~~
-In Registrasion, credit notes originate against invoices, and are represented as negative payments to an invoice.
-
-Credit notes are usually created automatically. In most cases, Credit Notes come about from asking to refund an invoice::
-
-    InvoiceController(invoice).refund()
-
-Calling ``refund()`` will generate a refund of all of the payments applied to that invoice.
-
-Otherwise, credit notes come about when invoices are overpaid, in this case, a credit for the overpay amount will be generated.
-
-Applying credits to new invoices
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Credits can be applied to invoices::
-
-    CreditNoteController(credit_not).apply_to_invoice(invoice)
-
-This will result in an instance of ``CreditNoteApplication`` being applied as a payment to ``invoice``. ``CreditNoteApplication`` will always be a payment of the full amount of its parent credit note. If this payment overpays the invoice it's being applied to, a credit note for the residual will be generated.
-
-Refunding credit notes
-~~~~~~~~~~~~~~~~~~~~~~
-It is possible to release a credit note back to the original payment platform. To do so, you attach an instance of ``CreditNoteRefund`` to the original ``CreditNote``:
-
-.. autoclass :: CreditNoteRefund
-
-You'll usually want to make a subclass of ``CreditNoteRefund`` for your own purposes, usually so that you can tie Registrasion's internal representation of the refund to a concrete refund on the side of your payment platform.
-
-Note that you can only release a credit back to the payment platform for the full amount of the credit.
-
-
-Manual payments
----------------
-
-Registrasion provides a *manual payments* feature, which allows for staff members to manually report payments into the system. This is the only payment facility built into Registrasion, but it's not intended as a reference implementation.
-
-The main use case for manual payments is to record the receipt of funds from bank transfers or cheques sent on behalf of attendees.
-
-It's not intended as a reference implementation is because it does not perform validation of the cart before the payment is applied to the invoice.
-
-This means that it's possible for a staff member to apply a payment with a specific invoice reference into the invoice matching that reference. Registrasion will generate a credit note if the invoice is not able to receive payment (e.g. because it has since been voided), you can use that credit note to pay into a valid invoice if necessary.
-
-It is possible for staff to enter a negative amount on a manual payment. This will be treated as a refund. Generally, it's preferred to issue a credit note to an invoice rather than enter a negative amount manually.
diff --git a/docs/registrasion/views.rst b/docs/registrasion/views.rst
deleted file mode 100644
index 313337ec..00000000
--- a/docs/registrasion/views.rst
+++ /dev/null
@@ -1,41 +0,0 @@
-User-facing views
-=================
-
-
-View functions
---------------
-
-Here's all of the views that Registrasion exposes to the public.
-
-.. automodule:: registrasion.views
-    :members:
-
-Data types
-~~~~~~~~~~
-
-.. automodule:: registrasion.controllers.discount
-
-.. autoclass:: DiscountAndQuantity
-
-
-Template tags
--------------
-
-Registrasion makes template tags available:
-
-.. automodule:: registrasion.templatetags.registrasion_tags
-    :members:
-
-
-Rendering invoices
-------------------
-
-You'll need to render the following Django models in order to view invoices.
-
-.. automodule:: registrasion.models.commerce
-
-.. autoclass:: Invoice
-
-.. autoclass:: LineItem
-
-See also: :class:`PaymentBase`

From 8d84438543dc80114731e34d74f4f11690378517 Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sun, 1 Oct 2017 12:01:09 +1100
Subject: [PATCH 29/40] Remove antiquated compiled js

afaict this is no longer in use anywhere
---
 static/src/js/site-92ae8d0d6c.js | 38 --------------------------------
 1 file changed, 38 deletions(-)
 delete mode 100644 static/src/js/site-92ae8d0d6c.js

diff --git a/static/src/js/site-92ae8d0d6c.js b/static/src/js/site-92ae8d0d6c.js
deleted file mode 100644
index 9ae21479..00000000
--- a/static/src/js/site-92ae8d0d6c.js
+++ /dev/null
@@ -1,38 +0,0 @@
-!function t(e,n,i){function o(s,a){if(!n[s]){if(!e[s]){var l="function"==typeof require&&require;if(!a&&l)return l(s,!0);if(r)return r(s,!0);var u=new Error("Cannot find module '"+s+"'");throw u.code="MODULE_NOT_FOUND",u}var c=n[s]={exports:{}};e[s][0].call(c.exports,function(t){var n=e[s][1][t];return o(n?n:t)},c,c.exports,t,e,n,i)}return n[s].exports}for(var r="function"==typeof require&&require,s=0;s<i.length;s++)o(i[s]);return o}({1:[function(t,e,n){t("../../js/transition.js"),t("../../js/alert.js"),t("../../js/button.js"),t("../../js/carousel.js"),t("../../js/collapse.js"),t("../../js/dropdown.js"),t("../../js/modal.js"),t("../../js/tooltip.js"),t("../../js/popover.js"),t("../../js/scrollspy.js"),t("../../js/tab.js"),t("../../js/affix.js")},{"../../js/affix.js":2,"../../js/alert.js":3,"../../js/button.js":4,"../../js/carousel.js":5,"../../js/collapse.js":6,"../../js/dropdown.js":7,"../../js/modal.js":8,"../../js/popover.js":9,"../../js/scrollspy.js":10,"../../js/tab.js":11,"../../js/tooltip.js":12,"../../js/transition.js":13}],2:[function(t,e,n){+function(t){"use strict";function e(e){return this.each(function(){var i=t(this),o=i.data("bs.affix"),r="object"==typeof e&&e;o||i.data("bs.affix",o=new n(this,r)),"string"==typeof e&&o[e]()})}var n=function(e,i){this.options=t.extend({},n.DEFAULTS,i),this.$target=t(this.options.target).on("scroll.bs.affix.data-api",t.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",t.proxy(this.checkPositionWithEventLoop,this)),this.$element=t(e),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};n.VERSION="3.3.6",n.RESET="affix affix-top affix-bottom",n.DEFAULTS={offset:0,target:window},n.prototype.getState=function(t,e,n,i){var o=this.$target.scrollTop(),r=this.$element.offset(),s=this.$target.height();if(null!=n&&"top"==this.affixed)return n>o?"top":!1;if("bottom"==this.affixed)return null!=n?o+this.unpin<=r.top?!1:"bottom":t-i>=o+s?!1:"bottom";var a=null==this.affixed,l=a?o:r.top,u=a?s:e;return null!=n&&n>=o?"top":null!=i&&l+u>=t-i?"bottom":!1},n.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(n.RESET).addClass("affix");var t=this.$target.scrollTop(),e=this.$element.offset();return this.pinnedOffset=e.top-t},n.prototype.checkPositionWithEventLoop=function(){setTimeout(t.proxy(this.checkPosition,this),1)},n.prototype.checkPosition=function(){if(this.$element.is(":visible")){var e=this.$element.height(),i=this.options.offset,o=i.top,r=i.bottom,s=Math.max(t(document).height(),t(document.body).height());"object"!=typeof i&&(r=o=i),"function"==typeof o&&(o=i.top(this.$element)),"function"==typeof r&&(r=i.bottom(this.$element));var a=this.getState(s,e,o,r);if(this.affixed!=a){null!=this.unpin&&this.$element.css("top","");var l="affix"+(a?"-"+a:""),u=t.Event(l+".bs.affix");if(this.$element.trigger(u),u.isDefaultPrevented())return;this.affixed=a,this.unpin="bottom"==a?this.getPinnedOffset():null,this.$element.removeClass(n.RESET).addClass(l).trigger(l.replace("affix","affixed")+".bs.affix")}"bottom"==a&&this.$element.offset({top:s-e-r})}};var i=t.fn.affix;t.fn.affix=e,t.fn.affix.Constructor=n,t.fn.affix.noConflict=function(){return t.fn.affix=i,this},t(window).on("load",function(){t('[data-spy="affix"]').each(function(){var n=t(this),i=n.data();i.offset=i.offset||{},null!=i.offsetBottom&&(i.offset.bottom=i.offsetBottom),null!=i.offsetTop&&(i.offset.top=i.offsetTop),e.call(n,i)})})}(jQuery)},{}],3:[function(t,e,n){+function(t){"use strict";function e(e){return this.each(function(){var n=t(this),o=n.data("bs.alert");o||n.data("bs.alert",o=new i(this)),"string"==typeof e&&o[e].call(n)})}var n='[data-dismiss="alert"]',i=function(e){t(e).on("click",n,this.close)};i.VERSION="3.3.6",i.TRANSITION_DURATION=150,i.prototype.close=function(e){function n(){s.detach().trigger("closed.bs.alert").remove()}var o=t(this),r=o.attr("data-target");r||(r=o.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,""));var s=t(r);e&&e.preventDefault(),s.length||(s=o.closest(".alert")),s.trigger(e=t.Event("close.bs.alert")),e.isDefaultPrevented()||(s.removeClass("in"),t.support.transition&&s.hasClass("fade")?s.one("bsTransitionEnd",n).emulateTransitionEnd(i.TRANSITION_DURATION):n())};var o=t.fn.alert;t.fn.alert=e,t.fn.alert.Constructor=i,t.fn.alert.noConflict=function(){return t.fn.alert=o,this},t(document).on("click.bs.alert.data-api",n,i.prototype.close)}(jQuery)},{}],4:[function(t,e,n){+function(t){"use strict";function e(e){return this.each(function(){var i=t(this),o=i.data("bs.button"),r="object"==typeof e&&e;o||i.data("bs.button",o=new n(this,r)),"toggle"==e?o.toggle():e&&o.setState(e)})}var n=function(e,i){this.$element=t(e),this.options=t.extend({},n.DEFAULTS,i),this.isLoading=!1};n.VERSION="3.3.6",n.DEFAULTS={loadingText:"loading..."},n.prototype.setState=function(e){var n="disabled",i=this.$element,o=i.is("input")?"val":"html",r=i.data();e+="Text",null==r.resetText&&i.data("resetText",i[o]()),setTimeout(t.proxy(function(){i[o](null==r[e]?this.options[e]:r[e]),"loadingText"==e?(this.isLoading=!0,i.addClass(n).attr(n,n)):this.isLoading&&(this.isLoading=!1,i.removeClass(n).removeAttr(n))},this),0)},n.prototype.toggle=function(){var t=!0,e=this.$element.closest('[data-toggle="buttons"]');if(e.length){var n=this.$element.find("input");"radio"==n.prop("type")?(n.prop("checked")&&(t=!1),e.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==n.prop("type")&&(n.prop("checked")!==this.$element.hasClass("active")&&(t=!1),this.$element.toggleClass("active")),n.prop("checked",this.$element.hasClass("active")),t&&n.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var i=t.fn.button;t.fn.button=e,t.fn.button.Constructor=n,t.fn.button.noConflict=function(){return t.fn.button=i,this},t(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(n){var i=t(n.target);i.hasClass("btn")||(i=i.closest(".btn")),e.call(i,"toggle"),t(n.target).is('input[type="radio"]')||t(n.target).is('input[type="checkbox"]')||n.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(e){t(e.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(e.type))})}(jQuery)},{}],5:[function(t,e,n){+function(t){"use strict";function e(e){return this.each(function(){var i=t(this),o=i.data("bs.carousel"),r=t.extend({},n.DEFAULTS,i.data(),"object"==typeof e&&e),s="string"==typeof e?e:r.slide;o||i.data("bs.carousel",o=new n(this,r)),"number"==typeof e?o.to(e):s?o[s]():r.interval&&o.pause().cycle()})}var n=function(e,n){this.$element=t(e),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",t.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",t.proxy(this.pause,this)).on("mouseleave.bs.carousel",t.proxy(this.cycle,this))};n.VERSION="3.3.6",n.TRANSITION_DURATION=600,n.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},n.prototype.keydown=function(t){if(!/input|textarea/i.test(t.target.tagName)){switch(t.which){case 37:this.prev();break;case 39:this.next();break;default:return}t.preventDefault()}},n.prototype.cycle=function(e){return e||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(t.proxy(this.next,this),this.options.interval)),this},n.prototype.getItemIndex=function(t){return this.$items=t.parent().children(".item"),this.$items.index(t||this.$active)},n.prototype.getItemForDirection=function(t,e){var n=this.getItemIndex(e),i="prev"==t&&0===n||"next"==t&&n==this.$items.length-1;if(i&&!this.options.wrap)return e;var o="prev"==t?-1:1,r=(n+o)%this.$items.length;return this.$items.eq(r)},n.prototype.to=function(t){var e=this,n=this.getItemIndex(this.$active=this.$element.find(".item.active"));return t>this.$items.length-1||0>t?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){e.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",this.$items.eq(t))},n.prototype.pause=function(e){return e||(this.paused=!0),this.$element.find(".next, .prev").length&&t.support.transition&&(this.$element.trigger(t.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},n.prototype.next=function(){return this.sliding?void 0:this.slide("next")},n.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},n.prototype.slide=function(e,i){var o=this.$element.find(".item.active"),r=i||this.getItemForDirection(e,o),s=this.interval,a="next"==e?"left":"right",l=this;if(r.hasClass("active"))return this.sliding=!1;var u=r[0],c=t.Event("slide.bs.carousel",{relatedTarget:u,direction:a});if(this.$element.trigger(c),!c.isDefaultPrevented()){if(this.sliding=!0,s&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var f=t(this.$indicators.children()[this.getItemIndex(r)]);f&&f.addClass("active")}var p=t.Event("slid.bs.carousel",{relatedTarget:u,direction:a});return t.support.transition&&this.$element.hasClass("slide")?(r.addClass(e),r[0].offsetWidth,o.addClass(a),r.addClass(a),o.one("bsTransitionEnd",function(){r.removeClass([e,a].join(" ")).addClass("active"),o.removeClass(["active",a].join(" ")),l.sliding=!1,setTimeout(function(){l.$element.trigger(p)},0)}).emulateTransitionEnd(n.TRANSITION_DURATION)):(o.removeClass("active"),r.addClass("active"),this.sliding=!1,this.$element.trigger(p)),s&&this.cycle(),this}};var i=t.fn.carousel;t.fn.carousel=e,t.fn.carousel.Constructor=n,t.fn.carousel.noConflict=function(){return t.fn.carousel=i,this};var o=function(n){var i,o=t(this),r=t(o.attr("data-target")||(i=o.attr("href"))&&i.replace(/.*(?=#[^\s]+$)/,""));if(r.hasClass("carousel")){var s=t.extend({},r.data(),o.data()),a=o.attr("data-slide-to");a&&(s.interval=!1),e.call(r,s),a&&r.data("bs.carousel").to(a),n.preventDefault()}};t(document).on("click.bs.carousel.data-api","[data-slide]",o).on("click.bs.carousel.data-api","[data-slide-to]",o),t(window).on("load",function(){t('[data-ride="carousel"]').each(function(){var n=t(this);e.call(n,n.data())})})}(jQuery)},{}],6:[function(t,e,n){+function(t){"use strict";function e(e){var n,i=e.attr("data-target")||(n=e.attr("href"))&&n.replace(/.*(?=#[^\s]+$)/,"");return t(i)}function n(e){return this.each(function(){var n=t(this),o=n.data("bs.collapse"),r=t.extend({},i.DEFAULTS,n.data(),"object"==typeof e&&e);!o&&r.toggle&&/show|hide/.test(e)&&(r.toggle=!1),o||n.data("bs.collapse",o=new i(this,r)),"string"==typeof e&&o[e]()})}var i=function(e,n){this.$element=t(e),this.options=t.extend({},i.DEFAULTS,n),this.$trigger=t('[data-toggle="collapse"][href="#'+e.id+'"],[data-toggle="collapse"][data-target="#'+e.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};i.VERSION="3.3.6",i.TRANSITION_DURATION=350,i.DEFAULTS={toggle:!0},i.prototype.dimension=function(){var t=this.$element.hasClass("width");return t?"width":"height"},i.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var e,o=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(o&&o.length&&(e=o.data("bs.collapse"),e&&e.transitioning))){var r=t.Event("show.bs.collapse");if(this.$element.trigger(r),!r.isDefaultPrevented()){o&&o.length&&(n.call(o,"hide"),e||o.data("bs.collapse",null));var s=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[s](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var a=function(){this.$element.removeClass("collapsing").addClass("collapse in")[s](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!t.support.transition)return a.call(this);var l=t.camelCase(["scroll",s].join("-"));this.$element.one("bsTransitionEnd",t.proxy(a,this)).emulateTransitionEnd(i.TRANSITION_DURATION)[s](this.$element[0][l])}}}},i.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var e=t.Event("hide.bs.collapse");if(this.$element.trigger(e),!e.isDefaultPrevented()){var n=this.dimension();this.$element[n](this.$element[n]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var o=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return t.support.transition?void this.$element[n](0).one("bsTransitionEnd",t.proxy(o,this)).emulateTransitionEnd(i.TRANSITION_DURATION):o.call(this)}}},i.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},i.prototype.getParent=function(){return t(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(t.proxy(function(n,i){var o=t(i);this.addAriaAndCollapsedClass(e(o),o)},this)).end()},i.prototype.addAriaAndCollapsedClass=function(t,e){var n=t.hasClass("in");t.attr("aria-expanded",n),e.toggleClass("collapsed",!n).attr("aria-expanded",n)};var o=t.fn.collapse;t.fn.collapse=n,t.fn.collapse.Constructor=i,t.fn.collapse.noConflict=function(){return t.fn.collapse=o,this},t(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(i){var o=t(this);o.attr("data-target")||i.preventDefault();var r=e(o),s=r.data("bs.collapse"),a=s?"toggle":o.data();n.call(r,a)})}(jQuery)},{}],7:[function(t,e,n){+function(t){"use strict";function e(e){var n=e.attr("data-target");n||(n=e.attr("href"),n=n&&/#[A-Za-z]/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,""));var i=n&&t(n);return i&&i.length?i:e.parent()}function n(n){n&&3===n.which||(t(o).remove(),t(r).each(function(){var i=t(this),o=e(i),r={relatedTarget:this};o.hasClass("open")&&(n&&"click"==n.type&&/input|textarea/i.test(n.target.tagName)&&t.contains(o[0],n.target)||(o.trigger(n=t.Event("hide.bs.dropdown",r)),n.isDefaultPrevented()||(i.attr("aria-expanded","false"),o.removeClass("open").trigger(t.Event("hidden.bs.dropdown",r)))))}))}function i(e){return this.each(function(){var n=t(this),i=n.data("bs.dropdown");i||n.data("bs.dropdown",i=new s(this)),"string"==typeof e&&i[e].call(n)})}var o=".dropdown-backdrop",r='[data-toggle="dropdown"]',s=function(e){t(e).on("click.bs.dropdown",this.toggle)};s.VERSION="3.3.6",s.prototype.toggle=function(i){var o=t(this);if(!o.is(".disabled, :disabled")){var r=e(o),s=r.hasClass("open");if(n(),!s){"ontouchstart"in document.documentElement&&!r.closest(".navbar-nav").length&&t(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(t(this)).on("click",n);var a={relatedTarget:this};if(r.trigger(i=t.Event("show.bs.dropdown",a)),i.isDefaultPrevented())return;o.trigger("focus").attr("aria-expanded","true"),r.toggleClass("open").trigger(t.Event("shown.bs.dropdown",a))}return!1}},s.prototype.keydown=function(n){if(/(38|40|27|32)/.test(n.which)&&!/input|textarea/i.test(n.target.tagName)){var i=t(this);if(n.preventDefault(),n.stopPropagation(),!i.is(".disabled, :disabled")){var o=e(i),s=o.hasClass("open");if(!s&&27!=n.which||s&&27==n.which)return 27==n.which&&o.find(r).trigger("focus"),i.trigger("click");var a=" li:not(.disabled):visible a",l=o.find(".dropdown-menu"+a);if(l.length){var u=l.index(n.target);38==n.which&&u>0&&u--,40==n.which&&u<l.length-1&&u++,~u||(u=0),l.eq(u).trigger("focus")}}}};var a=t.fn.dropdown;t.fn.dropdown=i,t.fn.dropdown.Constructor=s,t.fn.dropdown.noConflict=function(){return t.fn.dropdown=a,this},t(document).on("click.bs.dropdown.data-api",n).on("click.bs.dropdown.data-api",".dropdown form",function(t){t.stopPropagation()}).on("click.bs.dropdown.data-api",r,s.prototype.toggle).on("keydown.bs.dropdown.data-api",r,s.prototype.keydown).on("keydown.bs.dropdown.data-api",".dropdown-menu",s.prototype.keydown)}(jQuery)},{}],8:[function(t,e,n){+function(t){"use strict";function e(e,i){return this.each(function(){var o=t(this),r=o.data("bs.modal"),s=t.extend({},n.DEFAULTS,o.data(),"object"==typeof e&&e);r||o.data("bs.modal",r=new n(this,s)),"string"==typeof e?r[e](i):s.show&&r.show(i)})}var n=function(e,n){this.options=n,this.$body=t(document.body),this.$element=t(e),this.$dialog=this.$element.find(".modal-dialog"),this.$backdrop=null,this.isShown=null,this.originalBodyPad=null,this.scrollbarWidth=0,this.ignoreBackdropClick=!1,this.options.remote&&this.$element.find(".modal-content").load(this.options.remote,t.proxy(function(){this.$element.trigger("loaded.bs.modal")},this))};n.VERSION="3.3.6",n.TRANSITION_DURATION=300,n.BACKDROP_TRANSITION_DURATION=150,n.DEFAULTS={backdrop:!0,keyboard:!0,show:!0},n.prototype.toggle=function(t){return this.isShown?this.hide():this.show(t)},n.prototype.show=function(e){var i=this,o=t.Event("show.bs.modal",{relatedTarget:e});this.$element.trigger(o),this.isShown||o.isDefaultPrevented()||(this.isShown=!0,this.checkScrollbar(),this.setScrollbar(),this.$body.addClass("modal-open"),this.escape(),this.resize(),this.$element.on("click.dismiss.bs.modal",'[data-dismiss="modal"]',t.proxy(this.hide,this)),this.$dialog.on("mousedown.dismiss.bs.modal",function(){i.$element.one("mouseup.dismiss.bs.modal",function(e){t(e.target).is(i.$element)&&(i.ignoreBackdropClick=!0)})}),this.backdrop(function(){var o=t.support.transition&&i.$element.hasClass("fade");i.$element.parent().length||i.$element.appendTo(i.$body),i.$element.show().scrollTop(0),i.adjustDialog(),o&&i.$element[0].offsetWidth,i.$element.addClass("in"),i.enforceFocus();var r=t.Event("shown.bs.modal",{relatedTarget:e});o?i.$dialog.one("bsTransitionEnd",function(){i.$element.trigger("focus").trigger(r)}).emulateTransitionEnd(n.TRANSITION_DURATION):i.$element.trigger("focus").trigger(r)}))},n.prototype.hide=function(e){e&&e.preventDefault(),e=t.Event("hide.bs.modal"),this.$element.trigger(e),this.isShown&&!e.isDefaultPrevented()&&(this.isShown=!1,this.escape(),this.resize(),t(document).off("focusin.bs.modal"),this.$element.removeClass("in").off("click.dismiss.bs.modal").off("mouseup.dismiss.bs.modal"),this.$dialog.off("mousedown.dismiss.bs.modal"),t.support.transition&&this.$element.hasClass("fade")?this.$element.one("bsTransitionEnd",t.proxy(this.hideModal,this)).emulateTransitionEnd(n.TRANSITION_DURATION):this.hideModal())},n.prototype.enforceFocus=function(){t(document).off("focusin.bs.modal").on("focusin.bs.modal",t.proxy(function(t){this.$element[0]===t.target||this.$element.has(t.target).length||this.$element.trigger("focus")},this))},n.prototype.escape=function(){this.isShown&&this.options.keyboard?this.$element.on("keydown.dismiss.bs.modal",t.proxy(function(t){27==t.which&&this.hide()},this)):this.isShown||this.$element.off("keydown.dismiss.bs.modal")},n.prototype.resize=function(){this.isShown?t(window).on("resize.bs.modal",t.proxy(this.handleUpdate,this)):t(window).off("resize.bs.modal")},n.prototype.hideModal=function(){var t=this;this.$element.hide(),this.backdrop(function(){t.$body.removeClass("modal-open"),t.resetAdjustments(),t.resetScrollbar(),t.$element.trigger("hidden.bs.modal")})},n.prototype.removeBackdrop=function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},n.prototype.backdrop=function(e){var i=this,o=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var r=t.support.transition&&o;if(this.$backdrop=t(document.createElement("div")).addClass("modal-backdrop "+o).appendTo(this.$body),this.$element.on("click.dismiss.bs.modal",t.proxy(function(t){return this.ignoreBackdropClick?void(this.ignoreBackdropClick=!1):void(t.target===t.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus():this.hide()))},this)),r&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!e)return;r?this.$backdrop.one("bsTransitionEnd",e).emulateTransitionEnd(n.BACKDROP_TRANSITION_DURATION):e()}else if(!this.isShown&&this.$backdrop){this.$backdrop.removeClass("in");var s=function(){i.removeBackdrop(),e&&e()};t.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one("bsTransitionEnd",s).emulateTransitionEnd(n.BACKDROP_TRANSITION_DURATION):s()}else e&&e()},n.prototype.handleUpdate=function(){this.adjustDialog()},n.prototype.adjustDialog=function(){var t=this.$element[0].scrollHeight>document.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&t?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!t?this.scrollbarWidth:""})},n.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},n.prototype.checkScrollbar=function(){var t=window.innerWidth;if(!t){var e=document.documentElement.getBoundingClientRect();t=e.right-Math.abs(e.left)}this.bodyIsOverflowing=document.body.clientWidth<t,this.scrollbarWidth=this.measureScrollbar()},n.prototype.setScrollbar=function(){var t=parseInt(this.$body.css("padding-right")||0,10);this.originalBodyPad=document.body.style.paddingRight||"",this.bodyIsOverflowing&&this.$body.css("padding-right",t+this.scrollbarWidth)},n.prototype.resetScrollbar=function(){this.$body.css("padding-right",this.originalBodyPad)},n.prototype.measureScrollbar=function(){var t=document.createElement("div");t.className="modal-scrollbar-measure",this.$body.append(t);var e=t.offsetWidth-t.clientWidth;return this.$body[0].removeChild(t),e};var i=t.fn.modal;t.fn.modal=e,t.fn.modal.Constructor=n,t.fn.modal.noConflict=function(){return t.fn.modal=i,this},t(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(n){var i=t(this),o=i.attr("href"),r=t(i.attr("data-target")||o&&o.replace(/.*(?=#[^\s]+$)/,"")),s=r.data("bs.modal")?"toggle":t.extend({remote:!/#/.test(o)&&o},r.data(),i.data());i.is("a")&&n.preventDefault(),r.one("show.bs.modal",function(t){t.isDefaultPrevented()||r.one("hidden.bs.modal",function(){i.is(":visible")&&i.trigger("focus")})}),e.call(r,s,this)})}(jQuery)},{}],9:[function(t,e,n){+function(t){"use strict";function e(e){return this.each(function(){var i=t(this),o=i.data("bs.popover"),r="object"==typeof e&&e;!o&&/destroy|hide/.test(e)||(o||i.data("bs.popover",o=new n(this,r)),"string"==typeof e&&o[e]())})}var n=function(t,e){this.init("popover",t,e)};if(!t.fn.tooltip)throw new Error("Popover requires tooltip.js");n.VERSION="3.3.6",n.DEFAULTS=t.extend({},t.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),n.prototype=t.extend({},t.fn.tooltip.Constructor.prototype),n.prototype.constructor=n,n.prototype.getDefaults=function(){return n.DEFAULTS},n.prototype.setContent=function(){var t=this.tip(),e=this.getTitle(),n=this.getContent();t.find(".popover-title")[this.options.html?"html":"text"](e),t.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof n?"html":"append":"text"](n),t.removeClass("fade top bottom left right in"),t.find(".popover-title").html()||t.find(".popover-title").hide()},n.prototype.hasContent=function(){return this.getTitle()||this.getContent()},n.prototype.getContent=function(){var t=this.$element,e=this.options;return t.attr("data-content")||("function"==typeof e.content?e.content.call(t[0]):e.content)},n.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var i=t.fn.popover;t.fn.popover=e,t.fn.popover.Constructor=n,t.fn.popover.noConflict=function(){return t.fn.popover=i,this}}(jQuery)},{}],10:[function(t,e,n){+function(t){"use strict";function e(n,i){this.$body=t(document.body),this.$scrollElement=t(t(n).is(document.body)?window:n),this.options=t.extend({},e.DEFAULTS,i),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",t.proxy(this.process,this)),this.refresh(),this.process()}function n(n){return this.each(function(){var i=t(this),o=i.data("bs.scrollspy"),r="object"==typeof n&&n;o||i.data("bs.scrollspy",o=new e(this,r)),"string"==typeof n&&o[n]()})}e.VERSION="3.3.6",e.DEFAULTS={offset:10},e.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},e.prototype.refresh=function(){var e=this,n="offset",i=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),t.isWindow(this.$scrollElement[0])||(n="position",i=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var e=t(this),o=e.data("target")||e.attr("href"),r=/^#./.test(o)&&t(o);return r&&r.length&&r.is(":visible")&&[[r[n]().top+i,o]]||null}).sort(function(t,e){return t[0]-e[0]}).each(function(){e.offsets.push(this[0]),e.targets.push(this[1])})},e.prototype.process=function(){var t,e=this.$scrollElement.scrollTop()+this.options.offset,n=this.getScrollHeight(),i=this.options.offset+n-this.$scrollElement.height(),o=this.offsets,r=this.targets,s=this.activeTarget;if(this.scrollHeight!=n&&this.refresh(),e>=i)return s!=(t=r[r.length-1])&&this.activate(t);if(s&&e<o[0])return this.activeTarget=null,this.clear();for(t=o.length;t--;)s!=r[t]&&e>=o[t]&&(void 0===o[t+1]||e<o[t+1])&&this.activate(r[t])},e.prototype.activate=function(e){this.activeTarget=e,this.clear();var n=this.selector+'[data-target="'+e+'"],'+this.selector+'[href="'+e+'"]',i=t(n).parents("li").addClass("active");i.parent(".dropdown-menu").length&&(i=i.closest("li.dropdown").addClass("active")),i.trigger("activate.bs.scrollspy")},e.prototype.clear=function(){t(this.selector).parentsUntil(this.options.target,".active").removeClass("active")};var i=t.fn.scrollspy;t.fn.scrollspy=n,t.fn.scrollspy.Constructor=e,t.fn.scrollspy.noConflict=function(){return t.fn.scrollspy=i,this},t(window).on("load.bs.scrollspy.data-api",function(){t('[data-spy="scroll"]').each(function(){var e=t(this);n.call(e,e.data())})})}(jQuery)},{}],11:[function(t,e,n){+function(t){"use strict";function e(e){return this.each(function(){var i=t(this),o=i.data("bs.tab");o||i.data("bs.tab",o=new n(this)),"string"==typeof e&&o[e]()})}var n=function(e){this.element=t(e)};n.VERSION="3.3.6",n.TRANSITION_DURATION=150,n.prototype.show=function(){var e=this.element,n=e.closest("ul:not(.dropdown-menu)"),i=e.data("target");if(i||(i=e.attr("href"),i=i&&i.replace(/.*(?=#[^\s]*$)/,"")),!e.parent("li").hasClass("active")){var o=n.find(".active:last a"),r=t.Event("hide.bs.tab",{relatedTarget:e[0]}),s=t.Event("show.bs.tab",{relatedTarget:o[0]});if(o.trigger(r),e.trigger(s),!s.isDefaultPrevented()&&!r.isDefaultPrevented()){var a=t(i);this.activate(e.closest("li"),n),this.activate(a,a.parent(),function(){o.trigger({type:"hidden.bs.tab",relatedTarget:e[0]}),e.trigger({type:"shown.bs.tab",relatedTarget:o[0]})})}}},n.prototype.activate=function(e,i,o){function r(){s.removeClass("active").find("> .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),e.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),a?(e[0].offsetWidth,e.addClass("in")):e.removeClass("fade"),e.parent(".dropdown-menu").length&&e.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),o&&o()}var s=i.find("> .active"),a=o&&t.support.transition&&(s.length&&s.hasClass("fade")||!!i.find("> .fade").length);s.length&&a?s.one("bsTransitionEnd",r).emulateTransitionEnd(n.TRANSITION_DURATION):r(),s.removeClass("in")};var i=t.fn.tab;t.fn.tab=e,t.fn.tab.Constructor=n,t.fn.tab.noConflict=function(){return t.fn.tab=i,this};var o=function(n){n.preventDefault(),e.call(t(this),"show")};t(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',o).on("click.bs.tab.data-api",'[data-toggle="pill"]',o)}(jQuery)},{}],12:[function(t,e,n){+function(t){"use strict";function e(e){return this.each(function(){var i=t(this),o=i.data("bs.tooltip"),r="object"==typeof e&&e;!o&&/destroy|hide/.test(e)||(o||i.data("bs.tooltip",o=new n(this,r)),"string"==typeof e&&o[e]())})}var n=function(t,e){this.type=null,this.options=null,this.enabled=null,this.timeout=null,this.hoverState=null,this.$element=null,this.inState=null,this.init("tooltip",t,e)};n.VERSION="3.3.6",n.TRANSITION_DURATION=150,n.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},n.prototype.init=function(e,n,i){if(this.enabled=!0,this.type=e,this.$element=t(n),this.options=this.getOptions(i),this.$viewport=this.options.viewport&&t(t.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var o=this.options.trigger.split(" "),r=o.length;r--;){var s=o[r];if("click"==s)this.$element.on("click."+this.type,this.options.selector,t.proxy(this.toggle,this));else if("manual"!=s){var a="hover"==s?"mouseenter":"focusin",l="hover"==s?"mouseleave":"focusout";this.$element.on(a+"."+this.type,this.options.selector,t.proxy(this.enter,this)),this.$element.on(l+"."+this.type,this.options.selector,t.proxy(this.leave,this))}}this.options.selector?this._options=t.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},n.prototype.getDefaults=function(){return n.DEFAULTS},n.prototype.getOptions=function(e){return e=t.extend({},this.getDefaults(),this.$element.data(),e),e.delay&&"number"==typeof e.delay&&(e.delay={show:e.delay,hide:e.delay}),e},n.prototype.getDelegateOptions=function(){var e={},n=this.getDefaults();return this._options&&t.each(this._options,function(t,i){n[t]!=i&&(e[t]=i)}),e},n.prototype.enter=function(e){var n=e instanceof this.constructor?e:t(e.currentTarget).data("bs."+this.type);return n||(n=new this.constructor(e.currentTarget,this.getDelegateOptions()),t(e.currentTarget).data("bs."+this.type,n)),e instanceof t.Event&&(n.inState["focusin"==e.type?"focus":"hover"]=!0),n.tip().hasClass("in")||"in"==n.hoverState?void(n.hoverState="in"):(clearTimeout(n.timeout),n.hoverState="in",n.options.delay&&n.options.delay.show?void(n.timeout=setTimeout(function(){"in"==n.hoverState&&n.show()},n.options.delay.show)):n.show())},n.prototype.isInStateTrue=function(){for(var t in this.inState)if(this.inState[t])return!0;return!1},n.prototype.leave=function(e){var n=e instanceof this.constructor?e:t(e.currentTarget).data("bs."+this.type);return n||(n=new this.constructor(e.currentTarget,this.getDelegateOptions()),t(e.currentTarget).data("bs."+this.type,n)),e instanceof t.Event&&(n.inState["focusout"==e.type?"focus":"hover"]=!1),n.isInStateTrue()?void 0:(clearTimeout(n.timeout),n.hoverState="out",n.options.delay&&n.options.delay.hide?void(n.timeout=setTimeout(function(){"out"==n.hoverState&&n.hide()},n.options.delay.hide)):n.hide())},n.prototype.show=function(){var e=t.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(e);var i=t.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(e.isDefaultPrevented()||!i)return;var o=this,r=this.tip(),s=this.getUID(this.type);this.setContent(),r.attr("id",s),this.$element.attr("aria-describedby",s),this.options.animation&&r.addClass("fade");var a="function"==typeof this.options.placement?this.options.placement.call(this,r[0],this.$element[0]):this.options.placement,l=/\s?auto?\s?/i,u=l.test(a);u&&(a=a.replace(l,"")||"top"),
-r.detach().css({top:0,left:0,display:"block"}).addClass(a).data("bs."+this.type,this),this.options.container?r.appendTo(this.options.container):r.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var c=this.getPosition(),f=r[0].offsetWidth,p=r[0].offsetHeight;if(u){var d=a,h=this.getPosition(this.$viewport);a="bottom"==a&&c.bottom+p>h.bottom?"top":"top"==a&&c.top-p<h.top?"bottom":"right"==a&&c.right+f>h.width?"left":"left"==a&&c.left-f<h.left?"right":a,r.removeClass(d).addClass(a)}var g=this.getCalculatedOffset(a,c,f,p);this.applyPlacement(g,a);var v=function(){var t=o.hoverState;o.$element.trigger("shown.bs."+o.type),o.hoverState=null,"out"==t&&o.leave(o)};t.support.transition&&this.$tip.hasClass("fade")?r.one("bsTransitionEnd",v).emulateTransitionEnd(n.TRANSITION_DURATION):v()}},n.prototype.applyPlacement=function(e,n){var i=this.tip(),o=i[0].offsetWidth,r=i[0].offsetHeight,s=parseInt(i.css("margin-top"),10),a=parseInt(i.css("margin-left"),10);isNaN(s)&&(s=0),isNaN(a)&&(a=0),e.top+=s,e.left+=a,t.offset.setOffset(i[0],t.extend({using:function(t){i.css({top:Math.round(t.top),left:Math.round(t.left)})}},e),0),i.addClass("in");var l=i[0].offsetWidth,u=i[0].offsetHeight;"top"==n&&u!=r&&(e.top=e.top+r-u);var c=this.getViewportAdjustedDelta(n,e,l,u);c.left?e.left+=c.left:e.top+=c.top;var f=/top|bottom/.test(n),p=f?2*c.left-o+l:2*c.top-r+u,d=f?"offsetWidth":"offsetHeight";i.offset(e),this.replaceArrow(p,i[0][d],f)},n.prototype.replaceArrow=function(t,e,n){this.arrow().css(n?"left":"top",50*(1-t/e)+"%").css(n?"top":"left","")},n.prototype.setContent=function(){var t=this.tip(),e=this.getTitle();t.find(".tooltip-inner")[this.options.html?"html":"text"](e),t.removeClass("fade in top bottom left right")},n.prototype.hide=function(e){function i(){"in"!=o.hoverState&&r.detach(),o.$element.removeAttr("aria-describedby").trigger("hidden.bs."+o.type),e&&e()}var o=this,r=t(this.$tip),s=t.Event("hide.bs."+this.type);return this.$element.trigger(s),s.isDefaultPrevented()?void 0:(r.removeClass("in"),t.support.transition&&r.hasClass("fade")?r.one("bsTransitionEnd",i).emulateTransitionEnd(n.TRANSITION_DURATION):i(),this.hoverState=null,this)},n.prototype.fixTitle=function(){var t=this.$element;(t.attr("title")||"string"!=typeof t.attr("data-original-title"))&&t.attr("data-original-title",t.attr("title")||"").attr("title","")},n.prototype.hasContent=function(){return this.getTitle()},n.prototype.getPosition=function(e){e=e||this.$element;var n=e[0],i="BODY"==n.tagName,o=n.getBoundingClientRect();null==o.width&&(o=t.extend({},o,{width:o.right-o.left,height:o.bottom-o.top}));var r=i?{top:0,left:0}:e.offset(),s={scroll:i?document.documentElement.scrollTop||document.body.scrollTop:e.scrollTop()},a=i?{width:t(window).width(),height:t(window).height()}:null;return t.extend({},o,s,a,r)},n.prototype.getCalculatedOffset=function(t,e,n,i){return"bottom"==t?{top:e.top+e.height,left:e.left+e.width/2-n/2}:"top"==t?{top:e.top-i,left:e.left+e.width/2-n/2}:"left"==t?{top:e.top+e.height/2-i/2,left:e.left-n}:{top:e.top+e.height/2-i/2,left:e.left+e.width}},n.prototype.getViewportAdjustedDelta=function(t,e,n,i){var o={top:0,left:0};if(!this.$viewport)return o;var r=this.options.viewport&&this.options.viewport.padding||0,s=this.getPosition(this.$viewport);if(/right|left/.test(t)){var a=e.top-r-s.scroll,l=e.top+r-s.scroll+i;a<s.top?o.top=s.top-a:l>s.top+s.height&&(o.top=s.top+s.height-l)}else{var u=e.left-r,c=e.left+r+n;u<s.left?o.left=s.left-u:c>s.right&&(o.left=s.left+s.width-c)}return o},n.prototype.getTitle=function(){var t,e=this.$element,n=this.options;return t=e.attr("data-original-title")||("function"==typeof n.title?n.title.call(e[0]):n.title)},n.prototype.getUID=function(t){do t+=~~(1e6*Math.random());while(document.getElementById(t));return t},n.prototype.tip=function(){if(!this.$tip&&(this.$tip=t(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},n.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},n.prototype.enable=function(){this.enabled=!0},n.prototype.disable=function(){this.enabled=!1},n.prototype.toggleEnabled=function(){this.enabled=!this.enabled},n.prototype.toggle=function(e){var n=this;e&&(n=t(e.currentTarget).data("bs."+this.type),n||(n=new this.constructor(e.currentTarget,this.getDelegateOptions()),t(e.currentTarget).data("bs."+this.type,n))),e?(n.inState.click=!n.inState.click,n.isInStateTrue()?n.enter(n):n.leave(n)):n.tip().hasClass("in")?n.leave(n):n.enter(n)},n.prototype.destroy=function(){var t=this;clearTimeout(this.timeout),this.hide(function(){t.$element.off("."+t.type).removeData("bs."+t.type),t.$tip&&t.$tip.detach(),t.$tip=null,t.$arrow=null,t.$viewport=null})};var i=t.fn.tooltip;t.fn.tooltip=e,t.fn.tooltip.Constructor=n,t.fn.tooltip.noConflict=function(){return t.fn.tooltip=i,this}}(jQuery)},{}],13:[function(t,e,n){+function(t){"use strict";function e(){var t=document.createElement("bootstrap"),e={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var n in e)if(void 0!==t.style[n])return{end:e[n]};return!1}t.fn.emulateTransitionEnd=function(e){var n=!1,i=this;t(this).one("bsTransitionEnd",function(){n=!0});var o=function(){n||t(i).trigger(t.support.transition.end)};return setTimeout(o,e),this},t(function(){t.support.transition=e(),t.support.transition&&(t.event.special.bsTransitionEnd={bindType:t.support.transition.end,delegateType:t.support.transition.end,handle:function(e){return t(e.target).is(this)?e.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery)},{}],14:[function(t,e,n){!function(t,n){"object"==typeof e&&"object"==typeof e.exports?e.exports=t.document?n(t,!0):function(t){if(!t.document)throw new Error("jQuery requires a window with a document");return n(t)}:n(t)}("undefined"!=typeof window?window:this,function(t,e){function n(t){var e=!!t&&"length"in t&&t.length,n=rt.type(t);return"function"===n||rt.isWindow(t)?!1:"array"===n||0===e||"number"==typeof e&&e>0&&e-1 in t}function i(t,e,n){if(rt.isFunction(e))return rt.grep(t,function(t,i){return!!e.call(t,i,t)!==n});if(e.nodeType)return rt.grep(t,function(t){return t===e!==n});if("string"==typeof e){if(gt.test(e))return rt.filter(e,t,n);e=rt.filter(e,t)}return rt.grep(t,function(t){return Z.call(e,t)>-1!==n})}function o(t,e){for(;(t=t[e])&&1!==t.nodeType;);return t}function r(t){var e={};return rt.each(t.match(wt)||[],function(t,n){e[n]=!0}),e}function s(){Y.removeEventListener("DOMContentLoaded",s),t.removeEventListener("load",s),rt.ready()}function a(){this.expando=rt.expando+a.uid++}function l(t,e,n){var i;if(void 0===n&&1===t.nodeType)if(i="data-"+e.replace(Nt,"-$&").toLowerCase(),n=t.getAttribute(i),"string"==typeof n){try{n="true"===n?!0:"false"===n?!1:"null"===n?null:+n+""===n?+n:St.test(n)?rt.parseJSON(n):n}catch(o){}$t.set(t,e,n)}else n=void 0;return n}function u(t,e,n,i){var o,r=1,s=20,a=i?function(){return i.cur()}:function(){return rt.css(t,e,"")},l=a(),u=n&&n[3]||(rt.cssNumber[e]?"":"px"),c=(rt.cssNumber[e]||"px"!==u&&+l)&&jt.exec(rt.css(t,e));if(c&&c[3]!==u){u=u||c[3],n=n||[],c=+l||1;do r=r||".5",c/=r,rt.style(t,e,c+u);while(r!==(r=a()/l)&&1!==r&&--s)}return n&&(c=+c||+l||0,o=n[1]?c+(n[1]+1)*n[2]:+n[2],i&&(i.unit=u,i.start=c,i.end=o)),o}function c(t,e){var n="undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e||"*"):"undefined"!=typeof t.querySelectorAll?t.querySelectorAll(e||"*"):[];return void 0===e||e&&rt.nodeName(t,e)?rt.merge([t],n):n}function f(t,e){for(var n=0,i=t.length;i>n;n++)kt.set(t[n],"globalEval",!e||kt.get(e[n],"globalEval"))}function p(t,e,n,i,o){for(var r,s,a,l,u,p,d=e.createDocumentFragment(),h=[],g=0,v=t.length;v>g;g++)if(r=t[g],r||0===r)if("object"===rt.type(r))rt.merge(h,r.nodeType?[r]:r);else if(Ht.test(r)){for(s=s||d.appendChild(e.createElement("div")),a=(Lt.exec(r)||["",""])[1].toLowerCase(),l=qt[a]||qt._default,s.innerHTML=l[1]+rt.htmlPrefilter(r)+l[2],p=l[0];p--;)s=s.lastChild;rt.merge(h,s.childNodes),s=d.firstChild,s.textContent=""}else h.push(e.createTextNode(r));for(d.textContent="",g=0;r=h[g++];)if(i&&rt.inArray(r,i)>-1)o&&o.push(r);else if(u=rt.contains(r.ownerDocument,r),s=c(d.appendChild(r),"script"),u&&f(s),n)for(p=0;r=s[p++];)Rt.test(r.type||"")&&n.push(r);return d}function d(){return!0}function h(){return!1}function g(){try{return Y.activeElement}catch(t){}}function v(t,e,n,i,o,r){var s,a;if("object"==typeof e){"string"!=typeof n&&(i=i||n,n=void 0);for(a in e)v(t,a,n,i,e[a],r);return t}if(null==i&&null==o?(o=n,i=n=void 0):null==o&&("string"==typeof n?(o=i,i=void 0):(o=i,i=n,n=void 0)),o===!1)o=h;else if(!o)return t;return 1===r&&(s=o,o=function(t){return rt().off(t),s.apply(this,arguments)},o.guid=s.guid||(s.guid=rt.guid++)),t.each(function(){rt.event.add(this,e,o,i,n)})}function m(t,e){return rt.nodeName(t,"table")&&rt.nodeName(11!==e.nodeType?e:e.firstChild,"tr")?t.getElementsByTagName("tbody")[0]||t.appendChild(t.ownerDocument.createElement("tbody")):t}function y(t){return t.type=(null!==t.getAttribute("type"))+"/"+t.type,t}function b(t){var e=Ut.exec(t.type);return e?t.type=e[1]:t.removeAttribute("type"),t}function x(t,e){var n,i,o,r,s,a,l,u;if(1===e.nodeType){if(kt.hasData(t)&&(r=kt.access(t),s=kt.set(e,r),u=r.events)){delete s.handle,s.events={};for(o in u)for(n=0,i=u[o].length;i>n;n++)rt.event.add(e,o,u[o][n])}$t.hasData(t)&&(a=$t.access(t),l=rt.extend({},a),$t.set(e,l))}}function w(t,e){var n=e.nodeName.toLowerCase();"input"===n&&It.test(t.type)?e.checked=t.checked:"input"!==n&&"textarea"!==n||(e.defaultValue=t.defaultValue)}function T(t,e,n,i){e=K.apply([],e);var o,r,s,a,l,u,f=0,d=t.length,h=d-1,g=e[0],v=rt.isFunction(g);if(v||d>1&&"string"==typeof g&&!it.checkClone&&Bt.test(g))return t.each(function(o){var r=t.eq(o);v&&(e[0]=g.call(this,o,r.html())),T(r,e,n,i)});if(d&&(o=p(e,t[0].ownerDocument,!1,t,i),r=o.firstChild,1===o.childNodes.length&&(o=r),r||i)){for(s=rt.map(c(o,"script"),y),a=s.length;d>f;f++)l=o,f!==h&&(l=rt.clone(l,!0,!0),a&&rt.merge(s,c(l,"script"))),n.call(t[f],l,f);if(a)for(u=s[s.length-1].ownerDocument,rt.map(s,b),f=0;a>f;f++)l=s[f],Rt.test(l.type||"")&&!kt.access(l,"globalEval")&&rt.contains(u,l)&&(l.src?rt._evalUrl&&rt._evalUrl(l.src):rt.globalEval(l.textContent.replace(zt,"")))}return t}function C(t,e,n){for(var i,o=e?rt.filter(e,t):t,r=0;null!=(i=o[r]);r++)n||1!==i.nodeType||rt.cleanData(c(i)),i.parentNode&&(n&&rt.contains(i.ownerDocument,i)&&f(c(i,"script")),i.parentNode.removeChild(i));return t}function E(t,e){var n=rt(e.createElement(t)).appendTo(e.body),i=rt.css(n[0],"display");return n.detach(),i}function k(t){var e=Y,n=Xt[t];return n||(n=E(t,e),"none"!==n&&n||(Vt=(Vt||rt("<iframe frameborder='0' width='0' height='0'/>")).appendTo(e.documentElement),e=Vt[0].contentDocument,e.write(),e.close(),n=E(t,e),Vt.detach()),Xt[t]=n),n}function $(t,e,n){var i,o,r,s,a=t.style;return n=n||Gt(t),s=n?n.getPropertyValue(e)||n[e]:void 0,""!==s&&void 0!==s||rt.contains(t.ownerDocument,t)||(s=rt.style(t,e)),n&&!it.pixelMarginRight()&&Yt.test(s)&&Qt.test(e)&&(i=a.width,o=a.minWidth,r=a.maxWidth,a.minWidth=a.maxWidth=a.width=s,s=n.width,a.width=i,a.minWidth=o,a.maxWidth=r),void 0!==s?s+"":s}function S(t,e){return{get:function(){return t()?void delete this.get:(this.get=e).apply(this,arguments)}}}function N(t){if(t in ie)return t;for(var e=t[0].toUpperCase()+t.slice(1),n=ne.length;n--;)if(t=ne[n]+e,t in ie)return t}function D(t,e,n){var i=jt.exec(e);return i?Math.max(0,i[2]-(n||0))+(i[3]||"px"):e}function j(t,e,n,i,o){for(var r=n===(i?"border":"content")?4:"width"===e?1:0,s=0;4>r;r+=2)"margin"===n&&(s+=rt.css(t,n+At[r],!0,o)),i?("content"===n&&(s-=rt.css(t,"padding"+At[r],!0,o)),"margin"!==n&&(s-=rt.css(t,"border"+At[r]+"Width",!0,o))):(s+=rt.css(t,"padding"+At[r],!0,o),"padding"!==n&&(s+=rt.css(t,"border"+At[r]+"Width",!0,o)));return s}function A(e,n,i){var o=!0,r="width"===n?e.offsetWidth:e.offsetHeight,s=Gt(e),a="border-box"===rt.css(e,"boxSizing",!1,s);if(Y.msFullscreenElement&&t.top!==t&&e.getClientRects().length&&(r=Math.round(100*e.getBoundingClientRect()[n])),0>=r||null==r){if(r=$(e,n,s),(0>r||null==r)&&(r=e.style[n]),Yt.test(r))return r;o=a&&(it.boxSizingReliable()||r===e.style[n]),r=parseFloat(r)||0}return r+j(e,n,i||(a?"border":"content"),o,s)+"px"}function O(t,e){for(var n,i,o,r=[],s=0,a=t.length;a>s;s++)i=t[s],i.style&&(r[s]=kt.get(i,"olddisplay"),n=i.style.display,e?(r[s]||"none"!==n||(i.style.display=""),""===i.style.display&&Ot(i)&&(r[s]=kt.access(i,"olddisplay",k(i.nodeName)))):(o=Ot(i),"none"===n&&o||kt.set(i,"olddisplay",o?n:rt.css(i,"display"))));for(s=0;a>s;s++)i=t[s],i.style&&(e&&"none"!==i.style.display&&""!==i.style.display||(i.style.display=e?r[s]||"":"none"));return t}function I(t,e,n,i,o){return new I.prototype.init(t,e,n,i,o)}function L(){return t.setTimeout(function(){oe=void 0}),oe=rt.now()}function R(t,e){var n,i=0,o={height:t};for(e=e?1:0;4>i;i+=2-e)n=At[i],o["margin"+n]=o["padding"+n]=t;return e&&(o.opacity=o.width=t),o}function q(t,e,n){for(var i,o=(F.tweeners[e]||[]).concat(F.tweeners["*"]),r=0,s=o.length;s>r;r++)if(i=o[r].call(n,e,t))return i}function H(t,e,n){var i,o,r,s,a,l,u,c,f=this,p={},d=t.style,h=t.nodeType&&Ot(t),g=kt.get(t,"fxshow");n.queue||(a=rt._queueHooks(t,"fx"),null==a.unqueued&&(a.unqueued=0,l=a.empty.fire,a.empty.fire=function(){a.unqueued||l()}),a.unqueued++,f.always(function(){f.always(function(){a.unqueued--,rt.queue(t,"fx").length||a.empty.fire()})})),1===t.nodeType&&("height"in e||"width"in e)&&(n.overflow=[d.overflow,d.overflowX,d.overflowY],u=rt.css(t,"display"),c="none"===u?kt.get(t,"olddisplay")||k(t.nodeName):u,"inline"===c&&"none"===rt.css(t,"float")&&(d.display="inline-block")),n.overflow&&(d.overflow="hidden",f.always(function(){d.overflow=n.overflow[0],d.overflowX=n.overflow[1],d.overflowY=n.overflow[2]}));for(i in e)if(o=e[i],se.exec(o)){if(delete e[i],r=r||"toggle"===o,o===(h?"hide":"show")){if("show"!==o||!g||void 0===g[i])continue;h=!0}p[i]=g&&g[i]||rt.style(t,i)}else u=void 0;if(rt.isEmptyObject(p))"inline"===("none"===u?k(t.nodeName):u)&&(d.display=u);else{g?"hidden"in g&&(h=g.hidden):g=kt.access(t,"fxshow",{}),r&&(g.hidden=!h),h?rt(t).show():f.done(function(){rt(t).hide()}),f.done(function(){var e;kt.remove(t,"fxshow");for(e in p)rt.style(t,e,p[e])});for(i in p)s=q(h?g[i]:0,i,f),i in g||(g[i]=s.start,h&&(s.end=s.start,s.start="width"===i||"height"===i?1:0))}}function P(t,e){var n,i,o,r,s;for(n in t)if(i=rt.camelCase(n),o=e[i],r=t[n],rt.isArray(r)&&(o=r[1],r=t[n]=r[0]),n!==i&&(t[i]=r,delete t[n]),s=rt.cssHooks[i],s&&"expand"in s){r=s.expand(r),delete t[i];for(n in r)n in t||(t[n]=r[n],e[n]=o)}else e[i]=o}function F(t,e,n){var i,o,r=0,s=F.prefilters.length,a=rt.Deferred().always(function(){delete l.elem}),l=function(){if(o)return!1;for(var e=oe||L(),n=Math.max(0,u.startTime+u.duration-e),i=n/u.duration||0,r=1-i,s=0,l=u.tweens.length;l>s;s++)u.tweens[s].run(r);return a.notifyWith(t,[u,r,n]),1>r&&l?n:(a.resolveWith(t,[u]),!1)},u=a.promise({elem:t,props:rt.extend({},e),opts:rt.extend(!0,{specialEasing:{},easing:rt.easing._default},n),originalProperties:e,originalOptions:n,startTime:oe||L(),duration:n.duration,tweens:[],createTween:function(e,n){var i=rt.Tween(t,u.opts,e,n,u.opts.specialEasing[e]||u.opts.easing);return u.tweens.push(i),i},stop:function(e){var n=0,i=e?u.tweens.length:0;if(o)return this;for(o=!0;i>n;n++)u.tweens[n].run(1);return e?(a.notifyWith(t,[u,1,0]),a.resolveWith(t,[u,e])):a.rejectWith(t,[u,e]),this}}),c=u.props;for(P(c,u.opts.specialEasing);s>r;r++)if(i=F.prefilters[r].call(u,t,c,u.opts))return rt.isFunction(i.stop)&&(rt._queueHooks(u.elem,u.opts.queue).stop=rt.proxy(i.stop,i)),i;return rt.map(c,q,u),rt.isFunction(u.opts.start)&&u.opts.start.call(t,u),rt.fx.timer(rt.extend(l,{elem:t,anim:u,queue:u.opts.queue})),u.progress(u.opts.progress).done(u.opts.done,u.opts.complete).fail(u.opts.fail).always(u.opts.always)}function M(t){return t.getAttribute&&t.getAttribute("class")||""}function W(t){return function(e,n){"string"!=typeof e&&(n=e,e="*");var i,o=0,r=e.toLowerCase().match(wt)||[];if(rt.isFunction(n))for(;i=r[o++];)"+"===i[0]?(i=i.slice(1)||"*",(t[i]=t[i]||[]).unshift(n)):(t[i]=t[i]||[]).push(n)}}function _(t,e,n,i){function o(a){var l;return r[a]=!0,rt.each(t[a]||[],function(t,a){var u=a(e,n,i);return"string"!=typeof u||s||r[u]?s?!(l=u):void 0:(e.dataTypes.unshift(u),o(u),!1)}),l}var r={},s=t===ke;return o(e.dataTypes[0])||!r["*"]&&o("*")}function B(t,e){var n,i,o=rt.ajaxSettings.flatOptions||{};for(n in e)void 0!==e[n]&&((o[n]?t:i||(i={}))[n]=e[n]);return i&&rt.extend(!0,t,i),t}function U(t,e,n){for(var i,o,r,s,a=t.contents,l=t.dataTypes;"*"===l[0];)l.shift(),void 0===i&&(i=t.mimeType||e.getResponseHeader("Content-Type"));if(i)for(o in a)if(a[o]&&a[o].test(i)){l.unshift(o);break}if(l[0]in n)r=l[0];else{for(o in n){if(!l[0]||t.converters[o+" "+l[0]]){r=o;break}s||(s=o)}r=r||s}return r?(r!==l[0]&&l.unshift(r),n[r]):void 0}function z(t,e,n,i){var o,r,s,a,l,u={},c=t.dataTypes.slice();if(c[1])for(s in t.converters)u[s.toLowerCase()]=t.converters[s];for(r=c.shift();r;)if(t.responseFields[r]&&(n[t.responseFields[r]]=e),!l&&i&&t.dataFilter&&(e=t.dataFilter(e,t.dataType)),l=r,r=c.shift())if("*"===r)r=l;else if("*"!==l&&l!==r){if(s=u[l+" "+r]||u["* "+r],!s)for(o in u)if(a=o.split(" "),a[1]===r&&(s=u[l+" "+a[0]]||u["* "+a[0]])){s===!0?s=u[o]:u[o]!==!0&&(r=a[0],c.unshift(a[1]));break}if(s!==!0)if(s&&t["throws"])e=s(e);else try{e=s(e)}catch(f){return{state:"parsererror",error:s?f:"No conversion from "+l+" to "+r}}}return{state:"success",data:e}}function V(t,e,n,i){var o;if(rt.isArray(e))rt.each(e,function(e,o){n||De.test(t)?i(t,o):V(t+"["+("object"==typeof o&&null!=o?e:"")+"]",o,n,i)});else if(n||"object"!==rt.type(e))i(t,e);else for(o in e)V(t+"["+o+"]",e[o],n,i)}function X(t){return rt.isWindow(t)?t:9===t.nodeType&&t.defaultView}var Q=[],Y=t.document,G=Q.slice,K=Q.concat,J=Q.push,Z=Q.indexOf,tt={},et=tt.toString,nt=tt.hasOwnProperty,it={},ot="2.2.1",rt=function(t,e){return new rt.fn.init(t,e)},st=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,at=/^-ms-/,lt=/-([\da-z])/gi,ut=function(t,e){return e.toUpperCase()};rt.fn=rt.prototype={jquery:ot,constructor:rt,selector:"",length:0,toArray:function(){return G.call(this)},get:function(t){return null!=t?0>t?this[t+this.length]:this[t]:G.call(this)},pushStack:function(t){var e=rt.merge(this.constructor(),t);return e.prevObject=this,e.context=this.context,e},each:function(t){return rt.each(this,t)},map:function(t){return this.pushStack(rt.map(this,function(e,n){return t.call(e,n,e)}))},slice:function(){return this.pushStack(G.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(t){var e=this.length,n=+t+(0>t?e:0);return this.pushStack(n>=0&&e>n?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:J,sort:Q.sort,splice:Q.splice},rt.extend=rt.fn.extend=function(){var t,e,n,i,o,r,s=arguments[0]||{},a=1,l=arguments.length,u=!1;for("boolean"==typeof s&&(u=s,s=arguments[a]||{},a++),"object"==typeof s||rt.isFunction(s)||(s={}),a===l&&(s=this,a--);l>a;a++)if(null!=(t=arguments[a]))for(e in t)n=s[e],i=t[e],s!==i&&(u&&i&&(rt.isPlainObject(i)||(o=rt.isArray(i)))?(o?(o=!1,r=n&&rt.isArray(n)?n:[]):r=n&&rt.isPlainObject(n)?n:{},s[e]=rt.extend(u,r,i)):void 0!==i&&(s[e]=i));return s},rt.extend({expando:"jQuery"+(ot+Math.random()).replace(/\D/g,""),isReady:!0,error:function(t){throw new Error(t)},noop:function(){},isFunction:function(t){return"function"===rt.type(t)},isArray:Array.isArray,isWindow:function(t){return null!=t&&t===t.window},isNumeric:function(t){var e=t&&t.toString();return!rt.isArray(t)&&e-parseFloat(e)+1>=0},isPlainObject:function(t){return"object"!==rt.type(t)||t.nodeType||rt.isWindow(t)?!1:!t.constructor||nt.call(t.constructor.prototype,"isPrototypeOf")},isEmptyObject:function(t){var e;for(e in t)return!1;return!0},type:function(t){return null==t?t+"":"object"==typeof t||"function"==typeof t?tt[et.call(t)]||"object":typeof t},globalEval:function(t){var e,n=eval;t=rt.trim(t),t&&(1===t.indexOf("use strict")?(e=Y.createElement("script"),e.text=t,Y.head.appendChild(e).parentNode.removeChild(e)):n(t))},camelCase:function(t){return t.replace(at,"ms-").replace(lt,ut)},nodeName:function(t,e){return t.nodeName&&t.nodeName.toLowerCase()===e.toLowerCase()},each:function(t,e){var i,o=0;if(n(t))for(i=t.length;i>o&&e.call(t[o],o,t[o])!==!1;o++);else for(o in t)if(e.call(t[o],o,t[o])===!1)break;return t},trim:function(t){return null==t?"":(t+"").replace(st,"")},makeArray:function(t,e){var i=e||[];return null!=t&&(n(Object(t))?rt.merge(i,"string"==typeof t?[t]:t):J.call(i,t)),i},inArray:function(t,e,n){return null==e?-1:Z.call(e,t,n)},merge:function(t,e){for(var n=+e.length,i=0,o=t.length;n>i;i++)t[o++]=e[i];return t.length=o,t},grep:function(t,e,n){for(var i,o=[],r=0,s=t.length,a=!n;s>r;r++)i=!e(t[r],r),i!==a&&o.push(t[r]);return o},map:function(t,e,i){var o,r,s=0,a=[];if(n(t))for(o=t.length;o>s;s++)r=e(t[s],s,i),null!=r&&a.push(r);else for(s in t)r=e(t[s],s,i),null!=r&&a.push(r);return K.apply([],a)},guid:1,proxy:function(t,e){var n,i,o;return"string"==typeof e&&(n=t[e],e=t,t=n),rt.isFunction(t)?(i=G.call(arguments,2),o=function(){return t.apply(e||this,i.concat(G.call(arguments)))},o.guid=t.guid=t.guid||rt.guid++,o):void 0},now:Date.now,support:it}),"function"==typeof Symbol&&(rt.fn[Symbol.iterator]=Q[Symbol.iterator]),rt.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(t,e){tt["[object "+e+"]"]=e.toLowerCase()});var ct=function(t){function e(t,e,n,i){var o,r,s,a,l,u,f,d,h=e&&e.ownerDocument,g=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==g&&9!==g&&11!==g)return n;if(!i&&((e?e.ownerDocument||e:M)!==O&&A(e),e=e||O,L)){if(11!==g&&(u=mt.exec(t)))if(o=u[1]){if(9===g){if(!(s=e.getElementById(o)))return n;if(s.id===o)return n.push(s),n}else if(h&&(s=h.getElementById(o))&&P(e,s)&&s.id===o)return n.push(s),n}else{if(u[2])return J.apply(n,e.getElementsByTagName(t)),n;if((o=u[3])&&w.getElementsByClassName&&e.getElementsByClassName)return J.apply(n,e.getElementsByClassName(o)),n}if(w.qsa&&!z[t+" "]&&(!R||!R.test(t))){if(1!==g)h=e,d=t;else if("object"!==e.nodeName.toLowerCase()){for((a=e.getAttribute("id"))?a=a.replace(bt,"\\$&"):e.setAttribute("id",a=F),f=k(t),r=f.length,l=pt.test(a)?"#"+a:"[id='"+a+"']";r--;)f[r]=l+" "+p(f[r]);d=f.join(","),h=yt.test(t)&&c(e.parentNode)||e}if(d)try{return J.apply(n,h.querySelectorAll(d)),n}catch(v){}finally{a===F&&e.removeAttribute("id")}}}return S(t.replace(at,"$1"),e,n,i)}function n(){function t(n,i){return e.push(n+" ")>T.cacheLength&&delete t[e.shift()],t[n+" "]=i}var e=[];return t}function i(t){return t[F]=!0,t}function o(t){var e=O.createElement("div");try{return!!t(e)}catch(n){return!1}finally{e.parentNode&&e.parentNode.removeChild(e),e=null}}function r(t,e){for(var n=t.split("|"),i=n.length;i--;)T.attrHandle[n[i]]=e}function s(t,e){var n=e&&t,i=n&&1===t.nodeType&&1===e.nodeType&&(~e.sourceIndex||X)-(~t.sourceIndex||X);if(i)return i;if(n)for(;n=n.nextSibling;)if(n===e)return-1;return t?1:-1}function a(t){return function(e){var n=e.nodeName.toLowerCase();return"input"===n&&e.type===t}}function l(t){return function(e){var n=e.nodeName.toLowerCase();return("input"===n||"button"===n)&&e.type===t}}function u(t){return i(function(e){return e=+e,i(function(n,i){for(var o,r=t([],n.length,e),s=r.length;s--;)n[o=r[s]]&&(n[o]=!(i[o]=n[o]))})})}function c(t){return t&&"undefined"!=typeof t.getElementsByTagName&&t}function f(){}function p(t){for(var e=0,n=t.length,i="";n>e;e++)i+=t[e].value;return i}function d(t,e,n){var i=e.dir,o=n&&"parentNode"===i,r=_++;return e.first?function(e,n,r){for(;e=e[i];)if(1===e.nodeType||o)return t(e,n,r)}:function(e,n,s){var a,l,u,c=[W,r];if(s){for(;e=e[i];)if((1===e.nodeType||o)&&t(e,n,s))return!0}else for(;e=e[i];)if(1===e.nodeType||o){if(u=e[F]||(e[F]={}),l=u[e.uniqueID]||(u[e.uniqueID]={}),(a=l[i])&&a[0]===W&&a[1]===r)return c[2]=a[2];if(l[i]=c,c[2]=t(e,n,s))return!0}}}function h(t){return t.length>1?function(e,n,i){for(var o=t.length;o--;)if(!t[o](e,n,i))return!1;return!0}:t[0]}function g(t,n,i){for(var o=0,r=n.length;r>o;o++)e(t,n[o],i);return i}function v(t,e,n,i,o){for(var r,s=[],a=0,l=t.length,u=null!=e;l>a;a++)(r=t[a])&&(n&&!n(r,i,o)||(s.push(r),u&&e.push(a)));return s}function m(t,e,n,o,r,s){return o&&!o[F]&&(o=m(o)),r&&!r[F]&&(r=m(r,s)),i(function(i,s,a,l){var u,c,f,p=[],d=[],h=s.length,m=i||g(e||"*",a.nodeType?[a]:a,[]),y=!t||!i&&e?m:v(m,p,t,a,l),b=n?r||(i?t:h||o)?[]:s:y;if(n&&n(y,b,a,l),o)for(u=v(b,d),o(u,[],a,l),c=u.length;c--;)(f=u[c])&&(b[d[c]]=!(y[d[c]]=f));if(i){if(r||t){if(r){for(u=[],c=b.length;c--;)(f=b[c])&&u.push(y[c]=f);r(null,b=[],u,l)}for(c=b.length;c--;)(f=b[c])&&(u=r?tt(i,f):p[c])>-1&&(i[u]=!(s[u]=f))}}else b=v(b===s?b.splice(h,b.length):b),r?r(null,s,b,l):J.apply(s,b)})}function y(t){for(var e,n,i,o=t.length,r=T.relative[t[0].type],s=r||T.relative[" "],a=r?1:0,l=d(function(t){return t===e},s,!0),u=d(function(t){return tt(e,t)>-1},s,!0),c=[function(t,n,i){var o=!r&&(i||n!==N)||((e=n).nodeType?l(t,n,i):u(t,n,i));return e=null,o}];o>a;a++)if(n=T.relative[t[a].type])c=[d(h(c),n)];else{if(n=T.filter[t[a].type].apply(null,t[a].matches),n[F]){for(i=++a;o>i&&!T.relative[t[i].type];i++);return m(a>1&&h(c),a>1&&p(t.slice(0,a-1).concat({value:" "===t[a-2].type?"*":""})).replace(at,"$1"),n,i>a&&y(t.slice(a,i)),o>i&&y(t=t.slice(i)),o>i&&p(t))}c.push(n)}return h(c)}function b(t,n){var o=n.length>0,r=t.length>0,s=function(i,s,a,l,u){var c,f,p,d=0,h="0",g=i&&[],m=[],y=N,b=i||r&&T.find.TAG("*",u),x=W+=null==y?1:Math.random()||.1,w=b.length;for(u&&(N=s===O||s||u);h!==w&&null!=(c=b[h]);h++){if(r&&c){for(f=0,s||c.ownerDocument===O||(A(c),a=!L);p=t[f++];)if(p(c,s||O,a)){l.push(c);break}u&&(W=x)}o&&((c=!p&&c)&&d--,i&&g.push(c))}if(d+=h,o&&h!==d){for(f=0;p=n[f++];)p(g,m,s,a);if(i){if(d>0)for(;h--;)g[h]||m[h]||(m[h]=G.call(l));m=v(m)}J.apply(l,m),u&&!i&&m.length>0&&d+n.length>1&&e.uniqueSort(l)}return u&&(W=x,N=y),g};return o?i(s):s}var x,w,T,C,E,k,$,S,N,D,j,A,O,I,L,R,q,H,P,F="sizzle"+1*new Date,M=t.document,W=0,_=0,B=n(),U=n(),z=n(),V=function(t,e){return t===e&&(j=!0),0},X=1<<31,Q={}.hasOwnProperty,Y=[],G=Y.pop,K=Y.push,J=Y.push,Z=Y.slice,tt=function(t,e){for(var n=0,i=t.length;i>n;n++)if(t[n]===e)return n;return-1},et="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",nt="[\\x20\\t\\r\\n\\f]",it="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",ot="\\["+nt+"*("+it+")(?:"+nt+"*([*^$|!~]?=)"+nt+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+it+"))|)"+nt+"*\\]",rt=":("+it+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+ot+")*)|.*)\\)|)",st=new RegExp(nt+"+","g"),at=new RegExp("^"+nt+"+|((?:^|[^\\\\])(?:\\\\.)*)"+nt+"+$","g"),lt=new RegExp("^"+nt+"*,"+nt+"*"),ut=new RegExp("^"+nt+"*([>+~]|"+nt+")"+nt+"*"),ct=new RegExp("="+nt+"*([^\\]'\"]*?)"+nt+"*\\]","g"),ft=new RegExp(rt),pt=new RegExp("^"+it+"$"),dt={ID:new RegExp("^#("+it+")"),CLASS:new RegExp("^\\.("+it+")"),TAG:new RegExp("^("+it+"|[*])"),ATTR:new RegExp("^"+ot),PSEUDO:new RegExp("^"+rt),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+nt+"*(even|odd|(([+-]|)(\\d*)n|)"+nt+"*(?:([+-]|)"+nt+"*(\\d+)|))"+nt+"*\\)|)","i"),bool:new RegExp("^(?:"+et+")$","i"),needsContext:new RegExp("^"+nt+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+nt+"*((?:-\\d)?\\d*)"+nt+"*\\)|)(?=[^-]|$)","i")},ht=/^(?:input|select|textarea|button)$/i,gt=/^h\d$/i,vt=/^[^{]+\{\s*\[native \w/,mt=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,yt=/[+~]/,bt=/'|\\/g,xt=new RegExp("\\\\([\\da-f]{1,6}"+nt+"?|("+nt+")|.)","ig"),wt=function(t,e,n){var i="0x"+e-65536;return i!==i||n?e:0>i?String.fromCharCode(i+65536):String.fromCharCode(i>>10|55296,1023&i|56320)},Tt=function(){A()};try{J.apply(Y=Z.call(M.childNodes),M.childNodes),Y[M.childNodes.length].nodeType}catch(Ct){J={apply:Y.length?function(t,e){K.apply(t,Z.call(e))}:function(t,e){for(var n=t.length,i=0;t[n++]=e[i++];);t.length=n-1}}}w=e.support={},E=e.isXML=function(t){var e=t&&(t.ownerDocument||t).documentElement;return e?"HTML"!==e.nodeName:!1},A=e.setDocument=function(t){var e,n,i=t?t.ownerDocument||t:M;return i!==O&&9===i.nodeType&&i.documentElement?(O=i,I=O.documentElement,L=!E(O),(n=O.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",Tt,!1):n.attachEvent&&n.attachEvent("onunload",Tt)),w.attributes=o(function(t){return t.className="i",!t.getAttribute("className")}),w.getElementsByTagName=o(function(t){return t.appendChild(O.createComment("")),!t.getElementsByTagName("*").length}),w.getElementsByClassName=vt.test(O.getElementsByClassName),w.getById=o(function(t){return I.appendChild(t).id=F,!O.getElementsByName||!O.getElementsByName(F).length}),w.getById?(T.find.ID=function(t,e){if("undefined"!=typeof e.getElementById&&L){var n=e.getElementById(t);return n?[n]:[]}},T.filter.ID=function(t){var e=t.replace(xt,wt);return function(t){return t.getAttribute("id")===e}}):(delete T.find.ID,T.filter.ID=function(t){var e=t.replace(xt,wt);return function(t){var n="undefined"!=typeof t.getAttributeNode&&t.getAttributeNode("id");return n&&n.value===e}}),T.find.TAG=w.getElementsByTagName?function(t,e){return"undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t):w.qsa?e.querySelectorAll(t):void 0}:function(t,e){var n,i=[],o=0,r=e.getElementsByTagName(t);if("*"===t){for(;n=r[o++];)1===n.nodeType&&i.push(n);return i}return r},T.find.CLASS=w.getElementsByClassName&&function(t,e){return"undefined"!=typeof e.getElementsByClassName&&L?e.getElementsByClassName(t):void 0},q=[],R=[],(w.qsa=vt.test(O.querySelectorAll))&&(o(function(t){I.appendChild(t).innerHTML="<a id='"+F+"'></a><select id='"+F+"-\r\\' msallowcapture=''><option selected=''></option></select>",t.querySelectorAll("[msallowcapture^='']").length&&R.push("[*^$]="+nt+"*(?:''|\"\")"),t.querySelectorAll("[selected]").length||R.push("\\["+nt+"*(?:value|"+et+")"),t.querySelectorAll("[id~="+F+"-]").length||R.push("~="),t.querySelectorAll(":checked").length||R.push(":checked"),t.querySelectorAll("a#"+F+"+*").length||R.push(".#.+[+~]")}),o(function(t){var e=O.createElement("input");e.setAttribute("type","hidden"),t.appendChild(e).setAttribute("name","D"),t.querySelectorAll("[name=d]").length&&R.push("name"+nt+"*[*^$|!~]?="),t.querySelectorAll(":enabled").length||R.push(":enabled",":disabled"),t.querySelectorAll("*,:x"),R.push(",.*:")})),(w.matchesSelector=vt.test(H=I.matches||I.webkitMatchesSelector||I.mozMatchesSelector||I.oMatchesSelector||I.msMatchesSelector))&&o(function(t){w.disconnectedMatch=H.call(t,"div"),H.call(t,"[s!='']:x"),q.push("!=",rt)}),R=R.length&&new RegExp(R.join("|")),q=q.length&&new RegExp(q.join("|")),e=vt.test(I.compareDocumentPosition),P=e||vt.test(I.contains)?function(t,e){var n=9===t.nodeType?t.documentElement:t,i=e&&e.parentNode;return t===i||!(!i||1!==i.nodeType||!(n.contains?n.contains(i):t.compareDocumentPosition&&16&t.compareDocumentPosition(i)))}:function(t,e){if(e)for(;e=e.parentNode;)if(e===t)return!0;return!1},V=e?function(t,e){if(t===e)return j=!0,0;var n=!t.compareDocumentPosition-!e.compareDocumentPosition;return n?n:(n=(t.ownerDocument||t)===(e.ownerDocument||e)?t.compareDocumentPosition(e):1,1&n||!w.sortDetached&&e.compareDocumentPosition(t)===n?t===O||t.ownerDocument===M&&P(M,t)?-1:e===O||e.ownerDocument===M&&P(M,e)?1:D?tt(D,t)-tt(D,e):0:4&n?-1:1)}:function(t,e){if(t===e)return j=!0,0;var n,i=0,o=t.parentNode,r=e.parentNode,a=[t],l=[e];if(!o||!r)return t===O?-1:e===O?1:o?-1:r?1:D?tt(D,t)-tt(D,e):0;if(o===r)return s(t,e);for(n=t;n=n.parentNode;)a.unshift(n);for(n=e;n=n.parentNode;)l.unshift(n);for(;a[i]===l[i];)i++;return i?s(a[i],l[i]):a[i]===M?-1:l[i]===M?1:0},O):O},e.matches=function(t,n){return e(t,null,null,n)},e.matchesSelector=function(t,n){if((t.ownerDocument||t)!==O&&A(t),n=n.replace(ct,"='$1']"),
-w.matchesSelector&&L&&!z[n+" "]&&(!q||!q.test(n))&&(!R||!R.test(n)))try{var i=H.call(t,n);if(i||w.disconnectedMatch||t.document&&11!==t.document.nodeType)return i}catch(o){}return e(n,O,null,[t]).length>0},e.contains=function(t,e){return(t.ownerDocument||t)!==O&&A(t),P(t,e)},e.attr=function(t,e){(t.ownerDocument||t)!==O&&A(t);var n=T.attrHandle[e.toLowerCase()],i=n&&Q.call(T.attrHandle,e.toLowerCase())?n(t,e,!L):void 0;return void 0!==i?i:w.attributes||!L?t.getAttribute(e):(i=t.getAttributeNode(e))&&i.specified?i.value:null},e.error=function(t){throw new Error("Syntax error, unrecognized expression: "+t)},e.uniqueSort=function(t){var e,n=[],i=0,o=0;if(j=!w.detectDuplicates,D=!w.sortStable&&t.slice(0),t.sort(V),j){for(;e=t[o++];)e===t[o]&&(i=n.push(o));for(;i--;)t.splice(n[i],1)}return D=null,t},C=e.getText=function(t){var e,n="",i=0,o=t.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof t.textContent)return t.textContent;for(t=t.firstChild;t;t=t.nextSibling)n+=C(t)}else if(3===o||4===o)return t.nodeValue}else for(;e=t[i++];)n+=C(e);return n},T=e.selectors={cacheLength:50,createPseudo:i,match:dt,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(t){return t[1]=t[1].replace(xt,wt),t[3]=(t[3]||t[4]||t[5]||"").replace(xt,wt),"~="===t[2]&&(t[3]=" "+t[3]+" "),t.slice(0,4)},CHILD:function(t){return t[1]=t[1].toLowerCase(),"nth"===t[1].slice(0,3)?(t[3]||e.error(t[0]),t[4]=+(t[4]?t[5]+(t[6]||1):2*("even"===t[3]||"odd"===t[3])),t[5]=+(t[7]+t[8]||"odd"===t[3])):t[3]&&e.error(t[0]),t},PSEUDO:function(t){var e,n=!t[6]&&t[2];return dt.CHILD.test(t[0])?null:(t[3]?t[2]=t[4]||t[5]||"":n&&ft.test(n)&&(e=k(n,!0))&&(e=n.indexOf(")",n.length-e)-n.length)&&(t[0]=t[0].slice(0,e),t[2]=n.slice(0,e)),t.slice(0,3))}},filter:{TAG:function(t){var e=t.replace(xt,wt).toLowerCase();return"*"===t?function(){return!0}:function(t){return t.nodeName&&t.nodeName.toLowerCase()===e}},CLASS:function(t){var e=B[t+" "];return e||(e=new RegExp("(^|"+nt+")"+t+"("+nt+"|$)"))&&B(t,function(t){return e.test("string"==typeof t.className&&t.className||"undefined"!=typeof t.getAttribute&&t.getAttribute("class")||"")})},ATTR:function(t,n,i){return function(o){var r=e.attr(o,t);return null==r?"!="===n:n?(r+="","="===n?r===i:"!="===n?r!==i:"^="===n?i&&0===r.indexOf(i):"*="===n?i&&r.indexOf(i)>-1:"$="===n?i&&r.slice(-i.length)===i:"~="===n?(" "+r.replace(st," ")+" ").indexOf(i)>-1:"|="===n?r===i||r.slice(0,i.length+1)===i+"-":!1):!0}},CHILD:function(t,e,n,i,o){var r="nth"!==t.slice(0,3),s="last"!==t.slice(-4),a="of-type"===e;return 1===i&&0===o?function(t){return!!t.parentNode}:function(e,n,l){var u,c,f,p,d,h,g=r!==s?"nextSibling":"previousSibling",v=e.parentNode,m=a&&e.nodeName.toLowerCase(),y=!l&&!a,b=!1;if(v){if(r){for(;g;){for(p=e;p=p[g];)if(a?p.nodeName.toLowerCase()===m:1===p.nodeType)return!1;h=g="only"===t&&!h&&"nextSibling"}return!0}if(h=[s?v.firstChild:v.lastChild],s&&y){for(p=v,f=p[F]||(p[F]={}),c=f[p.uniqueID]||(f[p.uniqueID]={}),u=c[t]||[],d=u[0]===W&&u[1],b=d&&u[2],p=d&&v.childNodes[d];p=++d&&p&&p[g]||(b=d=0)||h.pop();)if(1===p.nodeType&&++b&&p===e){c[t]=[W,d,b];break}}else if(y&&(p=e,f=p[F]||(p[F]={}),c=f[p.uniqueID]||(f[p.uniqueID]={}),u=c[t]||[],d=u[0]===W&&u[1],b=d),b===!1)for(;(p=++d&&p&&p[g]||(b=d=0)||h.pop())&&((a?p.nodeName.toLowerCase()!==m:1!==p.nodeType)||!++b||(y&&(f=p[F]||(p[F]={}),c=f[p.uniqueID]||(f[p.uniqueID]={}),c[t]=[W,b]),p!==e)););return b-=o,b===i||b%i===0&&b/i>=0}}},PSEUDO:function(t,n){var o,r=T.pseudos[t]||T.setFilters[t.toLowerCase()]||e.error("unsupported pseudo: "+t);return r[F]?r(n):r.length>1?(o=[t,t,"",n],T.setFilters.hasOwnProperty(t.toLowerCase())?i(function(t,e){for(var i,o=r(t,n),s=o.length;s--;)i=tt(t,o[s]),t[i]=!(e[i]=o[s])}):function(t){return r(t,0,o)}):r}},pseudos:{not:i(function(t){var e=[],n=[],o=$(t.replace(at,"$1"));return o[F]?i(function(t,e,n,i){for(var r,s=o(t,null,i,[]),a=t.length;a--;)(r=s[a])&&(t[a]=!(e[a]=r))}):function(t,i,r){return e[0]=t,o(e,null,r,n),e[0]=null,!n.pop()}}),has:i(function(t){return function(n){return e(t,n).length>0}}),contains:i(function(t){return t=t.replace(xt,wt),function(e){return(e.textContent||e.innerText||C(e)).indexOf(t)>-1}}),lang:i(function(t){return pt.test(t||"")||e.error("unsupported lang: "+t),t=t.replace(xt,wt).toLowerCase(),function(e){var n;do if(n=L?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return n=n.toLowerCase(),n===t||0===n.indexOf(t+"-");while((e=e.parentNode)&&1===e.nodeType);return!1}}),target:function(e){var n=t.location&&t.location.hash;return n&&n.slice(1)===e.id},root:function(t){return t===I},focus:function(t){return t===O.activeElement&&(!O.hasFocus||O.hasFocus())&&!!(t.type||t.href||~t.tabIndex)},enabled:function(t){return t.disabled===!1},disabled:function(t){return t.disabled===!0},checked:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&!!t.checked||"option"===e&&!!t.selected},selected:function(t){return t.parentNode&&t.parentNode.selectedIndex,t.selected===!0},empty:function(t){for(t=t.firstChild;t;t=t.nextSibling)if(t.nodeType<6)return!1;return!0},parent:function(t){return!T.pseudos.empty(t)},header:function(t){return gt.test(t.nodeName)},input:function(t){return ht.test(t.nodeName)},button:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&"button"===t.type||"button"===e},text:function(t){var e;return"input"===t.nodeName.toLowerCase()&&"text"===t.type&&(null==(e=t.getAttribute("type"))||"text"===e.toLowerCase())},first:u(function(){return[0]}),last:u(function(t,e){return[e-1]}),eq:u(function(t,e,n){return[0>n?n+e:n]}),even:u(function(t,e){for(var n=0;e>n;n+=2)t.push(n);return t}),odd:u(function(t,e){for(var n=1;e>n;n+=2)t.push(n);return t}),lt:u(function(t,e,n){for(var i=0>n?n+e:n;--i>=0;)t.push(i);return t}),gt:u(function(t,e,n){for(var i=0>n?n+e:n;++i<e;)t.push(i);return t})}},T.pseudos.nth=T.pseudos.eq;for(x in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})T.pseudos[x]=a(x);for(x in{submit:!0,reset:!0})T.pseudos[x]=l(x);return f.prototype=T.filters=T.pseudos,T.setFilters=new f,k=e.tokenize=function(t,n){var i,o,r,s,a,l,u,c=U[t+" "];if(c)return n?0:c.slice(0);for(a=t,l=[],u=T.preFilter;a;){i&&!(o=lt.exec(a))||(o&&(a=a.slice(o[0].length)||a),l.push(r=[])),i=!1,(o=ut.exec(a))&&(i=o.shift(),r.push({value:i,type:o[0].replace(at," ")}),a=a.slice(i.length));for(s in T.filter)!(o=dt[s].exec(a))||u[s]&&!(o=u[s](o))||(i=o.shift(),r.push({value:i,type:s,matches:o}),a=a.slice(i.length));if(!i)break}return n?a.length:a?e.error(t):U(t,l).slice(0)},$=e.compile=function(t,e){var n,i=[],o=[],r=z[t+" "];if(!r){for(e||(e=k(t)),n=e.length;n--;)r=y(e[n]),r[F]?i.push(r):o.push(r);r=z(t,b(o,i)),r.selector=t}return r},S=e.select=function(t,e,n,i){var o,r,s,a,l,u="function"==typeof t&&t,f=!i&&k(t=u.selector||t);if(n=n||[],1===f.length){if(r=f[0]=f[0].slice(0),r.length>2&&"ID"===(s=r[0]).type&&w.getById&&9===e.nodeType&&L&&T.relative[r[1].type]){if(e=(T.find.ID(s.matches[0].replace(xt,wt),e)||[])[0],!e)return n;u&&(e=e.parentNode),t=t.slice(r.shift().value.length)}for(o=dt.needsContext.test(t)?0:r.length;o--&&(s=r[o],!T.relative[a=s.type]);)if((l=T.find[a])&&(i=l(s.matches[0].replace(xt,wt),yt.test(r[0].type)&&c(e.parentNode)||e))){if(r.splice(o,1),t=i.length&&p(r),!t)return J.apply(n,i),n;break}}return(u||$(t,f))(i,e,!L,n,!e||yt.test(t)&&c(e.parentNode)||e),n},w.sortStable=F.split("").sort(V).join("")===F,w.detectDuplicates=!!j,A(),w.sortDetached=o(function(t){return 1&t.compareDocumentPosition(O.createElement("div"))}),o(function(t){return t.innerHTML="<a href='#'></a>","#"===t.firstChild.getAttribute("href")})||r("type|href|height|width",function(t,e,n){return n?void 0:t.getAttribute(e,"type"===e.toLowerCase()?1:2)}),w.attributes&&o(function(t){return t.innerHTML="<input/>",t.firstChild.setAttribute("value",""),""===t.firstChild.getAttribute("value")})||r("value",function(t,e,n){return n||"input"!==t.nodeName.toLowerCase()?void 0:t.defaultValue}),o(function(t){return null==t.getAttribute("disabled")})||r(et,function(t,e,n){var i;return n?void 0:t[e]===!0?e.toLowerCase():(i=t.getAttributeNode(e))&&i.specified?i.value:null}),e}(t);rt.find=ct,rt.expr=ct.selectors,rt.expr[":"]=rt.expr.pseudos,rt.uniqueSort=rt.unique=ct.uniqueSort,rt.text=ct.getText,rt.isXMLDoc=ct.isXML,rt.contains=ct.contains;var ft=function(t,e,n){for(var i=[],o=void 0!==n;(t=t[e])&&9!==t.nodeType;)if(1===t.nodeType){if(o&&rt(t).is(n))break;i.push(t)}return i},pt=function(t,e){for(var n=[];t;t=t.nextSibling)1===t.nodeType&&t!==e&&n.push(t);return n},dt=rt.expr.match.needsContext,ht=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,gt=/^.[^:#\[\.,]*$/;rt.filter=function(t,e,n){var i=e[0];return n&&(t=":not("+t+")"),1===e.length&&1===i.nodeType?rt.find.matchesSelector(i,t)?[i]:[]:rt.find.matches(t,rt.grep(e,function(t){return 1===t.nodeType}))},rt.fn.extend({find:function(t){var e,n=this.length,i=[],o=this;if("string"!=typeof t)return this.pushStack(rt(t).filter(function(){for(e=0;n>e;e++)if(rt.contains(o[e],this))return!0}));for(e=0;n>e;e++)rt.find(t,o[e],i);return i=this.pushStack(n>1?rt.unique(i):i),i.selector=this.selector?this.selector+" "+t:t,i},filter:function(t){return this.pushStack(i(this,t||[],!1))},not:function(t){return this.pushStack(i(this,t||[],!0))},is:function(t){return!!i(this,"string"==typeof t&&dt.test(t)?rt(t):t||[],!1).length}});var vt,mt=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,yt=rt.fn.init=function(t,e,n){var i,o;if(!t)return this;if(n=n||vt,"string"==typeof t){if(i="<"===t[0]&&">"===t[t.length-1]&&t.length>=3?[null,t,null]:mt.exec(t),!i||!i[1]&&e)return!e||e.jquery?(e||n).find(t):this.constructor(e).find(t);if(i[1]){if(e=e instanceof rt?e[0]:e,rt.merge(this,rt.parseHTML(i[1],e&&e.nodeType?e.ownerDocument||e:Y,!0)),ht.test(i[1])&&rt.isPlainObject(e))for(i in e)rt.isFunction(this[i])?this[i](e[i]):this.attr(i,e[i]);return this}return o=Y.getElementById(i[2]),o&&o.parentNode&&(this.length=1,this[0]=o),this.context=Y,this.selector=t,this}return t.nodeType?(this.context=this[0]=t,this.length=1,this):rt.isFunction(t)?void 0!==n.ready?n.ready(t):t(rt):(void 0!==t.selector&&(this.selector=t.selector,this.context=t.context),rt.makeArray(t,this))};yt.prototype=rt.fn,vt=rt(Y);var bt=/^(?:parents|prev(?:Until|All))/,xt={children:!0,contents:!0,next:!0,prev:!0};rt.fn.extend({has:function(t){var e=rt(t,this),n=e.length;return this.filter(function(){for(var t=0;n>t;t++)if(rt.contains(this,e[t]))return!0})},closest:function(t,e){for(var n,i=0,o=this.length,r=[],s=dt.test(t)||"string"!=typeof t?rt(t,e||this.context):0;o>i;i++)for(n=this[i];n&&n!==e;n=n.parentNode)if(n.nodeType<11&&(s?s.index(n)>-1:1===n.nodeType&&rt.find.matchesSelector(n,t))){r.push(n);break}return this.pushStack(r.length>1?rt.uniqueSort(r):r)},index:function(t){return t?"string"==typeof t?Z.call(rt(t),this[0]):Z.call(this,t.jquery?t[0]:t):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(t,e){return this.pushStack(rt.uniqueSort(rt.merge(this.get(),rt(t,e))))},addBack:function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}}),rt.each({parent:function(t){var e=t.parentNode;return e&&11!==e.nodeType?e:null},parents:function(t){return ft(t,"parentNode")},parentsUntil:function(t,e,n){return ft(t,"parentNode",n)},next:function(t){return o(t,"nextSibling")},prev:function(t){return o(t,"previousSibling")},nextAll:function(t){return ft(t,"nextSibling")},prevAll:function(t){return ft(t,"previousSibling")},nextUntil:function(t,e,n){return ft(t,"nextSibling",n)},prevUntil:function(t,e,n){return ft(t,"previousSibling",n)},siblings:function(t){return pt((t.parentNode||{}).firstChild,t)},children:function(t){return pt(t.firstChild)},contents:function(t){return t.contentDocument||rt.merge([],t.childNodes)}},function(t,e){rt.fn[t]=function(n,i){var o=rt.map(this,e,n);return"Until"!==t.slice(-5)&&(i=n),i&&"string"==typeof i&&(o=rt.filter(i,o)),this.length>1&&(xt[t]||rt.uniqueSort(o),bt.test(t)&&o.reverse()),this.pushStack(o)}});var wt=/\S+/g;rt.Callbacks=function(t){t="string"==typeof t?r(t):rt.extend({},t);var e,n,i,o,s=[],a=[],l=-1,u=function(){for(o=t.once,i=e=!0;a.length;l=-1)for(n=a.shift();++l<s.length;)s[l].apply(n[0],n[1])===!1&&t.stopOnFalse&&(l=s.length,n=!1);t.memory||(n=!1),e=!1,o&&(s=n?[]:"")},c={add:function(){return s&&(n&&!e&&(l=s.length-1,a.push(n)),function i(e){rt.each(e,function(e,n){rt.isFunction(n)?t.unique&&c.has(n)||s.push(n):n&&n.length&&"string"!==rt.type(n)&&i(n)})}(arguments),n&&!e&&u()),this},remove:function(){return rt.each(arguments,function(t,e){for(var n;(n=rt.inArray(e,s,n))>-1;)s.splice(n,1),l>=n&&l--}),this},has:function(t){return t?rt.inArray(t,s)>-1:s.length>0},empty:function(){return s&&(s=[]),this},disable:function(){return o=a=[],s=n="",this},disabled:function(){return!s},lock:function(){return o=a=[],n||(s=n=""),this},locked:function(){return!!o},fireWith:function(t,n){return o||(n=n||[],n=[t,n.slice?n.slice():n],a.push(n),e||u()),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!i}};return c},rt.extend({Deferred:function(t){var e=[["resolve","done",rt.Callbacks("once memory"),"resolved"],["reject","fail",rt.Callbacks("once memory"),"rejected"],["notify","progress",rt.Callbacks("memory")]],n="pending",i={state:function(){return n},always:function(){return o.done(arguments).fail(arguments),this},then:function(){var t=arguments;return rt.Deferred(function(n){rt.each(e,function(e,r){var s=rt.isFunction(t[e])&&t[e];o[r[1]](function(){var t=s&&s.apply(this,arguments);t&&rt.isFunction(t.promise)?t.promise().progress(n.notify).done(n.resolve).fail(n.reject):n[r[0]+"With"](this===i?n.promise():this,s?[t]:arguments)})}),t=null}).promise()},promise:function(t){return null!=t?rt.extend(t,i):i}},o={};return i.pipe=i.then,rt.each(e,function(t,r){var s=r[2],a=r[3];i[r[1]]=s.add,a&&s.add(function(){n=a},e[1^t][2].disable,e[2][2].lock),o[r[0]]=function(){return o[r[0]+"With"](this===o?i:this,arguments),this},o[r[0]+"With"]=s.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(t){var e,n,i,o=0,r=G.call(arguments),s=r.length,a=1!==s||t&&rt.isFunction(t.promise)?s:0,l=1===a?t:rt.Deferred(),u=function(t,n,i){return function(o){n[t]=this,i[t]=arguments.length>1?G.call(arguments):o,i===e?l.notifyWith(n,i):--a||l.resolveWith(n,i)}};if(s>1)for(e=new Array(s),n=new Array(s),i=new Array(s);s>o;o++)r[o]&&rt.isFunction(r[o].promise)?r[o].promise().progress(u(o,n,e)).done(u(o,i,r)).fail(l.reject):--a;return a||l.resolveWith(i,r),l.promise()}});var Tt;rt.fn.ready=function(t){return rt.ready.promise().done(t),this},rt.extend({isReady:!1,readyWait:1,holdReady:function(t){t?rt.readyWait++:rt.ready(!0)},ready:function(t){(t===!0?--rt.readyWait:rt.isReady)||(rt.isReady=!0,t!==!0&&--rt.readyWait>0||(Tt.resolveWith(Y,[rt]),rt.fn.triggerHandler&&(rt(Y).triggerHandler("ready"),rt(Y).off("ready"))))}}),rt.ready.promise=function(e){return Tt||(Tt=rt.Deferred(),"complete"===Y.readyState||"loading"!==Y.readyState&&!Y.documentElement.doScroll?t.setTimeout(rt.ready):(Y.addEventListener("DOMContentLoaded",s),t.addEventListener("load",s))),Tt.promise(e)},rt.ready.promise();var Ct=function(t,e,n,i,o,r,s){var a=0,l=t.length,u=null==n;if("object"===rt.type(n)){o=!0;for(a in n)Ct(t,e,a,n[a],!0,r,s)}else if(void 0!==i&&(o=!0,rt.isFunction(i)||(s=!0),u&&(s?(e.call(t,i),e=null):(u=e,e=function(t,e,n){return u.call(rt(t),n)})),e))for(;l>a;a++)e(t[a],n,s?i:i.call(t[a],a,e(t[a],n)));return o?t:u?e.call(t):l?e(t[0],n):r},Et=function(t){return 1===t.nodeType||9===t.nodeType||!+t.nodeType};a.uid=1,a.prototype={register:function(t,e){var n=e||{};return t.nodeType?t[this.expando]=n:Object.defineProperty(t,this.expando,{value:n,writable:!0,configurable:!0}),t[this.expando]},cache:function(t){if(!Et(t))return{};var e=t[this.expando];return e||(e={},Et(t)&&(t.nodeType?t[this.expando]=e:Object.defineProperty(t,this.expando,{value:e,configurable:!0}))),e},set:function(t,e,n){var i,o=this.cache(t);if("string"==typeof e)o[e]=n;else for(i in e)o[i]=e[i];return o},get:function(t,e){return void 0===e?this.cache(t):t[this.expando]&&t[this.expando][e]},access:function(t,e,n){var i;return void 0===e||e&&"string"==typeof e&&void 0===n?(i=this.get(t,e),void 0!==i?i:this.get(t,rt.camelCase(e))):(this.set(t,e,n),void 0!==n?n:e)},remove:function(t,e){var n,i,o,r=t[this.expando];if(void 0!==r){if(void 0===e)this.register(t);else{rt.isArray(e)?i=e.concat(e.map(rt.camelCase)):(o=rt.camelCase(e),e in r?i=[e,o]:(i=o,i=i in r?[i]:i.match(wt)||[])),n=i.length;for(;n--;)delete r[i[n]]}(void 0===e||rt.isEmptyObject(r))&&(t.nodeType?t[this.expando]=void 0:delete t[this.expando])}},hasData:function(t){var e=t[this.expando];return void 0!==e&&!rt.isEmptyObject(e)}};var kt=new a,$t=new a,St=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Nt=/[A-Z]/g;rt.extend({hasData:function(t){return $t.hasData(t)||kt.hasData(t)},data:function(t,e,n){return $t.access(t,e,n)},removeData:function(t,e){$t.remove(t,e)},_data:function(t,e,n){return kt.access(t,e,n)},_removeData:function(t,e){kt.remove(t,e)}}),rt.fn.extend({data:function(t,e){var n,i,o,r=this[0],s=r&&r.attributes;if(void 0===t){if(this.length&&(o=$t.get(r),1===r.nodeType&&!kt.get(r,"hasDataAttrs"))){for(n=s.length;n--;)s[n]&&(i=s[n].name,0===i.indexOf("data-")&&(i=rt.camelCase(i.slice(5)),l(r,i,o[i])));kt.set(r,"hasDataAttrs",!0)}return o}return"object"==typeof t?this.each(function(){$t.set(this,t)}):Ct(this,function(e){var n,i;if(r&&void 0===e){if(n=$t.get(r,t)||$t.get(r,t.replace(Nt,"-$&").toLowerCase()),void 0!==n)return n;if(i=rt.camelCase(t),n=$t.get(r,i),void 0!==n)return n;if(n=l(r,i,void 0),void 0!==n)return n}else i=rt.camelCase(t),this.each(function(){var n=$t.get(this,i);$t.set(this,i,e),t.indexOf("-")>-1&&void 0!==n&&$t.set(this,t,e)})},null,e,arguments.length>1,null,!0)},removeData:function(t){return this.each(function(){$t.remove(this,t)})}}),rt.extend({queue:function(t,e,n){var i;return t?(e=(e||"fx")+"queue",i=kt.get(t,e),n&&(!i||rt.isArray(n)?i=kt.access(t,e,rt.makeArray(n)):i.push(n)),i||[]):void 0},dequeue:function(t,e){e=e||"fx";var n=rt.queue(t,e),i=n.length,o=n.shift(),r=rt._queueHooks(t,e),s=function(){rt.dequeue(t,e)};"inprogress"===o&&(o=n.shift(),i--),o&&("fx"===e&&n.unshift("inprogress"),delete r.stop,o.call(t,s,r)),!i&&r&&r.empty.fire()},_queueHooks:function(t,e){var n=e+"queueHooks";return kt.get(t,n)||kt.access(t,n,{empty:rt.Callbacks("once memory").add(function(){kt.remove(t,[e+"queue",n])})})}}),rt.fn.extend({queue:function(t,e){var n=2;return"string"!=typeof t&&(e=t,t="fx",n--),arguments.length<n?rt.queue(this[0],t):void 0===e?this:this.each(function(){var n=rt.queue(this,t,e);rt._queueHooks(this,t),"fx"===t&&"inprogress"!==n[0]&&rt.dequeue(this,t)})},dequeue:function(t){return this.each(function(){rt.dequeue(this,t)})},clearQueue:function(t){return this.queue(t||"fx",[])},promise:function(t,e){var n,i=1,o=rt.Deferred(),r=this,s=this.length,a=function(){--i||o.resolveWith(r,[r])};for("string"!=typeof t&&(e=t,t=void 0),t=t||"fx";s--;)n=kt.get(r[s],t+"queueHooks"),n&&n.empty&&(i++,n.empty.add(a));return a(),o.promise(e)}});var Dt=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,jt=new RegExp("^(?:([+-])=|)("+Dt+")([a-z%]*)$","i"),At=["Top","Right","Bottom","Left"],Ot=function(t,e){return t=e||t,"none"===rt.css(t,"display")||!rt.contains(t.ownerDocument,t)},It=/^(?:checkbox|radio)$/i,Lt=/<([\w:-]+)/,Rt=/^$|\/(?:java|ecma)script/i,qt={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};qt.optgroup=qt.option,qt.tbody=qt.tfoot=qt.colgroup=qt.caption=qt.thead,qt.th=qt.td;var Ht=/<|&#?\w+;/;!function(){var t=Y.createDocumentFragment(),e=t.appendChild(Y.createElement("div")),n=Y.createElement("input");n.setAttribute("type","radio"),n.setAttribute("checked","checked"),n.setAttribute("name","t"),e.appendChild(n),it.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,e.innerHTML="<textarea>x</textarea>",it.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue}();var Pt=/^key/,Ft=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Mt=/^([^.]*)(?:\.(.+)|)/;rt.event={global:{},add:function(t,e,n,i,o){var r,s,a,l,u,c,f,p,d,h,g,v=kt.get(t);if(v)for(n.handler&&(r=n,n=r.handler,o=r.selector),n.guid||(n.guid=rt.guid++),(l=v.events)||(l=v.events={}),(s=v.handle)||(s=v.handle=function(e){return"undefined"!=typeof rt&&rt.event.triggered!==e.type?rt.event.dispatch.apply(t,arguments):void 0}),e=(e||"").match(wt)||[""],u=e.length;u--;)a=Mt.exec(e[u])||[],d=g=a[1],h=(a[2]||"").split(".").sort(),d&&(f=rt.event.special[d]||{},d=(o?f.delegateType:f.bindType)||d,f=rt.event.special[d]||{},c=rt.extend({type:d,origType:g,data:i,handler:n,guid:n.guid,selector:o,needsContext:o&&rt.expr.match.needsContext.test(o),namespace:h.join(".")},r),(p=l[d])||(p=l[d]=[],p.delegateCount=0,f.setup&&f.setup.call(t,i,h,s)!==!1||t.addEventListener&&t.addEventListener(d,s)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),o?p.splice(p.delegateCount++,0,c):p.push(c),rt.event.global[d]=!0)},remove:function(t,e,n,i,o){var r,s,a,l,u,c,f,p,d,h,g,v=kt.hasData(t)&&kt.get(t);if(v&&(l=v.events)){for(e=(e||"").match(wt)||[""],u=e.length;u--;)if(a=Mt.exec(e[u])||[],d=g=a[1],h=(a[2]||"").split(".").sort(),d){for(f=rt.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,p=l[d]||[],a=a[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),s=r=p.length;r--;)c=p[r],!o&&g!==c.origType||n&&n.guid!==c.guid||a&&!a.test(c.namespace)||i&&i!==c.selector&&("**"!==i||!c.selector)||(p.splice(r,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(t,c));s&&!p.length&&(f.teardown&&f.teardown.call(t,h,v.handle)!==!1||rt.removeEvent(t,d,v.handle),delete l[d])}else for(d in l)rt.event.remove(t,d+e[u],n,i,!0);rt.isEmptyObject(l)&&kt.remove(t,"handle events")}},dispatch:function(t){t=rt.event.fix(t);var e,n,i,o,r,s=[],a=G.call(arguments),l=(kt.get(this,"events")||{})[t.type]||[],u=rt.event.special[t.type]||{};if(a[0]=t,t.delegateTarget=this,!u.preDispatch||u.preDispatch.call(this,t)!==!1){for(s=rt.event.handlers.call(this,t,l),e=0;(o=s[e++])&&!t.isPropagationStopped();)for(t.currentTarget=o.elem,n=0;(r=o.handlers[n++])&&!t.isImmediatePropagationStopped();)t.rnamespace&&!t.rnamespace.test(r.namespace)||(t.handleObj=r,t.data=r.data,i=((rt.event.special[r.origType]||{}).handle||r.handler).apply(o.elem,a),void 0!==i&&(t.result=i)===!1&&(t.preventDefault(),t.stopPropagation()));return u.postDispatch&&u.postDispatch.call(this,t),t.result}},handlers:function(t,e){var n,i,o,r,s=[],a=e.delegateCount,l=t.target;if(a&&l.nodeType&&("click"!==t.type||isNaN(t.button)||t.button<1))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&(l.disabled!==!0||"click"!==t.type)){for(i=[],n=0;a>n;n++)r=e[n],o=r.selector+" ",void 0===i[o]&&(i[o]=r.needsContext?rt(o,this).index(l)>-1:rt.find(o,this,null,[l]).length),i[o]&&i.push(r);i.length&&s.push({elem:l,handlers:i})}return a<e.length&&s.push({elem:this,handlers:e.slice(a)}),s},props:"altKey bubbles cancelable ctrlKey currentTarget detail eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(t,e){return null==t.which&&(t.which=null!=e.charCode?e.charCode:e.keyCode),t}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(t,e){var n,i,o,r=e.button;return null==t.pageX&&null!=e.clientX&&(n=t.target.ownerDocument||Y,i=n.documentElement,o=n.body,t.pageX=e.clientX+(i&&i.scrollLeft||o&&o.scrollLeft||0)-(i&&i.clientLeft||o&&o.clientLeft||0),t.pageY=e.clientY+(i&&i.scrollTop||o&&o.scrollTop||0)-(i&&i.clientTop||o&&o.clientTop||0)),t.which||void 0===r||(t.which=1&r?1:2&r?3:4&r?2:0),t}},fix:function(t){if(t[rt.expando])return t;var e,n,i,o=t.type,r=t,s=this.fixHooks[o];for(s||(this.fixHooks[o]=s=Ft.test(o)?this.mouseHooks:Pt.test(o)?this.keyHooks:{}),i=s.props?this.props.concat(s.props):this.props,t=new rt.Event(r),e=i.length;e--;)n=i[e],t[n]=r[n];return t.target||(t.target=Y),3===t.target.nodeType&&(t.target=t.target.parentNode),s.filter?s.filter(t,r):t},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==g()&&this.focus?(this.focus(),!1):void 0},delegateType:"focusin"},blur:{trigger:function(){return this===g()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&rt.nodeName(this,"input")?(this.click(),!1):void 0},_default:function(t){return rt.nodeName(t.target,"a")}},beforeunload:{postDispatch:function(t){void 0!==t.result&&t.originalEvent&&(t.originalEvent.returnValue=t.result)}}}},rt.removeEvent=function(t,e,n){t.removeEventListener&&t.removeEventListener(e,n)},rt.Event=function(t,e){return this instanceof rt.Event?(t&&t.type?(this.originalEvent=t,this.type=t.type,this.isDefaultPrevented=t.defaultPrevented||void 0===t.defaultPrevented&&t.returnValue===!1?d:h):this.type=t,e&&rt.extend(this,e),this.timeStamp=t&&t.timeStamp||rt.now(),void(this[rt.expando]=!0)):new rt.Event(t,e)},rt.Event.prototype={constructor:rt.Event,isDefaultPrevented:h,isPropagationStopped:h,isImmediatePropagationStopped:h,preventDefault:function(){var t=this.originalEvent;this.isDefaultPrevented=d,t&&t.preventDefault()},stopPropagation:function(){var t=this.originalEvent;this.isPropagationStopped=d,t&&t.stopPropagation()},stopImmediatePropagation:function(){var t=this.originalEvent;this.isImmediatePropagationStopped=d,t&&t.stopImmediatePropagation(),this.stopPropagation()}},rt.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(t,e){rt.event.special[t]={delegateType:e,bindType:e,handle:function(t){var n,i=this,o=t.relatedTarget,r=t.handleObj;return o&&(o===i||rt.contains(i,o))||(t.type=r.origType,n=r.handler.apply(this,arguments),t.type=e),n}}}),rt.fn.extend({on:function(t,e,n,i){return v(this,t,e,n,i)},one:function(t,e,n,i){return v(this,t,e,n,i,1)},off:function(t,e,n){var i,o;if(t&&t.preventDefault&&t.handleObj)return i=t.handleObj,rt(t.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof t){for(o in t)this.off(o,e,t[o]);return this}return e!==!1&&"function"!=typeof e||(n=e,e=void 0),n===!1&&(n=h),this.each(function(){rt.event.remove(this,t,n,e)})}});var Wt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,_t=/<script|<style|<link/i,Bt=/checked\s*(?:[^=]|=\s*.checked.)/i,Ut=/^true\/(.*)/,zt=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;rt.extend({htmlPrefilter:function(t){return t.replace(Wt,"<$1></$2>")},clone:function(t,e,n){var i,o,r,s,a=t.cloneNode(!0),l=rt.contains(t.ownerDocument,t);if(!(it.noCloneChecked||1!==t.nodeType&&11!==t.nodeType||rt.isXMLDoc(t)))for(s=c(a),r=c(t),i=0,o=r.length;o>i;i++)w(r[i],s[i]);if(e)if(n)for(r=r||c(t),s=s||c(a),i=0,o=r.length;o>i;i++)x(r[i],s[i]);else x(t,a);return s=c(a,"script"),s.length>0&&f(s,!l&&c(t,"script")),a},cleanData:function(t){for(var e,n,i,o=rt.event.special,r=0;void 0!==(n=t[r]);r++)if(Et(n)){if(e=n[kt.expando]){if(e.events)for(i in e.events)o[i]?rt.event.remove(n,i):rt.removeEvent(n,i,e.handle);n[kt.expando]=void 0}n[$t.expando]&&(n[$t.expando]=void 0)}}}),rt.fn.extend({domManip:T,detach:function(t){return C(this,t,!0)},remove:function(t){return C(this,t)},text:function(t){return Ct(this,function(t){return void 0===t?rt.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=t)})},null,t,arguments.length)},append:function(){return T(this,arguments,function(t){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var e=m(this,t);e.appendChild(t)}})},prepend:function(){return T(this,arguments,function(t){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var e=m(this,t);e.insertBefore(t,e.firstChild)}})},before:function(){return T(this,arguments,function(t){this.parentNode&&this.parentNode.insertBefore(t,this)})},after:function(){return T(this,arguments,function(t){this.parentNode&&this.parentNode.insertBefore(t,this.nextSibling)})},empty:function(){for(var t,e=0;null!=(t=this[e]);e++)1===t.nodeType&&(rt.cleanData(c(t,!1)),t.textContent="");return this},clone:function(t,e){return t=null==t?!1:t,e=null==e?t:e,this.map(function(){return rt.clone(this,t,e)})},html:function(t){return Ct(this,function(t){var e=this[0]||{},n=0,i=this.length;if(void 0===t&&1===e.nodeType)return e.innerHTML;if("string"==typeof t&&!_t.test(t)&&!qt[(Lt.exec(t)||["",""])[1].toLowerCase()]){t=rt.htmlPrefilter(t);try{for(;i>n;n++)e=this[n]||{},1===e.nodeType&&(rt.cleanData(c(e,!1)),e.innerHTML=t);e=0}catch(o){}}e&&this.empty().append(t)},null,t,arguments.length)},replaceWith:function(){var t=[];return T(this,arguments,function(e){var n=this.parentNode;rt.inArray(this,t)<0&&(rt.cleanData(c(this)),n&&n.replaceChild(e,this))},t)}}),rt.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(t,e){rt.fn[t]=function(t){for(var n,i=[],o=rt(t),r=o.length-1,s=0;r>=s;s++)n=s===r?this:this.clone(!0),rt(o[s])[e](n),J.apply(i,n.get());return this.pushStack(i)}});var Vt,Xt={HTML:"block",BODY:"block"},Qt=/^margin/,Yt=new RegExp("^("+Dt+")(?!px)[a-z%]+$","i"),Gt=function(e){var n=e.ownerDocument.defaultView;return n&&n.opener||(n=t),n.getComputedStyle(e)},Kt=function(t,e,n,i){var o,r,s={};for(r in e)s[r]=t.style[r],t.style[r]=e[r];o=n.apply(t,i||[]);for(r in e)t.style[r]=s[r];return o},Jt=Y.documentElement;!function(){function e(){a.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;position:relative;display:block;margin:auto;border:1px;padding:1px;top:1%;width:50%",a.innerHTML="",Jt.appendChild(s);var e=t.getComputedStyle(a);n="1%"!==e.top,r="2px"===e.marginLeft,i="4px"===e.width,a.style.marginRight="50%",o="4px"===e.marginRight,Jt.removeChild(s)}var n,i,o,r,s=Y.createElement("div"),a=Y.createElement("div");a.style&&(a.style.backgroundClip="content-box",a.cloneNode(!0).style.backgroundClip="",it.clearCloneStyle="content-box"===a.style.backgroundClip,s.style.cssText="border:0;width:8px;height:0;top:0;left:-9999px;padding:0;margin-top:1px;position:absolute",s.appendChild(a),rt.extend(it,{pixelPosition:function(){return e(),n},boxSizingReliable:function(){return null==i&&e(),i},pixelMarginRight:function(){return null==i&&e(),o},reliableMarginLeft:function(){return null==i&&e(),r},reliableMarginRight:function(){var e,n=a.appendChild(Y.createElement("div"));return n.style.cssText=a.style.cssText="-webkit-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",n.style.marginRight=n.style.width="0",a.style.width="1px",Jt.appendChild(s),e=!parseFloat(t.getComputedStyle(n).marginRight),Jt.removeChild(s),a.removeChild(n),e}}))}();var Zt=/^(none|table(?!-c[ea]).+)/,te={position:"absolute",visibility:"hidden",display:"block"},ee={letterSpacing:"0",fontWeight:"400"},ne=["Webkit","O","Moz","ms"],ie=Y.createElement("div").style;rt.extend({cssHooks:{opacity:{get:function(t,e){if(e){var n=$(t,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(t,e,n,i){if(t&&3!==t.nodeType&&8!==t.nodeType&&t.style){var o,r,s,a=rt.camelCase(e),l=t.style;return e=rt.cssProps[a]||(rt.cssProps[a]=N(a)||a),s=rt.cssHooks[e]||rt.cssHooks[a],void 0===n?s&&"get"in s&&void 0!==(o=s.get(t,!1,i))?o:l[e]:(r=typeof n,"string"===r&&(o=jt.exec(n))&&o[1]&&(n=u(t,e,o),r="number"),null!=n&&n===n&&("number"===r&&(n+=o&&o[3]||(rt.cssNumber[a]?"":"px")),it.clearCloneStyle||""!==n||0!==e.indexOf("background")||(l[e]="inherit"),s&&"set"in s&&void 0===(n=s.set(t,n,i))||(l[e]=n)),
-void 0)}},css:function(t,e,n,i){var o,r,s,a=rt.camelCase(e);return e=rt.cssProps[a]||(rt.cssProps[a]=N(a)||a),s=rt.cssHooks[e]||rt.cssHooks[a],s&&"get"in s&&(o=s.get(t,!0,n)),void 0===o&&(o=$(t,e,i)),"normal"===o&&e in ee&&(o=ee[e]),""===n||n?(r=parseFloat(o),n===!0||isFinite(r)?r||0:o):o}}),rt.each(["height","width"],function(t,e){rt.cssHooks[e]={get:function(t,n,i){return n?Zt.test(rt.css(t,"display"))&&0===t.offsetWidth?Kt(t,te,function(){return A(t,e,i)}):A(t,e,i):void 0},set:function(t,n,i){var o,r=i&&Gt(t),s=i&&j(t,e,i,"border-box"===rt.css(t,"boxSizing",!1,r),r);return s&&(o=jt.exec(n))&&"px"!==(o[3]||"px")&&(t.style[e]=n,n=rt.css(t,e)),D(t,n,s)}}}),rt.cssHooks.marginLeft=S(it.reliableMarginLeft,function(t,e){return e?(parseFloat($(t,"marginLeft"))||t.getBoundingClientRect().left-Kt(t,{marginLeft:0},function(){return t.getBoundingClientRect().left}))+"px":void 0}),rt.cssHooks.marginRight=S(it.reliableMarginRight,function(t,e){return e?Kt(t,{display:"inline-block"},$,[t,"marginRight"]):void 0}),rt.each({margin:"",padding:"",border:"Width"},function(t,e){rt.cssHooks[t+e]={expand:function(n){for(var i=0,o={},r="string"==typeof n?n.split(" "):[n];4>i;i++)o[t+At[i]+e]=r[i]||r[i-2]||r[0];return o}},Qt.test(t)||(rt.cssHooks[t+e].set=D)}),rt.fn.extend({css:function(t,e){return Ct(this,function(t,e,n){var i,o,r={},s=0;if(rt.isArray(e)){for(i=Gt(t),o=e.length;o>s;s++)r[e[s]]=rt.css(t,e[s],!1,i);return r}return void 0!==n?rt.style(t,e,n):rt.css(t,e)},t,e,arguments.length>1)},show:function(){return O(this,!0)},hide:function(){return O(this)},toggle:function(t){return"boolean"==typeof t?t?this.show():this.hide():this.each(function(){Ot(this)?rt(this).show():rt(this).hide()})}}),rt.Tween=I,I.prototype={constructor:I,init:function(t,e,n,i,o,r){this.elem=t,this.prop=n,this.easing=o||rt.easing._default,this.options=e,this.start=this.now=this.cur(),this.end=i,this.unit=r||(rt.cssNumber[n]?"":"px")},cur:function(){var t=I.propHooks[this.prop];return t&&t.get?t.get(this):I.propHooks._default.get(this)},run:function(t){var e,n=I.propHooks[this.prop];return this.options.duration?this.pos=e=rt.easing[this.easing](t,this.options.duration*t,0,1,this.options.duration):this.pos=e=t,this.now=(this.end-this.start)*e+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):I.propHooks._default.set(this),this}},I.prototype.init.prototype=I.prototype,I.propHooks={_default:{get:function(t){var e;return 1!==t.elem.nodeType||null!=t.elem[t.prop]&&null==t.elem.style[t.prop]?t.elem[t.prop]:(e=rt.css(t.elem,t.prop,""),e&&"auto"!==e?e:0)},set:function(t){rt.fx.step[t.prop]?rt.fx.step[t.prop](t):1!==t.elem.nodeType||null==t.elem.style[rt.cssProps[t.prop]]&&!rt.cssHooks[t.prop]?t.elem[t.prop]=t.now:rt.style(t.elem,t.prop,t.now+t.unit)}}},I.propHooks.scrollTop=I.propHooks.scrollLeft={set:function(t){t.elem.nodeType&&t.elem.parentNode&&(t.elem[t.prop]=t.now)}},rt.easing={linear:function(t){return t},swing:function(t){return.5-Math.cos(t*Math.PI)/2},_default:"swing"},rt.fx=I.prototype.init,rt.fx.step={};var oe,re,se=/^(?:toggle|show|hide)$/,ae=/queueHooks$/;rt.Animation=rt.extend(F,{tweeners:{"*":[function(t,e){var n=this.createTween(t,e);return u(n.elem,t,jt.exec(e),n),n}]},tweener:function(t,e){rt.isFunction(t)?(e=t,t=["*"]):t=t.match(wt);for(var n,i=0,o=t.length;o>i;i++)n=t[i],F.tweeners[n]=F.tweeners[n]||[],F.tweeners[n].unshift(e)},prefilters:[H],prefilter:function(t,e){e?F.prefilters.unshift(t):F.prefilters.push(t)}}),rt.speed=function(t,e,n){var i=t&&"object"==typeof t?rt.extend({},t):{complete:n||!n&&e||rt.isFunction(t)&&t,duration:t,easing:n&&e||e&&!rt.isFunction(e)&&e};return i.duration=rt.fx.off?0:"number"==typeof i.duration?i.duration:i.duration in rt.fx.speeds?rt.fx.speeds[i.duration]:rt.fx.speeds._default,null!=i.queue&&i.queue!==!0||(i.queue="fx"),i.old=i.complete,i.complete=function(){rt.isFunction(i.old)&&i.old.call(this),i.queue&&rt.dequeue(this,i.queue)},i},rt.fn.extend({fadeTo:function(t,e,n,i){return this.filter(Ot).css("opacity",0).show().end().animate({opacity:e},t,n,i)},animate:function(t,e,n,i){var o=rt.isEmptyObject(t),r=rt.speed(e,n,i),s=function(){var e=F(this,rt.extend({},t),r);(o||kt.get(this,"finish"))&&e.stop(!0)};return s.finish=s,o||r.queue===!1?this.each(s):this.queue(r.queue,s)},stop:function(t,e,n){var i=function(t){var e=t.stop;delete t.stop,e(n)};return"string"!=typeof t&&(n=e,e=t,t=void 0),e&&t!==!1&&this.queue(t||"fx",[]),this.each(function(){var e=!0,o=null!=t&&t+"queueHooks",r=rt.timers,s=kt.get(this);if(o)s[o]&&s[o].stop&&i(s[o]);else for(o in s)s[o]&&s[o].stop&&ae.test(o)&&i(s[o]);for(o=r.length;o--;)r[o].elem!==this||null!=t&&r[o].queue!==t||(r[o].anim.stop(n),e=!1,r.splice(o,1));!e&&n||rt.dequeue(this,t)})},finish:function(t){return t!==!1&&(t=t||"fx"),this.each(function(){var e,n=kt.get(this),i=n[t+"queue"],o=n[t+"queueHooks"],r=rt.timers,s=i?i.length:0;for(n.finish=!0,rt.queue(this,t,[]),o&&o.stop&&o.stop.call(this,!0),e=r.length;e--;)r[e].elem===this&&r[e].queue===t&&(r[e].anim.stop(!0),r.splice(e,1));for(e=0;s>e;e++)i[e]&&i[e].finish&&i[e].finish.call(this);delete n.finish})}}),rt.each(["toggle","show","hide"],function(t,e){var n=rt.fn[e];rt.fn[e]=function(t,i,o){return null==t||"boolean"==typeof t?n.apply(this,arguments):this.animate(R(e,!0),t,i,o)}}),rt.each({slideDown:R("show"),slideUp:R("hide"),slideToggle:R("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(t,e){rt.fn[t]=function(t,n,i){return this.animate(e,t,n,i)}}),rt.timers=[],rt.fx.tick=function(){var t,e=0,n=rt.timers;for(oe=rt.now();e<n.length;e++)t=n[e],t()||n[e]!==t||n.splice(e--,1);n.length||rt.fx.stop(),oe=void 0},rt.fx.timer=function(t){rt.timers.push(t),t()?rt.fx.start():rt.timers.pop()},rt.fx.interval=13,rt.fx.start=function(){re||(re=t.setInterval(rt.fx.tick,rt.fx.interval))},rt.fx.stop=function(){t.clearInterval(re),re=null},rt.fx.speeds={slow:600,fast:200,_default:400},rt.fn.delay=function(e,n){return e=rt.fx?rt.fx.speeds[e]||e:e,n=n||"fx",this.queue(n,function(n,i){var o=t.setTimeout(n,e);i.stop=function(){t.clearTimeout(o)}})},function(){var t=Y.createElement("input"),e=Y.createElement("select"),n=e.appendChild(Y.createElement("option"));t.type="checkbox",it.checkOn=""!==t.value,it.optSelected=n.selected,e.disabled=!0,it.optDisabled=!n.disabled,t=Y.createElement("input"),t.value="t",t.type="radio",it.radioValue="t"===t.value}();var le,ue=rt.expr.attrHandle;rt.fn.extend({attr:function(t,e){return Ct(this,rt.attr,t,e,arguments.length>1)},removeAttr:function(t){return this.each(function(){rt.removeAttr(this,t)})}}),rt.extend({attr:function(t,e,n){var i,o,r=t.nodeType;if(3!==r&&8!==r&&2!==r)return"undefined"==typeof t.getAttribute?rt.prop(t,e,n):(1===r&&rt.isXMLDoc(t)||(e=e.toLowerCase(),o=rt.attrHooks[e]||(rt.expr.match.bool.test(e)?le:void 0)),void 0!==n?null===n?void rt.removeAttr(t,e):o&&"set"in o&&void 0!==(i=o.set(t,n,e))?i:(t.setAttribute(e,n+""),n):o&&"get"in o&&null!==(i=o.get(t,e))?i:(i=rt.find.attr(t,e),null==i?void 0:i))},attrHooks:{type:{set:function(t,e){if(!it.radioValue&&"radio"===e&&rt.nodeName(t,"input")){var n=t.value;return t.setAttribute("type",e),n&&(t.value=n),e}}}},removeAttr:function(t,e){var n,i,o=0,r=e&&e.match(wt);if(r&&1===t.nodeType)for(;n=r[o++];)i=rt.propFix[n]||n,rt.expr.match.bool.test(n)&&(t[i]=!1),t.removeAttribute(n)}}),le={set:function(t,e,n){return e===!1?rt.removeAttr(t,n):t.setAttribute(n,n),n}},rt.each(rt.expr.match.bool.source.match(/\w+/g),function(t,e){var n=ue[e]||rt.find.attr;ue[e]=function(t,e,i){var o,r;return i||(r=ue[e],ue[e]=o,o=null!=n(t,e,i)?e.toLowerCase():null,ue[e]=r),o}});var ce=/^(?:input|select|textarea|button)$/i,fe=/^(?:a|area)$/i;rt.fn.extend({prop:function(t,e){return Ct(this,rt.prop,t,e,arguments.length>1)},removeProp:function(t){return this.each(function(){delete this[rt.propFix[t]||t]})}}),rt.extend({prop:function(t,e,n){var i,o,r=t.nodeType;if(3!==r&&8!==r&&2!==r)return 1===r&&rt.isXMLDoc(t)||(e=rt.propFix[e]||e,o=rt.propHooks[e]),void 0!==n?o&&"set"in o&&void 0!==(i=o.set(t,n,e))?i:t[e]=n:o&&"get"in o&&null!==(i=o.get(t,e))?i:t[e]},propHooks:{tabIndex:{get:function(t){var e=rt.find.attr(t,"tabindex");return e?parseInt(e,10):ce.test(t.nodeName)||fe.test(t.nodeName)&&t.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),it.optSelected||(rt.propHooks.selected={get:function(t){var e=t.parentNode;return e&&e.parentNode&&e.parentNode.selectedIndex,null}}),rt.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){rt.propFix[this.toLowerCase()]=this});var pe=/[\t\r\n\f]/g;rt.fn.extend({addClass:function(t){var e,n,i,o,r,s,a,l=0;if(rt.isFunction(t))return this.each(function(e){rt(this).addClass(t.call(this,e,M(this)))});if("string"==typeof t&&t)for(e=t.match(wt)||[];n=this[l++];)if(o=M(n),i=1===n.nodeType&&(" "+o+" ").replace(pe," ")){for(s=0;r=e[s++];)i.indexOf(" "+r+" ")<0&&(i+=r+" ");a=rt.trim(i),o!==a&&n.setAttribute("class",a)}return this},removeClass:function(t){var e,n,i,o,r,s,a,l=0;if(rt.isFunction(t))return this.each(function(e){rt(this).removeClass(t.call(this,e,M(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof t&&t)for(e=t.match(wt)||[];n=this[l++];)if(o=M(n),i=1===n.nodeType&&(" "+o+" ").replace(pe," ")){for(s=0;r=e[s++];)for(;i.indexOf(" "+r+" ")>-1;)i=i.replace(" "+r+" "," ");a=rt.trim(i),o!==a&&n.setAttribute("class",a)}return this},toggleClass:function(t,e){var n=typeof t;return"boolean"==typeof e&&"string"===n?e?this.addClass(t):this.removeClass(t):rt.isFunction(t)?this.each(function(n){rt(this).toggleClass(t.call(this,n,M(this),e),e)}):this.each(function(){var e,i,o,r;if("string"===n)for(i=0,o=rt(this),r=t.match(wt)||[];e=r[i++];)o.hasClass(e)?o.removeClass(e):o.addClass(e);else void 0!==t&&"boolean"!==n||(e=M(this),e&&kt.set(this,"__className__",e),this.setAttribute&&this.setAttribute("class",e||t===!1?"":kt.get(this,"__className__")||""))})},hasClass:function(t){var e,n,i=0;for(e=" "+t+" ";n=this[i++];)if(1===n.nodeType&&(" "+M(n)+" ").replace(pe," ").indexOf(e)>-1)return!0;return!1}});var de=/\r/g;rt.fn.extend({val:function(t){var e,n,i,o=this[0];{if(arguments.length)return i=rt.isFunction(t),this.each(function(n){var o;1===this.nodeType&&(o=i?t.call(this,n,rt(this).val()):t,null==o?o="":"number"==typeof o?o+="":rt.isArray(o)&&(o=rt.map(o,function(t){return null==t?"":t+""})),e=rt.valHooks[this.type]||rt.valHooks[this.nodeName.toLowerCase()],e&&"set"in e&&void 0!==e.set(this,o,"value")||(this.value=o))});if(o)return e=rt.valHooks[o.type]||rt.valHooks[o.nodeName.toLowerCase()],e&&"get"in e&&void 0!==(n=e.get(o,"value"))?n:(n=o.value,"string"==typeof n?n.replace(de,""):null==n?"":n)}}}),rt.extend({valHooks:{option:{get:function(t){return rt.trim(t.value)}},select:{get:function(t){for(var e,n,i=t.options,o=t.selectedIndex,r="select-one"===t.type||0>o,s=r?null:[],a=r?o+1:i.length,l=0>o?a:r?o:0;a>l;l++)if(n=i[l],(n.selected||l===o)&&(it.optDisabled?!n.disabled:null===n.getAttribute("disabled"))&&(!n.parentNode.disabled||!rt.nodeName(n.parentNode,"optgroup"))){if(e=rt(n).val(),r)return e;s.push(e)}return s},set:function(t,e){for(var n,i,o=t.options,r=rt.makeArray(e),s=o.length;s--;)i=o[s],(i.selected=rt.inArray(rt.valHooks.option.get(i),r)>-1)&&(n=!0);return n||(t.selectedIndex=-1),r}}}}),rt.each(["radio","checkbox"],function(){rt.valHooks[this]={set:function(t,e){return rt.isArray(e)?t.checked=rt.inArray(rt(t).val(),e)>-1:void 0}},it.checkOn||(rt.valHooks[this].get=function(t){return null===t.getAttribute("value")?"on":t.value})});var he=/^(?:focusinfocus|focusoutblur)$/;rt.extend(rt.event,{trigger:function(e,n,i,o){var r,s,a,l,u,c,f,p=[i||Y],d=nt.call(e,"type")?e.type:e,h=nt.call(e,"namespace")?e.namespace.split("."):[];if(s=a=i=i||Y,3!==i.nodeType&&8!==i.nodeType&&!he.test(d+rt.event.triggered)&&(d.indexOf(".")>-1&&(h=d.split("."),d=h.shift(),h.sort()),u=d.indexOf(":")<0&&"on"+d,e=e[rt.expando]?e:new rt.Event(d,"object"==typeof e&&e),e.isTrigger=o?2:3,e.namespace=h.join("."),e.rnamespace=e.namespace?new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,e.result=void 0,e.target||(e.target=i),n=null==n?[e]:rt.makeArray(n,[e]),f=rt.event.special[d]||{},o||!f.trigger||f.trigger.apply(i,n)!==!1)){if(!o&&!f.noBubble&&!rt.isWindow(i)){for(l=f.delegateType||d,he.test(l+d)||(s=s.parentNode);s;s=s.parentNode)p.push(s),a=s;a===(i.ownerDocument||Y)&&p.push(a.defaultView||a.parentWindow||t)}for(r=0;(s=p[r++])&&!e.isPropagationStopped();)e.type=r>1?l:f.bindType||d,c=(kt.get(s,"events")||{})[e.type]&&kt.get(s,"handle"),c&&c.apply(s,n),c=u&&s[u],c&&c.apply&&Et(s)&&(e.result=c.apply(s,n),e.result===!1&&e.preventDefault());return e.type=d,o||e.isDefaultPrevented()||f._default&&f._default.apply(p.pop(),n)!==!1||!Et(i)||u&&rt.isFunction(i[d])&&!rt.isWindow(i)&&(a=i[u],a&&(i[u]=null),rt.event.triggered=d,i[d](),rt.event.triggered=void 0,a&&(i[u]=a)),e.result}},simulate:function(t,e,n){var i=rt.extend(new rt.Event,n,{type:t,isSimulated:!0});rt.event.trigger(i,null,e),i.isDefaultPrevented()&&n.preventDefault()}}),rt.fn.extend({trigger:function(t,e){return this.each(function(){rt.event.trigger(t,e,this)})},triggerHandler:function(t,e){var n=this[0];return n?rt.event.trigger(t,e,n,!0):void 0}}),rt.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(t,e){rt.fn[e]=function(t,n){return arguments.length>0?this.on(e,null,t,n):this.trigger(e)}}),rt.fn.extend({hover:function(t,e){return this.mouseenter(t).mouseleave(e||t)}}),it.focusin="onfocusin"in t,it.focusin||rt.each({focus:"focusin",blur:"focusout"},function(t,e){var n=function(t){rt.event.simulate(e,t.target,rt.event.fix(t))};rt.event.special[e]={setup:function(){var i=this.ownerDocument||this,o=kt.access(i,e);o||i.addEventListener(t,n,!0),kt.access(i,e,(o||0)+1)},teardown:function(){var i=this.ownerDocument||this,o=kt.access(i,e)-1;o?kt.access(i,e,o):(i.removeEventListener(t,n,!0),kt.remove(i,e))}}});var ge=t.location,ve=rt.now(),me=/\?/;rt.parseJSON=function(t){return JSON.parse(t+"")},rt.parseXML=function(e){var n;if(!e||"string"!=typeof e)return null;try{n=(new t.DOMParser).parseFromString(e,"text/xml")}catch(i){n=void 0}return n&&!n.getElementsByTagName("parsererror").length||rt.error("Invalid XML: "+e),n};var ye=/#.*$/,be=/([?&])_=[^&]*/,xe=/^(.*?):[ \t]*([^\r\n]*)$/gm,we=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Te=/^(?:GET|HEAD)$/,Ce=/^\/\//,Ee={},ke={},$e="*/".concat("*"),Se=Y.createElement("a");Se.href=ge.href,rt.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:ge.href,type:"GET",isLocal:we.test(ge.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":$e,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":rt.parseJSON,"text xml":rt.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(t,e){return e?B(B(t,rt.ajaxSettings),e):B(rt.ajaxSettings,t)},ajaxPrefilter:W(Ee),ajaxTransport:W(ke),ajax:function(e,n){function i(e,n,i,a){var u,f,y,b,w,C=n;2!==x&&(x=2,l&&t.clearTimeout(l),o=void 0,s=a||"",T.readyState=e>0?4:0,u=e>=200&&300>e||304===e,i&&(b=U(p,T,i)),b=z(p,b,T,u),u?(p.ifModified&&(w=T.getResponseHeader("Last-Modified"),w&&(rt.lastModified[r]=w),w=T.getResponseHeader("etag"),w&&(rt.etag[r]=w)),204===e||"HEAD"===p.type?C="nocontent":304===e?C="notmodified":(C=b.state,f=b.data,y=b.error,u=!y)):(y=C,!e&&C||(C="error",0>e&&(e=0))),T.status=e,T.statusText=(n||C)+"",u?g.resolveWith(d,[f,C,T]):g.rejectWith(d,[T,C,y]),T.statusCode(m),m=void 0,c&&h.trigger(u?"ajaxSuccess":"ajaxError",[T,p,u?f:y]),v.fireWith(d,[T,C]),c&&(h.trigger("ajaxComplete",[T,p]),--rt.active||rt.event.trigger("ajaxStop")))}"object"==typeof e&&(n=e,e=void 0),n=n||{};var o,r,s,a,l,u,c,f,p=rt.ajaxSetup({},n),d=p.context||p,h=p.context&&(d.nodeType||d.jquery)?rt(d):rt.event,g=rt.Deferred(),v=rt.Callbacks("once memory"),m=p.statusCode||{},y={},b={},x=0,w="canceled",T={readyState:0,getResponseHeader:function(t){var e;if(2===x){if(!a)for(a={};e=xe.exec(s);)a[e[1].toLowerCase()]=e[2];e=a[t.toLowerCase()]}return null==e?null:e},getAllResponseHeaders:function(){return 2===x?s:null},setRequestHeader:function(t,e){var n=t.toLowerCase();return x||(t=b[n]=b[n]||t,y[t]=e),this},overrideMimeType:function(t){return x||(p.mimeType=t),this},statusCode:function(t){var e;if(t)if(2>x)for(e in t)m[e]=[m[e],t[e]];else T.always(t[T.status]);return this},abort:function(t){var e=t||w;return o&&o.abort(e),i(0,e),this}};if(g.promise(T).complete=v.add,T.success=T.done,T.error=T.fail,p.url=((e||p.url||ge.href)+"").replace(ye,"").replace(Ce,ge.protocol+"//"),p.type=n.method||n.type||p.method||p.type,p.dataTypes=rt.trim(p.dataType||"*").toLowerCase().match(wt)||[""],null==p.crossDomain){u=Y.createElement("a");try{u.href=p.url,u.href=u.href,p.crossDomain=Se.protocol+"//"+Se.host!=u.protocol+"//"+u.host}catch(C){p.crossDomain=!0}}if(p.data&&p.processData&&"string"!=typeof p.data&&(p.data=rt.param(p.data,p.traditional)),_(Ee,p,n,T),2===x)return T;c=rt.event&&p.global,c&&0===rt.active++&&rt.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!Te.test(p.type),r=p.url,p.hasContent||(p.data&&(r=p.url+=(me.test(r)?"&":"?")+p.data,delete p.data),p.cache===!1&&(p.url=be.test(r)?r.replace(be,"$1_="+ve++):r+(me.test(r)?"&":"?")+"_="+ve++)),p.ifModified&&(rt.lastModified[r]&&T.setRequestHeader("If-Modified-Since",rt.lastModified[r]),rt.etag[r]&&T.setRequestHeader("If-None-Match",rt.etag[r])),(p.data&&p.hasContent&&p.contentType!==!1||n.contentType)&&T.setRequestHeader("Content-Type",p.contentType),T.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+$e+"; q=0.01":""):p.accepts["*"]);for(f in p.headers)T.setRequestHeader(f,p.headers[f]);if(p.beforeSend&&(p.beforeSend.call(d,T,p)===!1||2===x))return T.abort();w="abort";for(f in{success:1,error:1,complete:1})T[f](p[f]);if(o=_(ke,p,n,T)){if(T.readyState=1,c&&h.trigger("ajaxSend",[T,p]),2===x)return T;p.async&&p.timeout>0&&(l=t.setTimeout(function(){T.abort("timeout")},p.timeout));try{x=1,o.send(y,i)}catch(C){if(!(2>x))throw C;i(-1,C)}}else i(-1,"No Transport");return T},getJSON:function(t,e,n){return rt.get(t,e,n,"json")},getScript:function(t,e){return rt.get(t,void 0,e,"script")}}),rt.each(["get","post"],function(t,e){rt[e]=function(t,n,i,o){return rt.isFunction(n)&&(o=o||i,i=n,n=void 0),rt.ajax(rt.extend({url:t,type:e,dataType:o,data:n,success:i},rt.isPlainObject(t)&&t))}}),rt._evalUrl=function(t){return rt.ajax({url:t,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},rt.fn.extend({wrapAll:function(t){var e;return rt.isFunction(t)?this.each(function(e){rt(this).wrapAll(t.call(this,e))}):(this[0]&&(e=rt(t,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&e.insertBefore(this[0]),e.map(function(){for(var t=this;t.firstElementChild;)t=t.firstElementChild;return t}).append(this)),this)},wrapInner:function(t){return rt.isFunction(t)?this.each(function(e){rt(this).wrapInner(t.call(this,e))}):this.each(function(){var e=rt(this),n=e.contents();n.length?n.wrapAll(t):e.append(t)})},wrap:function(t){var e=rt.isFunction(t);return this.each(function(n){rt(this).wrapAll(e?t.call(this,n):t)})},unwrap:function(){return this.parent().each(function(){rt.nodeName(this,"body")||rt(this).replaceWith(this.childNodes)}).end()}}),rt.expr.filters.hidden=function(t){return!rt.expr.filters.visible(t)},rt.expr.filters.visible=function(t){return t.offsetWidth>0||t.offsetHeight>0||t.getClientRects().length>0};var Ne=/%20/g,De=/\[\]$/,je=/\r?\n/g,Ae=/^(?:submit|button|image|reset|file)$/i,Oe=/^(?:input|select|textarea|keygen)/i;rt.param=function(t,e){var n,i=[],o=function(t,e){e=rt.isFunction(e)?e():null==e?"":e,i[i.length]=encodeURIComponent(t)+"="+encodeURIComponent(e)};if(void 0===e&&(e=rt.ajaxSettings&&rt.ajaxSettings.traditional),rt.isArray(t)||t.jquery&&!rt.isPlainObject(t))rt.each(t,function(){o(this.name,this.value)});else for(n in t)V(n,t[n],e,o);return i.join("&").replace(Ne,"+")},rt.fn.extend({serialize:function(){return rt.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var t=rt.prop(this,"elements");return t?rt.makeArray(t):this}).filter(function(){var t=this.type;return this.name&&!rt(this).is(":disabled")&&Oe.test(this.nodeName)&&!Ae.test(t)&&(this.checked||!It.test(t))}).map(function(t,e){var n=rt(this).val();return null==n?null:rt.isArray(n)?rt.map(n,function(t){return{name:e.name,value:t.replace(je,"\r\n")}}):{name:e.name,value:n.replace(je,"\r\n")}}).get()}}),rt.ajaxSettings.xhr=function(){try{return new t.XMLHttpRequest}catch(e){}};var Ie={0:200,1223:204},Le=rt.ajaxSettings.xhr();it.cors=!!Le&&"withCredentials"in Le,it.ajax=Le=!!Le,rt.ajaxTransport(function(e){var n,i;return it.cors||Le&&!e.crossDomain?{send:function(o,r){var s,a=e.xhr();if(a.open(e.type,e.url,e.async,e.username,e.password),e.xhrFields)for(s in e.xhrFields)a[s]=e.xhrFields[s];e.mimeType&&a.overrideMimeType&&a.overrideMimeType(e.mimeType),e.crossDomain||o["X-Requested-With"]||(o["X-Requested-With"]="XMLHttpRequest");for(s in o)a.setRequestHeader(s,o[s]);n=function(t){return function(){n&&(n=i=a.onload=a.onerror=a.onabort=a.onreadystatechange=null,"abort"===t?a.abort():"error"===t?"number"!=typeof a.status?r(0,"error"):r(a.status,a.statusText):r(Ie[a.status]||a.status,a.statusText,"text"!==(a.responseType||"text")||"string"!=typeof a.responseText?{binary:a.response}:{text:a.responseText},a.getAllResponseHeaders()))}},a.onload=n(),i=a.onerror=n("error"),void 0!==a.onabort?a.onabort=i:a.onreadystatechange=function(){4===a.readyState&&t.setTimeout(function(){n&&i()})},n=n("abort");try{a.send(e.hasContent&&e.data||null)}catch(l){if(n)throw l}},abort:function(){n&&n()}}:void 0}),rt.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(t){return rt.globalEval(t),t}}}),rt.ajaxPrefilter("script",function(t){void 0===t.cache&&(t.cache=!1),t.crossDomain&&(t.type="GET")}),rt.ajaxTransport("script",function(t){if(t.crossDomain){var e,n;return{send:function(i,o){e=rt("<script>").prop({charset:t.scriptCharset,src:t.url}).on("load error",n=function(t){e.remove(),n=null,t&&o("error"===t.type?404:200,t.type)}),Y.head.appendChild(e[0])},abort:function(){n&&n()}}}});var Re=[],qe=/(=)\?(?=&|$)|\?\?/;rt.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var t=Re.pop()||rt.expando+"_"+ve++;return this[t]=!0,t}}),rt.ajaxPrefilter("json jsonp",function(e,n,i){var o,r,s,a=e.jsonp!==!1&&(qe.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&qe.test(e.data)&&"data");return a||"jsonp"===e.dataTypes[0]?(o=e.jsonpCallback=rt.isFunction(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(qe,"$1"+o):e.jsonp!==!1&&(e.url+=(me.test(e.url)?"&":"?")+e.jsonp+"="+o),e.converters["script json"]=function(){return s||rt.error(o+" was not called"),s[0]},e.dataTypes[0]="json",r=t[o],t[o]=function(){s=arguments},i.always(function(){void 0===r?rt(t).removeProp(o):t[o]=r,e[o]&&(e.jsonpCallback=n.jsonpCallback,Re.push(o)),s&&rt.isFunction(r)&&r(s[0]),s=r=void 0}),"script"):void 0}),it.createHTMLDocument=function(){var t=Y.implementation.createHTMLDocument("").body;return t.innerHTML="<form></form><form></form>",2===t.childNodes.length}(),rt.parseHTML=function(t,e,n){if(!t||"string"!=typeof t)return null;"boolean"==typeof e&&(n=e,e=!1),e=e||(it.createHTMLDocument?Y.implementation.createHTMLDocument(""):Y);var i=ht.exec(t),o=!n&&[];return i?[e.createElement(i[1])]:(i=p([t],e,o),o&&o.length&&rt(o).remove(),rt.merge([],i.childNodes))};var He=rt.fn.load;rt.fn.load=function(t,e,n){if("string"!=typeof t&&He)return He.apply(this,arguments);var i,o,r,s=this,a=t.indexOf(" ");return a>-1&&(i=rt.trim(t.slice(a)),t=t.slice(0,a)),rt.isFunction(e)?(n=e,e=void 0):e&&"object"==typeof e&&(o="POST"),s.length>0&&rt.ajax({url:t,type:o||"GET",dataType:"html",data:e}).done(function(t){r=arguments,s.html(i?rt("<div>").append(rt.parseHTML(t)).find(i):t)}).always(n&&function(t,e){s.each(function(){n.apply(s,r||[t.responseText,e,t])})}),this},rt.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(t,e){rt.fn[e]=function(t){return this.on(e,t)}}),rt.expr.filters.animated=function(t){return rt.grep(rt.timers,function(e){return t===e.elem}).length},rt.offset={setOffset:function(t,e,n){var i,o,r,s,a,l,u,c=rt.css(t,"position"),f=rt(t),p={};"static"===c&&(t.style.position="relative"),a=f.offset(),r=rt.css(t,"top"),l=rt.css(t,"left"),u=("absolute"===c||"fixed"===c)&&(r+l).indexOf("auto")>-1,u?(i=f.position(),s=i.top,o=i.left):(s=parseFloat(r)||0,o=parseFloat(l)||0),rt.isFunction(e)&&(e=e.call(t,n,rt.extend({},a))),null!=e.top&&(p.top=e.top-a.top+s),null!=e.left&&(p.left=e.left-a.left+o),"using"in e?e.using.call(t,p):f.css(p)}},rt.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){rt.offset.setOffset(this,t,e)});var e,n,i=this[0],o={top:0,left:0},r=i&&i.ownerDocument;if(r)return e=r.documentElement,rt.contains(e,i)?(o=i.getBoundingClientRect(),n=X(r),{top:o.top+n.pageYOffset-e.clientTop,left:o.left+n.pageXOffset-e.clientLeft}):o},position:function(){if(this[0]){var t,e,n=this[0],i={top:0,left:0};return"fixed"===rt.css(n,"position")?e=n.getBoundingClientRect():(t=this.offsetParent(),e=this.offset(),rt.nodeName(t[0],"html")||(i=t.offset()),i.top+=rt.css(t[0],"borderTopWidth",!0),i.left+=rt.css(t[0],"borderLeftWidth",!0)),{top:e.top-i.top-rt.css(n,"marginTop",!0),left:e.left-i.left-rt.css(n,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){for(var t=this.offsetParent;t&&"static"===rt.css(t,"position");)t=t.offsetParent;return t||Jt})}}),rt.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,e){var n="pageYOffset"===e;rt.fn[t]=function(i){return Ct(this,function(t,i,o){var r=X(t);return void 0===o?r?r[e]:t[i]:void(r?r.scrollTo(n?r.pageXOffset:o,n?o:r.pageYOffset):t[i]=o)},t,i,arguments.length)}}),rt.each(["top","left"],function(t,e){rt.cssHooks[e]=S(it.pixelPosition,function(t,n){return n?(n=$(t,e),Yt.test(n)?rt(t).position()[e]+"px":n):void 0})}),rt.each({Height:"height",Width:"width"},function(t,e){rt.each({padding:"inner"+t,content:e,"":"outer"+t},function(n,i){rt.fn[i]=function(i,o){var r=arguments.length&&(n||"boolean"!=typeof i),s=n||(i===!0||o===!0?"margin":"border");return Ct(this,function(e,n,i){var o;return rt.isWindow(e)?e.document.documentElement["client"+t]:9===e.nodeType?(o=e.documentElement,Math.max(e.body["scroll"+t],o["scroll"+t],e.body["offset"+t],o["offset"+t],o["client"+t])):void 0===i?rt.css(e,n,s):rt.style(e,n,i,s)},e,r?i:void 0,r,null)}})}),rt.fn.extend({bind:function(t,e,n){return this.on(t,null,e,n)},unbind:function(t,e){return this.off(t,null,e)},delegate:function(t,e,n,i){return this.on(e,t,n,i)},undelegate:function(t,e,n){return 1===arguments.length?this.off(t,"**"):this.off(e,t||"**",n)},size:function(){return this.length}}),rt.fn.andSelf=rt.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return rt});var Pe=t.jQuery,Fe=t.$;return rt.noConflict=function(e){return t.$===rt&&(t.$=Fe),e&&t.jQuery===rt&&(t.jQuery=Pe),rt},e||(t.jQuery=t.$=rt),rt})},{}],15:[function(t,e,n){"use strict";window.jQuery=window.$=t("jquery");var i=window.$;t("bootstrap");
-
-const loadEditors = () => {
-    const $editors = $('.modal-body textarea, #id_body, #id_comment, #id_message, #id_text, #id_abstract, #id_additional_notes, #id_content_override, #id_description, #id_biography');
-    $editors.each((i, el) => {
-      const editorId = `markdown-editor-${i}`;
-      const reportDiv = $('<div>').attr('id', editorId);
-      const setupEditor = (editor, textarea) => {
-          console.log("setupEditor: " + el.id);
-          const session = editor.getSession();
-          editor.setTheme('ace/theme/tomorrow');
-          editor.$blockScrolling = Infinity;
-          editor.setOption('scrollPastEnd', true);
-          session.setMode('ace/mode/markdown');
-          session.setValue(textarea.val());
-          session.setUseWrapMode(true);
-          session.on('change', () => {
-              textarea.val(session.getValue());
-          });
-          editor.renderer.setShowGutter(false);
-          session.setTabSize(4);
-          session.setUseSoftTabs(true);
-      };
-      const $formGroup = $(el).closest('.form-group');
-      const $textarea = $formGroup.find('textarea');
-      $formGroup.append(reportDiv);
-      setupEditor(ace.edit(editorId), $textarea);
-    });
-};
-
-$(() => {
-    loadEditors();
-});
-
-},{bootstrap:1,jquery:14}]},{},[15]);

From 7965ac4111afc746849dad825ddd2b16b8d0414d Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sun, 1 Oct 2017 14:44:41 +1100
Subject: [PATCH 30/40] De-dupe discount header/text

The version with the header was better, so let's use that in both
places by moving it inside the template.
---
 pinaxcon/templates/registrasion/discount_list.html       | 7 +++++++
 pinaxcon/templates/registrasion/guided_registration.html | 8 --------
 pinaxcon/templates/registrasion/product_category.html    | 6 ------
 3 files changed, 7 insertions(+), 14 deletions(-)

diff --git a/pinaxcon/templates/registrasion/discount_list.html b/pinaxcon/templates/registrasion/discount_list.html
index ea5542a7..8fafb241 100644
--- a/pinaxcon/templates/registrasion/discount_list.html
+++ b/pinaxcon/templates/registrasion/discount_list.html
@@ -1,4 +1,6 @@
 {% if discounts %}
+<h3>Discounts and Complimentary Items</h3>
+<div class="vertical-small"></div>
 
   {% regroup discounts by discount.description as discounts_grouped %}
   {% for discount_type in discounts_grouped %}
@@ -9,4 +11,9 @@
     {% endfor %}
   </ul>
   {% endfor %}
+  <blockquote><small>Any applicable discounts will be applied automatically when you check out.</small></blockquote>
+
+  <hr />
+
+
 {% endif %}
diff --git a/pinaxcon/templates/registrasion/guided_registration.html b/pinaxcon/templates/registrasion/guided_registration.html
index d3b30b85..a24ab235 100644
--- a/pinaxcon/templates/registrasion/guided_registration.html
+++ b/pinaxcon/templates/registrasion/guided_registration.html
@@ -26,14 +26,6 @@
 
         {% 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>
diff --git a/pinaxcon/templates/registrasion/product_category.html b/pinaxcon/templates/registrasion/product_category.html
index fa2e518a..182e8247 100644
--- a/pinaxcon/templates/registrasion/product_category.html
+++ b/pinaxcon/templates/registrasion/product_category.html
@@ -50,13 +50,7 @@
     <fieldset>
 
       {% 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>

From ac0ef79b73107f6c98c54646160bd31b2f1c704f Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sun, 1 Oct 2017 14:46:50 +1100
Subject: [PATCH 31/40] "Create" an attendee profile

---
 pinaxcon/templates/dashboard.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pinaxcon/templates/dashboard.html b/pinaxcon/templates/dashboard.html
index bee1c3c0..841b0a01 100644
--- a/pinaxcon/templates/dashboard.html
+++ b/pinaxcon/templates/dashboard.html
@@ -42,7 +42,7 @@
               <h4>Register</h4>
             </div>
             <div class="panel-body">
-              <p>To attend the conference, you must register an attendee profile and purchase your ticket</p>
+              <p>To attend the conference, you must create an attendee profile and purchase your ticket</p>
               <a class="btn btn-lg btn-success" role="button" href="{% url "guided_registration" %}">Get your ticket</a>
             </div>
           </div>

From 3f4050d96f9ab85887c4e762938d63aaa59c6479 Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sun, 1 Oct 2017 14:48:59 +1100
Subject: [PATCH 32/40] Use a YesNo field for T&C/CoC agreement

Using a checkbox doesn't make it obvious that this is required.
---
 pinaxcon/registrasion/forms.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/pinaxcon/registrasion/forms.py b/pinaxcon/registrasion/forms.py
index 7bb21f1e..17ef9ef2 100644
--- a/pinaxcon/registrasion/forms.py
+++ b/pinaxcon/registrasion/forms.py
@@ -28,4 +28,5 @@ class ProfileForm(forms.ModelForm):
         }
         field_classes = {
             "of_legal_age": YesNoField,
+            "agreement": YesNoField,
         }

From 38a99f7ae57c73a93738d366f5ecad584fd22a7c Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sun, 1 Oct 2017 14:50:53 +1100
Subject: [PATCH 33/40] And "discount" to EB discount descriptions

---
 .../registrasion/management/commands/populate_inventory.py  | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/pinaxcon/registrasion/management/commands/populate_inventory.py b/pinaxcon/registrasion/management/commands/populate_inventory.py
index bb2cb210..77a43795 100644
--- a/pinaxcon/registrasion/management/commands/populate_inventory.py
+++ b/pinaxcon/registrasion/management/commands/populate_inventory.py
@@ -673,7 +673,7 @@ class Command(BaseCommand):
         early_bird_hobbyist_discount = self.find_or_make(
             cond.TimeOrStockLimitDiscount,
             ("description", ),
-            description="Early Bird Hobbyist",
+            description="Early Bird Discount - Hobbyist",
             end_time=datetime(year=2017, month=11, day=1),
             limit=150,  # Across all users
         )
@@ -689,9 +689,9 @@ class Command(BaseCommand):
         early_bird = self.find_or_make(
             cond.TimeOrStockLimitDiscount,
             ("description", ),
-            description="Early Bird",
+            description="Early Bird Discount - Professional",
             end_time=datetime(year=2017, month=11, day=1),
-            limit=200,  # Across professionals and hobbyists
+            limit=200,  # Across professionals and fairy sponsors
         )
         add_early_birds(early_bird)
 

From 348e1c5c820c716de1df9a6a6acbeeb33a035c9d Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sun, 1 Oct 2017 18:39:20 +1100
Subject: [PATCH 34/40] Javascript hack to make it more clear that postcode is
 mandatory

On the two pages that display the profile form, add some javascript to
flag the postcode as required whenever the Country is AU
---
 .../registrasion/guided_registration.html     | 15 ++++++++++++++
 .../templates/registrasion/profile_form.html  | 20 +++++++++++++++----
 2 files changed, 31 insertions(+), 4 deletions(-)

diff --git a/pinaxcon/templates/registrasion/guided_registration.html b/pinaxcon/templates/registrasion/guided_registration.html
index a24ab235..8d507106 100644
--- a/pinaxcon/templates/registrasion/guided_registration.html
+++ b/pinaxcon/templates/registrasion/guided_registration.html
@@ -8,6 +8,21 @@
   {% for section in sections %}
     {{ section.form.media.js }}
   {% endfor %}
+<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>
+
 {% endblock %}
 
 {% block content %}
diff --git a/pinaxcon/templates/registrasion/profile_form.html b/pinaxcon/templates/registrasion/profile_form.html
index fe3fdc36..95111d1d 100644
--- a/pinaxcon/templates/registrasion/profile_form.html
+++ b/pinaxcon/templates/registrasion/profile_form.html
@@ -8,13 +8,25 @@
 {% endblock %}
 
 {% block scripts_extra %}
-  {{ form.media.js }}
-  <script type="text/javascript">
-  </script>
+{{ form.media.js }}
+<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>
 {% endblock %}
 
 {% block content %}
-
+THIS IS THE FORM
   <form class="form-horizontal" method="post" action="">
     {% csrf_token %}
 

From e325454fab75bb0b1c062614b553b399e093dba4 Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sun, 1 Oct 2017 19:14:37 +1100
Subject: [PATCH 35/40] Move agreement to terms to a Category

 - Remove field from attendee model
 - Create the category instead
 - Add a product as well
---
 pinaxcon/registrasion/forms.py                |  1 -
 .../management/commands/populate_inventory.py | 49 +++++++++++++++++--
 .../0008_remove_attendeeprofile_agreement.py  | 19 +++++++
 pinaxcon/registrasion/models.py               | 18 -------
 4 files changed, 64 insertions(+), 23 deletions(-)
 create mode 100644 pinaxcon/registrasion/migrations/0008_remove_attendeeprofile_agreement.py

diff --git a/pinaxcon/registrasion/forms.py b/pinaxcon/registrasion/forms.py
index 17ef9ef2..7bb21f1e 100644
--- a/pinaxcon/registrasion/forms.py
+++ b/pinaxcon/registrasion/forms.py
@@ -28,5 +28,4 @@ class ProfileForm(forms.ModelForm):
         }
         field_classes = {
             "of_legal_age": YesNoField,
-            "agreement": YesNoField,
         }
diff --git a/pinaxcon/registrasion/management/commands/populate_inventory.py b/pinaxcon/registrasion/management/commands/populate_inventory.py
index 77a43795..0607cc04 100644
--- a/pinaxcon/registrasion/management/commands/populate_inventory.py
+++ b/pinaxcon/registrasion/management/commands/populate_inventory.py
@@ -67,6 +67,20 @@ class Command(BaseCommand):
             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",),
@@ -78,7 +92,7 @@ class Command(BaseCommand):
             required=False,
             render_type=inv.Category.RENDER_TYPE_QUANTITY,
             limit_per_user=10,
-            order=10,
+            order=20,
         )
         self.speakers_dinner_ticket = self.find_or_make(
             inv.Category,
@@ -91,7 +105,7 @@ class Command(BaseCommand):
             required=False,
             render_type=inv.Category.RENDER_TYPE_QUANTITY,
             limit_per_user=5,
-            order=20,
+            order=30,
         )
         self.pdns_category = self.find_or_make(
             inv.Category,
@@ -105,7 +119,7 @@ class Command(BaseCommand):
             required=False,
             render_type=inv.Category.RENDER_TYPE_RADIO,
             limit_per_user=1,
-            order=30,
+            order=40,
         )
         self.t_shirt = self.find_or_make(
             inv.Category,
@@ -115,7 +129,7 @@ class Command(BaseCommand):
                         "linux.conf.au 2018 artwork.",
             required=False,
             render_type=inv.Category.RENDER_TYPE_ITEM_QUANTITY,
-            order=40,
+            order=50,
         )
         # self.accommodation = self.find_or_make(
         #     inv.Category,
@@ -258,6 +272,17 @@ class Command(BaseCommand):
             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,
+        )
         # Penguin dinner
 
         self.penguin_adult = self.find_or_make(
@@ -637,7 +662,23 @@ class Command(BaseCommand):
             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,
+        ])
+
 
     def populate_discounts(self):
 
diff --git a/pinaxcon/registrasion/migrations/0008_remove_attendeeprofile_agreement.py b/pinaxcon/registrasion/migrations/0008_remove_attendeeprofile_agreement.py
new file mode 100644
index 00000000..d35526fe
--- /dev/null
+++ b/pinaxcon/registrasion/migrations/0008_remove_attendeeprofile_agreement.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2017-10-01 08:14
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('pinaxcon_registrasion', '0007_auto_20170930_1610'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='attendeeprofile',
+            name='agreement',
+        ),
+    ]
diff --git a/pinaxcon/registrasion/models.py b/pinaxcon/registrasion/models.py
index db28ba26..91a05382 100644
--- a/pinaxcon/registrasion/models.py
+++ b/pinaxcon/registrasion/models.py
@@ -66,12 +66,6 @@ class AttendeeProfile(rego.AttendeeProfileBase):
                 "Please fill in line 1 before filling line 2",
             ))
 
-        if not self.agreement:
-            errors.append((
-                "agreement",
-                "Agreement to the terms and conditions and to the Code of Conduct is required.",
-            ))
-
         if errors:
             raise ValidationError(dict(errors))
 
@@ -201,18 +195,6 @@ class AttendeeProfile(rego.AttendeeProfileBase):
         blank=True,
     )
 
-    agreement = models.BooleanField(
-        blank=False,
-        help_text="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>."
-    )
-
-
     past_lca = models.ManyToManyField(
         PastEvent,
         verbose_name="Which past linux.conf.au events have you attended?",

From b8dcbd89b7b37c6b88b94699dce5cd4ca21a5635 Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sun, 1 Oct 2017 20:01:15 +1100
Subject: [PATCH 36/40] Draw more attention to the need to select complimentary
 items

A big complaint from 2017 was that people overlooked things like
shirts and dinner tickets as those are complimentary, so they assumed
they didn't need to choose them.

This change adds some labels and some explanatory text to try to make
this more clear.
---
 pinaxcon/templates/registrasion/discount_list.html | 10 +++++++---
 pinaxcon/templates/registrasion/review.html        |  6 +++---
 2 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/pinaxcon/templates/registrasion/discount_list.html b/pinaxcon/templates/registrasion/discount_list.html
index 8fafb241..00db6b2c 100644
--- a/pinaxcon/templates/registrasion/discount_list.html
+++ b/pinaxcon/templates/registrasion/discount_list.html
@@ -1,5 +1,6 @@
 {% if discounts %}
-<h3>Discounts and Complimentary Items</h3>
+<div class="alert-success">
+<h3 class="label-success">Discounts and Complimentary Items</h3>
 <div class="vertical-small"></div>
 
   {% regroup discounts by discount.description as discounts_grouped %}
@@ -11,9 +12,12 @@
     {% endfor %}
   </ul>
   {% endfor %}
-  <blockquote><small>Any applicable discounts will be applied automatically when you check out.</small></blockquote>
+  <blockquote>
+      <small>Note that you must choose your items below, even if they are complimentary.</small>
+      <small>Any applicable discounts will be applied automatically when you check out.</small>
+  </blockquote>
 
   <hr />
-
+</div>
 
 {% endif %}
diff --git a/pinaxcon/templates/registrasion/review.html b/pinaxcon/templates/registrasion/review.html
index 9a9ef144..376a73ea 100644
--- a/pinaxcon/templates/registrasion/review.html
+++ b/pinaxcon/templates/registrasion/review.html
@@ -33,8 +33,8 @@
 
   {% missing_categories as missing %}
   {% if missing %}
-
-    <p>
+  <div class="alert-warning">
+    <p class="label-warning">
       <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:
@@ -42,7 +42,7 @@
     </p>
 
     {% include "registrasion/_category_list.html" with categories=missing %}
-
+  </div>
   {% endif %}
 
   <p>

From 362ed28be2b7169dac61384127b193d838e70e78 Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sun, 1 Oct 2017 22:54:04 +1100
Subject: [PATCH 37/40] Alter guided_registrasion logic to handle new page

This is required for the new method of accepting terms and conditions
---
 vendor/registrasion/registrasion/forms.py |  6 ++++-
 vendor/registrasion/registrasion/views.py | 32 +++++++++++++++++------
 2 files changed, 29 insertions(+), 9 deletions(-)

diff --git a/vendor/registrasion/registrasion/forms.py b/vendor/registrasion/registrasion/forms.py
index 879d6d5c..32575080 100644
--- a/vendor/registrasion/registrasion/forms.py
+++ b/vendor/registrasion/registrasion/forms.py
@@ -262,8 +262,12 @@ class _CheckboxProductsForm(_ProductsForm):
     @classmethod
     def set_fields(cls, category, products):
         for product in products:
+            if product.price:
+                label='%s -- $%s' % (product.name, product.price)
+            else:
+                label='%s' % (product.name)
             field = forms.BooleanField(
-                label='%s -- $%s' % (product.name, product.price),
+                label=label,
                 required=False,
             )
             cls.base_fields[cls.field_name(product)] = field
diff --git a/vendor/registrasion/registrasion/views.py b/vendor/registrasion/registrasion/views.py
index 4d6a078f..2f72034b 100644
--- a/vendor/registrasion/registrasion/views.py
+++ b/vendor/registrasion/registrasion/views.py
@@ -1,6 +1,9 @@
 import datetime
 import zipfile
 import os
+import logging
+
+from django.contrib import messages
 
 from . import forms
 from . import util
@@ -43,7 +46,6 @@ from registrasion.contrib.badger import (
                                          InvalidTicketChoiceError
                                          )
 
-
 _GuidedRegistrationSection = namedtuple(
     "GuidedRegistrationSection",
     (
@@ -107,9 +109,10 @@ def guided_registration(request, page_number=None):
 
     PAGE_PROFILE = 1
     PAGE_TICKET = 2
-    PAGE_PRODUCTS = 3
-    PAGE_PRODUCTS_MAX = 4
-    TOTAL_PAGES = 4
+    PAGE_TERMS = 3
+    PAGE_PRODUCTS = 4
+    PAGE_PRODUCTS_MAX = 5
+    TOTAL_PAGES = 5
 
     ticket_category = inventory.Category.objects.get(
         id=settings.TICKET_PRODUCT_CATEGORY
@@ -136,12 +139,18 @@ def guided_registration(request, page_number=None):
         products = inventory.Product.objects.filter(
             productitem__cart=cart.cart
         )
-        products = products.filter(category=ticket_category)
+        tickets = products.filter(category=ticket_category)
 
-        if products.count() == 0:
+        logger.debug("tickets count %s" % tickets.count())
+        logger.debug("products: %s" % products.count())
+        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.
@@ -180,6 +189,12 @@ def guided_registration(request, page_number=None):
             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"
@@ -219,8 +234,9 @@ def guided_registration(request, page_number=None):
 
 
 GUIDED_MODE_TICKETS_ONLY = 2
-GUIDED_MODE_ALL_ADDITIONAL = 3
-GUIDED_MODE_EXCLUDE_COMPLETE = 4
+GUIDED_MODE_TERMS = 3
+GUIDED_MODE_ALL_ADDITIONAL = 4
+GUIDED_MODE_EXCLUDE_COMPLETE =5
 
 
 @login_required

From b0de856255618a688db8178154b78cf3e22b50bd Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sun, 1 Oct 2017 22:54:46 +1100
Subject: [PATCH 38/40] Clarify wording about complimentary products

---
 pinaxcon/templates/registrasion/discount_list.html | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/pinaxcon/templates/registrasion/discount_list.html b/pinaxcon/templates/registrasion/discount_list.html
index 00db6b2c..9cba9f37 100644
--- a/pinaxcon/templates/registrasion/discount_list.html
+++ b/pinaxcon/templates/registrasion/discount_list.html
@@ -3,6 +3,7 @@
 <h3 class="label-success">Discounts and Complimentary Items</h3>
 <div class="vertical-small"></div>
 
+<blockquote>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. This discounts will be applied automatically when you check out.</blockquote>
   {% regroup discounts by discount.description as discounts_grouped %}
   {% for discount_type in discounts_grouped %}
   <h4>{{ discount_type.grouper }}</h4>
@@ -12,10 +13,6 @@
     {% endfor %}
   </ul>
   {% endfor %}
-  <blockquote>
-      <small>Note that you must choose your items below, even if they are complimentary.</small>
-      <small>Any applicable discounts will be applied automatically when you check out.</small>
-  </blockquote>
 
   <hr />
 </div>

From 0103f9a91f631c1ac13941e12bde6ffc38e794e8 Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Sun, 1 Oct 2017 23:10:44 +1100
Subject: [PATCH 39/40] Remove erroneous debug logging statements

---
 vendor/registrasion/registrasion/views.py | 2 --
 1 file changed, 2 deletions(-)

diff --git a/vendor/registrasion/registrasion/views.py b/vendor/registrasion/registrasion/views.py
index 2f72034b..dc3c5a6a 100644
--- a/vendor/registrasion/registrasion/views.py
+++ b/vendor/registrasion/registrasion/views.py
@@ -141,8 +141,6 @@ def guided_registration(request, page_number=None):
         )
         tickets = products.filter(category=ticket_category)
 
-        logger.debug("tickets count %s" % tickets.count())
-        logger.debug("products: %s" % products.count())
         if tickets.count() == 0:
             # If no ticket, they can only see the profile or ticket page.
             max_page = PAGE_TICKET

From abe4f5ad7ad2603e40292693272dffffa16208af Mon Sep 17 00:00:00 2001
From: James Polley <jp@jamezpolley.com>
Date: Mon, 2 Oct 2017 00:02:15 +1100
Subject: [PATCH 40/40] Fix some rules that were overly generous

---
 .../management/commands/populate_inventory.py             | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/pinaxcon/registrasion/management/commands/populate_inventory.py b/pinaxcon/registrasion/management/commands/populate_inventory.py
index 0607cc04..a1a36a32 100644
--- a/pinaxcon/registrasion/management/commands/populate_inventory.py
+++ b/pinaxcon/registrasion/management/commands/populate_inventory.py
@@ -646,7 +646,6 @@ class Command(BaseCommand):
         )
         pdns_by_staff.group.set([
             self.group_team,
-            self.group_volunteers,
         ])
         pdns_by_staff.categories.set([self.pdns_category, ])
 
@@ -655,7 +654,7 @@ class Command(BaseCommand):
             cond.CategoryFlag,
             ("description", ),
             description="GottaGettaTicketFirst",
-            condition=cond.FlagBase.ENABLE_IF_TRUE,
+            condition=cond.FlagBase.DISABLE_IF_FALSE,
             enabling_category = self.ticket
         )
         needs_a_ticket.categories.set([
@@ -798,15 +797,14 @@ class Command(BaseCommand):
         ])
         free_category(ticket_student_inclusions, self.t_shirt)
 
-        # Team & volunteer ticket inclusions
+        # Team ticket inclusions
         ticket_staff_inclusions = self.find_or_make(
             cond.IncludedProductDiscount,
             ("description", ),
-            description="Complimentary for ticket holder (staff/volunteer)",
+            description="Complimentary for ticket holder staff)",
         )
         ticket_staff_inclusions.enabling_products.set([
             self.ticket_team,
-            self.ticket_volunteer,
         ])
         free_category(ticket_staff_inclusions, self.penguin_dinner)