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 688276bf..baf81ccd 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,18 +17,26 @@ 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 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/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/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/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 ) diff --git a/pinaxcon/registrasion/management/commands/populate_inventory.py b/pinaxcon/registrasion/management/commands/populate_inventory.py index ae2a09e6..a1a36a32 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 @@ -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,17 +119,17 @@ 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, ("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, - 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( @@ -358,7 +383,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), @@ -369,12 +394,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"), ), } @@ -621,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, ]) @@ -630,14 +654,30 @@ 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([ 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): @@ -673,7 +713,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 +729,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) @@ -757,23 +797,22 @@ 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) - # 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, 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/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/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 f30af712..91a05382 100644 --- a/pinaxcon/registrasion/models.py +++ b/pinaxcon/registrasion/models.py @@ -138,6 +138,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, ) @@ -179,13 +180,21 @@ 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/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/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 f681707a..841b0a01 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 %} @@ -42,8 +42,8 @@ <h4>Register</h4> </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> + <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> {% else %} @@ -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> + <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/discount_list.html b/pinaxcon/templates/registrasion/discount_list.html index ea5542a7..9cba9f37 100644 --- a/pinaxcon/templates/registrasion/discount_list.html +++ b/pinaxcon/templates/registrasion/discount_list.html @@ -1,5 +1,9 @@ {% if discounts %} +<div class="alert-success"> +<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> @@ -9,4 +13,8 @@ {% endfor %} </ul> {% endfor %} + + <hr /> +</div> + {% endif %} diff --git a/pinaxcon/templates/registrasion/guided_registration.html b/pinaxcon/templates/registrasion/guided_registration.html index 71af3298..8d507106 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 }} – {{ title }} {% endblock %} @@ -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 %} @@ -26,14 +41,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> @@ -45,7 +52,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/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–Friday 20 January 2017.</p> - <p>Wrest Point Convention Centre, Hobart, Tasmania, Australia.</p> + <p>Monday 22 January–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..182e8247 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 %} @@ -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> diff --git a/pinaxcon/templates/registrasion/profile_form.html b/pinaxcon/templates/registrasion/profile_form.html index 5911874d..95111d1d 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 %} @@ -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 %} diff --git a/pinaxcon/templates/registrasion/review.html b/pinaxcon/templates/registrasion/review.html index f582673b..376a73ea 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 %} @@ -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> @@ -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/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(); +} 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 "" 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/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]); 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 */ 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 new file mode 100644 index 00000000..0dfb8b08 --- /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 = 3545a809e8e14014963c670709b6d0273c0e354a + parent = 19e4185cd9433c8f743c32dde8aee09455db3982 + 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/docs/registrasion/README.rst b/vendor/registrasion/README.rst similarity index 98% rename from docs/registrasion/README.rst rename to vendor/registrasion/README.rst index f8386edb..1a18a7c0 100644 --- a/docs/registrasion/README.rst +++ b/vendor/registrasion/README.rst @@ -1,8 +1,6 @@ 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. 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/docs/registrasion/Makefile b/vendor/registrasion/docs/Makefile similarity index 100% rename from docs/registrasion/Makefile rename to vendor/registrasion/docs/Makefile diff --git a/docs/registrasion/conf.py b/vendor/registrasion/docs/conf.py similarity index 100% rename from docs/registrasion/conf.py rename to vendor/registrasion/docs/conf.py diff --git a/docs/registrasion/django_settings.py b/vendor/registrasion/docs/django_settings.py similarity index 100% rename from docs/registrasion/django_settings.py rename to vendor/registrasion/docs/django_settings.py diff --git a/docs/registrasion/for-zookeepr-users.rst b/vendor/registrasion/docs/for-zookeepr-users.rst similarity index 100% rename from docs/registrasion/for-zookeepr-users.rst rename to vendor/registrasion/docs/for-zookeepr-users.rst diff --git a/docs/registrasion/index.rst b/vendor/registrasion/docs/index.rst similarity index 98% rename from docs/registrasion/index.rst rename to vendor/registrasion/docs/index.rst index 75d0c4fa..e8cab500 100644 --- a/docs/registrasion/index.rst +++ b/vendor/registrasion/docs/index.rst @@ -24,6 +24,7 @@ Contents: payments for-zookeepr-users views + templates Indices and tables diff --git a/docs/registrasion/integration.rst b/vendor/registrasion/docs/integration.rst similarity index 100% rename from docs/registrasion/integration.rst rename to vendor/registrasion/docs/integration.rst diff --git a/docs/registrasion/inventory.rst b/vendor/registrasion/docs/inventory.rst similarity index 100% rename from docs/registrasion/inventory.rst rename to vendor/registrasion/docs/inventory.rst diff --git a/docs/registrasion/make.bat b/vendor/registrasion/docs/make.bat similarity index 100% rename from docs/registrasion/make.bat rename to vendor/registrasion/docs/make.bat diff --git a/docs/registrasion/overview.rst b/vendor/registrasion/docs/overview.rst similarity index 100% rename from docs/registrasion/overview.rst rename to vendor/registrasion/docs/overview.rst diff --git a/docs/registrasion/payments.rst b/vendor/registrasion/docs/payments.rst similarity index 100% rename from docs/registrasion/payments.rst rename to vendor/registrasion/docs/payments.rst 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/docs/registrasion/views.rst b/vendor/registrasion/docs/views.rst similarity index 100% rename from docs/registrasion/views.rst rename to vendor/registrasion/docs/views.rst diff --git a/vendor/registrasion/models/__init__.py b/vendor/registrasion/models/__init__.py deleted file mode 100644 index 47efa127..00000000 --- a/vendor/registrasion/models/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from registrasion.models.commerce import * # NOQA -from registrasion.models.conditions import * # NOQA -from registrasion.models.inventory import * # NOQA -from registrasion.models.people import * # NOQA 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/registrasion/contrib/badger.py b/vendor/registrasion/registrasion/contrib/badger.py new file mode 100644 index 00000000..b00f01ea --- /dev/null +++ b/vendor/registrasion/registrasion/contrib/badger.py @@ -0,0 +1,392 @@ +''' +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 django.db.utils import OperationalError +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)) + +## 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): + ''' + 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/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 98% rename from vendor/registrasion/controllers/cart.py rename to vendor/registrasion/registrasion/controllers/cart.py index bb772ce0..4bd416e9 100644 --- a/vendor/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/controllers/category.py b/vendor/registrasion/registrasion/controllers/category.py similarity index 92% rename from vendor/registrasion/controllers/category.py rename to vendor/registrasion/registrasion/controllers/category.py index 2b2b18d1..77875946 100644 --- a/vendor/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/controllers/conditions.py b/vendor/registrasion/registrasion/controllers/conditions.py similarity index 97% rename from vendor/registrasion/controllers/conditions.py rename to vendor/registrasion/registrasion/controllers/conditions.py index 87fb9cfb..e86a9d71 100644 --- a/vendor/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 diff --git a/vendor/registrasion/controllers/credit_note.py b/vendor/registrasion/registrasion/controllers/credit_note.py similarity index 90% rename from vendor/registrasion/controllers/credit_note.py rename to vendor/registrasion/registrasion/controllers/credit_note.py index 500d0ad7..6c2f7102 100644 --- a/vendor/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/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 83% rename from vendor/registrasion/controllers/flag.py rename to vendor/registrasion/registrasion/controllers/flag.py index b8d1b4e3..b64713e7 100644 --- a/vendor/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/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 98% rename from vendor/registrasion/controllers/invoice.py rename to vendor/registrasion/registrasion/controllers/invoice.py index 706db7f9..8937843b 100644 --- a/vendor/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/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 81% rename from vendor/registrasion/forms.py rename to vendor/registrasion/registrasion/forms.py index fec2ec79..32575080 100644 --- a/vendor/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,39 @@ 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: + if product.price: + label='%s -- $%s' % (product.name, product.price) + else: + label='%s' % (product.name) + field = forms.BooleanField( + label=label, + 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 +484,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, @@ -491,3 +525,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/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/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/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'), + ), + ] 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/registrasion/models/__init__.py b/vendor/registrasion/registrasion/models/__init__.py new file mode 100644 index 00000000..3d247559 --- /dev/null +++ b/vendor/registrasion/registrasion/models/__init__.py @@ -0,0 +1,4 @@ +from .commerce import * # NOQA +from .conditions import * # NOQA +from .inventory import * # NOQA +from .people import * # NOQA diff --git a/vendor/registrasion/models/commerce.py b/vendor/registrasion/registrasion/models/commerce.py similarity index 99% rename from vendor/registrasion/models/commerce.py rename to vendor/registrasion/registrasion/models/commerce.py index a392c96b..4791ec3d 100644 --- a/vendor/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/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 97% rename from vendor/registrasion/models/inventory.py rename to vendor/registrasion/registrasion/models/inventory.py index 575566e2..6dfc6cd4 100644 --- a/vendor/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/models/people.py b/vendor/registrasion/registrasion/models/people.py similarity index 99% rename from vendor/registrasion/models/people.py rename to vendor/registrasion/registrasion/models/people.py index 64d0ac40..561c3bb4 100644 --- a/vendor/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/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 95% rename from vendor/registrasion/reporting/reports.py rename to vendor/registrasion/registrasion/reporting/reports.py index a9684802..f63bacff 100644 --- a/vendor/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): @@ -177,12 +185,13 @@ 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) ] + def count(self): + return len(self._links) def report_view(title, form_type=None): ''' Decorator that converts a report view function into something that @@ -299,9 +308,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/reporting/views.py b/vendor/registrasion/registrasion/reporting/views.py similarity index 76% rename from vendor/registrasion/reporting/views.py rename to vendor/registrasion/registrasion/reporting/views.py index 239c174a..21f03a16 100644 --- a/vendor/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 @@ -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 @@ -24,11 +26,13 @@ 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 + +import bleach def CURRENCY(): @@ -95,8 +99,6 @@ def items_sold(): total_quantity=Sum("quantity"), ) - print(line_items) - headings = ["Description", "Quantity", "Price", "Total"] data = [] @@ -242,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 @@ -312,6 +391,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").distinct() + + 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 @@ -339,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), @@ -353,8 +481,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,20 +545,19 @@ 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() - 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"]) @@ -444,11 +571,20 @@ 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): + try: + value = bleach.clean(value) + except TypeError: + value = "Bad value for %s" % field.name 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)) @@ -470,53 +606,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 @@ -633,6 +781,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, ) @@ -667,7 +816,7 @@ def attendee_data(request, form, user_id=None): return None else: def display_field(value): - return value + return bleach.clean(str(value)) status_count = lambda status: Case(When( # noqa attendee__user__cart__status=status, @@ -714,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 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) @@ -846,9 +998,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/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 }} – {{ 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 }} – your most recent invoice</li> + <li>{{ current_host|add:invoice_url|urlize }} – 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 }} × {{ 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 }} × {{ 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 58% rename from vendor/registrasion/templatetags/registrasion_tags.py rename to vendor/registrasion/registrasion/templatetags/registrasion_tags.py index 7108ae95..ccebe20b 100644 --- a/vendor/registrasion/templatetags/registrasion_tags.py +++ b/vendor/registrasion/registrasion/templatetags/registrasion_tags.py @@ -3,8 +3,14 @@ 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 +try: + from urllib import urlencode +except ImportError: + from urllib.parse import urlencode + +from operator import attrgetter register = template.Library() @@ -42,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): @@ -117,3 +124,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/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 99% rename from vendor/registrasion/tests/test_cart.py rename to vendor/registrasion/registrasion/tests/test_cart.py index d8f5b4c0..825ff675 100644 --- a/vendor/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/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 97% rename from vendor/registrasion/tests/test_invoice.py rename to vendor/registrasion/registrasion/tests/test_invoice.py index 0c650dd4..b77fa223 100644 --- a/vendor/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/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 98% rename from vendor/registrasion/tests/test_speaker.py rename to vendor/registrasion/registrasion/tests/test_speaker.py index f31266b6..cf64074e 100644 --- a/vendor/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/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 86% rename from vendor/registrasion/urls.py rename to vendor/registrasion/registrasion/urls.py index 0789912d..72553877 100644 --- a/vendor/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 @@ -6,7 +6,7 @@ from django.conf.urls import url from .views import ( amend_registration, badge, - badges, + badger, checkout, credit_note, edit_profile, @@ -19,13 +19,15 @@ from .views import ( product_category, refund, review, + voucher_code, ) 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"), @@ -41,8 +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"), ] @@ -54,7 +57,13 @@ 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/?$", + 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/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 65% rename from vendor/registrasion/views.py rename to vendor/registrasion/registrasion/views.py index 239e924b..dc3c5a6a 100644 --- a/vendor/registrasion/views.py +++ b/vendor/registrasion/registrasion/views.py @@ -1,19 +1,24 @@ import datetime import zipfile +import os +import logging -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 django.contrib import messages + +from . import forms +from . import util +from .models import commerce +from .models import inventory +from .models import people +from .controllers.batch import BatchController +from .controllers.cart import CartController +from .controllers.category import CategoryController +from .controllers.credit_note import CreditNoteController +from .controllers.discount import DiscountController +from .controllers.invoice import InvoiceController +from .controllers.item import ItemController +from .controllers.product import ProductController +from .exceptions import CartValidationError from collections import namedtuple @@ -31,6 +36,15 @@ 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", @@ -64,17 +78,25 @@ 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:: { + "previous_step": int(), # Previous step "current_step": int(), # The current step in the # registration "sections": sections, # A list of @@ -85,148 +107,240 @@ def guided_registration(request): ''' - SESSION_KEY = "guided_registration_categories" - ASK_FOR_PROFILE = 777 # Magic number. Meh. + PAGE_PROFILE = 1 + PAGE_TICKET = 2 + PAGE_TERMS = 3 + PAGE_PRODUCTS = 4 + PAGE_PRODUCTS_MAX = 5 + TOTAL_PAGES = 5 - 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 ) + tickets = 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 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: - 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) + + prev_step = page_number - 1 + + next_step = redirect("guided_registration", page_number + 1) + + with BatchController.batch(request.user): + + # This view doesn't work if the conference has sold out. + available = ProductController.available_products( + request.user, category=ticket_category + ) + if not available: + messages.error(request, "There are no more tickets available.") + return redirect("dashboard") + + sections = [] + + # Build up the list of sections + if page_number == PAGE_PROFILE: + # Profile bit + title = "Attendee information" + sections = _guided_registration_profile_and_voucher(request) + elif page_number == PAGE_TICKET: + # Select ticket + title = "Select ticket type" + sections = _guided_registration_products( + request, GUIDED_MODE_TICKETS_ONLY + ) + elif page_number == PAGE_TERMS: + # Accept terms + title = "Terms and Conditions" + sections = _guided_registration_products( + request, GUIDED_MODE_TERMS + ) + elif page_number == PAGE_PRODUCTS: + # Select additional items + title = "Additional items" + sections = _guided_registration_products( + request, GUIDED_MODE_ALL_ADDITIONAL + ) + elif page_number == PAGE_PRODUCTS_MAX: + # Items enabled by things on page 3 -- only shows things + # that have not been marked as complete. + title = "More additional items" + sections = _guided_registration_products( + request, GUIDED_MODE_EXCLUDE_COMPLETE ) - 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, + "previous_step": prev_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_TERMS = 3 +GUIDED_MODE_ALL_ADDITIONAL = 4 +GUIDED_MODE_EXCLUDE_COMPLETE =5 + + +@login_required +def _guided_registration_products(request, mode): + sections = [] + + SESSION_KEY = "guided_registration" + MODE_KEY = "mode" + CATS_KEY = "cats" + + attendee = people.Attendee.get_instance(request.user) + + # Get the next category + cats = inventory.Category.objects.order_by("order") # TODO: default order? + + # Fun story: If _any_ of the category forms result in an error, but other + # new products get enabled with a flag, those new products will appear. + # We need to make sure that we only display the products that were valid + # in the first place. So we track them in a session, and refresh only if + # the page number does not change. Cheap! + + if SESSION_KEY in request.session: + session_struct = request.session[SESSION_KEY] + old_mode = session_struct[MODE_KEY] + old_cats = session_struct[CATS_KEY] + else: + old_mode = None + old_cats = [] + + if mode == old_mode: + cats = cats.filter(id__in=old_cats) + elif mode == GUIDED_MODE_TICKETS_ONLY: + cats = cats.filter(id=settings.TICKET_PRODUCT_CATEGORY) + elif mode == GUIDED_MODE_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 +513,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 @@ -437,11 +573,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, @@ -969,24 +1107,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", []) @@ -1021,12 +1177,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 diff --git a/vendor/registrasion/requirements/base.txt b/vendor/registrasion/requirements/base.txt new file mode 100644 index 00000000..c45a3a46 --- /dev/null +++ b/vendor/registrasion/requirements/base.txt @@ -0,0 +1,3 @@ +django-nested-admin==2.2.6 +#symposion==1.0b2.dev3 +lxml==4.0.0 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(), +) 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 new file mode 100644 index 00000000..1053e9ea --- /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 = git@gitlab.com:tchaypo/registrasion-stripe.git + branch = lca2018 + 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 90% rename from vendor/registripe/views.py rename to vendor/registripe/registripe/views.py index daf685d0..4deca61a 100644 --- a/vendor/registripe/views.py +++ b/vendor/registripe/registripe/views.py @@ -14,6 +14,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 +30,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 +78,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: @@ -170,7 +183,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 @@ -186,7 +198,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..e695d17b --- /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 new file mode 100644 index 00000000..8b01002b --- /dev/null +++ b/vendored_requirements.txt @@ -0,0 +1,2 @@ +vendor/registrasion +vendor/registripe