diff --git a/.gitignore b/.gitignore index 9b7266b4a453b482e6b77d836f1645a2e22cca62..7da754e7a57892adfb7bebef60b889f01ab8c80b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ *.pyc node_modules/ _build/ +.DS_Store +dev.db + diff --git a/fixtures/registrasion.json b/fixtures/registrasion.json new file mode 100644 index 0000000000000000000000000000000000000000..e42d0805d2bccc582f2bf9663f0445e4a1ca38cb --- /dev/null +++ b/fixtures/registrasion.json @@ -0,0 +1,393 @@ +[ +{ + "model": "registrasion.category", + "pk": 1, + "fields": { + "name": "Ticket", + "description": "Admission to the conference", + "limit_per_user": 1, + "required": false, + "order": 1, + "render_type": 1 + } +}, +{ + "model": "registrasion.category", + "pk": 2, + "fields": { + "name": "Dinner Ticket", + "description": "Tickets for the conference dinner", + "limit_per_user": null, + "required": false, + "order": 10, + "render_type": 2 + } +}, +{ + "model": "registrasion.category", + "pk": 3, + "fields": { + "name": "T-shirt", + "description": "The conference t-shirt", + "limit_per_user": null, + "required": false, + "order": 30, + "render_type": 2 + } +}, +{ + "model": "registrasion.category", + "pk": 4, + "fields": { + "name": "Accommodation", + "description": "Accommodation in the student dormitories", + "limit_per_user": null, + "required": false, + "order": 40, + "render_type": 2 + } +}, +{ + "model": "registrasion.category", + "pk": 5, + "fields": { + "name": "Extra accommodation nights", + "description": "Extra nights at the student dorms. You will need to purchase the week block before you can add extra nights", + "limit_per_user": null, + "required": false, + "order": 50, + "render_type": 2 + } +}, +{ + "model": "registrasion.product", + "pk": 1, + "fields": { + "name": "Professional", + "description": "", + "category": 1, + "price": "1000.00", + "limit_per_user": null, + "reservation_duration": "1 00:00:00", + "order": 10 + } +}, +{ + "model": "registrasion.product", + "pk": 2, + "fields": { + "name": "Enthusiast", + "description": "", + "category": 1, + "price": "450.00", + "limit_per_user": null, + "reservation_duration": "1 00:00:00", + "order": 20 + } +}, +{ + "model": "registrasion.product", + "pk": 3, + "fields": { + "name": "Student", + "description": "", + "category": 1, + "price": "160.00", + "limit_per_user": null, + "reservation_duration": "1 00:00:00", + "order": 30 + } +}, +{ + "model": "registrasion.product", + "pk": 4, + "fields": { + "name": "Adult", + "description": "Includes a full meal and drinks", + "category": 2, + "price": "85.00", + "limit_per_user": null, + "reservation_duration": "01:00:00", + "order": 10 + } +}, +{ + "model": "registrasion.product", + "pk": 5, + "fields": { + "name": "Child", + "description": "Includes a children's meal and soft drinks", + "category": 2, + "price": "40.00", + "limit_per_user": null, + "reservation_duration": "01:00:00", + "order": 20 + } +}, +{ + "model": "registrasion.product", + "pk": 6, + "fields": { + "name": "Infant", + "description": "A seat for an infant in lap", + "category": 2, + "price": "0.00", + "limit_per_user": null, + "reservation_duration": "01:00:00", + "order": 30 + } +}, +{ + "model": "registrasion.product", + "pk": 7, + "fields": { + "name": "S", + "description": "", + "category": 3, + "price": "25.00", + "limit_per_user": null, + "reservation_duration": "01:00:00", + "order": 10 + } +}, +{ + "model": "registrasion.product", + "pk": 8, + "fields": { + "name": "M", + "description": "", + "category": 3, + "price": "25.00", + "limit_per_user": null, + "reservation_duration": "01:00:00", + "order": 20 + } +}, +{ + "model": "registrasion.product", + "pk": 9, + "fields": { + "name": "L", + "description": "", + "category": 3, + "price": "25.00", + "limit_per_user": null, + "reservation_duration": "01:00:00", + "order": 30 + } +}, +{ + "model": "registrasion.product", + "pk": 10, + "fields": { + "name": "Sunday 19-Saturday 25", + "description": "6 nights", + "category": 4, + "price": "480.00", + "limit_per_user": null, + "reservation_duration": "01:00:00", + "order": 1 + } +}, +{ + "model": "registrasion.product", + "pk": 11, + "fields": { + "name": "Friday 17", + "description": "", + "category": 5, + "price": "80.00", + "limit_per_user": null, + "reservation_duration": "01:00:00", + "order": 10 + } +}, +{ + "model": "registrasion.product", + "pk": 12, + "fields": { + "name": "Saturday 18", + "description": "", + "category": 5, + "price": "80.00", + "limit_per_user": null, + "reservation_duration": "01:00:00", + "order": 20 + } +}, +{ + "model": "registrasion.product", + "pk": 13, + "fields": { + "name": "Saturday 25", + "description": "", + "category": 5, + "price": "80.00", + "limit_per_user": null, + "reservation_duration": "01:00:00", + "order": 30 + } +}, +{ + "model": "registrasion.product", + "pk": 14, + "fields": { + "name": "Sunday 26", + "description": "", + "category": 5, + "price": "80.00", + "limit_per_user": null, + "reservation_duration": "01:00:00", + "order": 40 + } +}, +{ + "model": "registrasion.voucher", + "pk": 1, + "fields": { + "recipient": "FooCorp", + "code": "FOOCORP", + "limit": 10 + } +}, +{ + "model": "registrasion.discountbase", + "pk": 1, + "fields": { + "description": "Early bird discount" + } +}, +{ + "model": "registrasion.discountbase", + "pk": 2, + "fields": { + "description": "FooCorp complimentary sponsor ticket" + } +}, +{ + "model": "registrasion.discountbase", + "pk": 3, + "fields": { + "description": "1 Dinner Ticket included with Professional Tickets" + } +}, +{ + "model": "registrasion.discountbase", + "pk": 4, + "fields": { + "description": "1 T-Shirt included with all tickets" + } +}, +{ + "model": "registrasion.discountforproduct", + "pk": 1, + "fields": { + "discount": 1, + "product": 1, + "percentage": null, + "price": "200.00", + "quantity": 1 + } +}, +{ + "model": "registrasion.discountforproduct", + "pk": 2, + "fields": { + "discount": 1, + "product": 2, + "percentage": null, + "price": "100.00", + "quantity": 1 + } +}, +{ + "model": "registrasion.discountforproduct", + "pk": 3, + "fields": { + "discount": 2, + "product": 1, + "percentage": "100.0", + "price": null, + "quantity": 1 + } +}, +{ + "model": "registrasion.discountforcategory", + "pk": 1, + "fields": { + "discount": 3, + "category": 2, + "percentage": "100.0", + "quantity": 1 + } +}, +{ + "model": "registrasion.discountforcategory", + "pk": 2, + "fields": { + "discount": 4, + "category": 3, + "percentage": "100.0", + "quantity": 1 + } +}, +{ + "model": "registrasion.timeorstocklimitdiscount", + "pk": 1, + "fields": { + "start_time": null, + "end_time": null, + "limit": 10 + } +}, +{ + "model": "registrasion.voucherdiscount", + "pk": 2, + "fields": { + "voucher": 1 + } +}, +{ + "model": "registrasion.includedproductdiscount", + "pk": 3, + "fields": { + "enabling_products": [ + 1 + ] + } +}, +{ + "model": "registrasion.includedproductdiscount", + "pk": 4, + "fields": { + "enabling_products": [ + 1, + 2, + 3 + ] + } +}, +{ + "model": "registrasion.enablingconditionbase", + "pk": 1, + "fields": { + "description": "Extra accommodation depends on Accommodation", + "condition": 2, + "products": [], + "categories": [ + 5 + ] + } +}, +{ + "model": "registrasion.categoryflag", + "pk": 1, + "fields": { + "enabling_category": 4, + "products": [], + "categories": [ + 5 + ] + } +} +] diff --git a/pinaxcon/registrasion/__init__.py b/pinaxcon/registrasion/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1a3225d125a515b335048d96dfc150bd832acc24 --- /dev/null +++ b/pinaxcon/registrasion/__init__.py @@ -0,0 +1 @@ +default_app_config = "pinaxcon.registrasion.apps.RegistrasionConfig" diff --git a/pinaxcon/registrasion/apps.py b/pinaxcon/registrasion/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..40580c31dd5ea1bbaeaba068c4b8eb8718055165 --- /dev/null +++ b/pinaxcon/registrasion/apps.py @@ -0,0 +1,9 @@ +from __future__ import unicode_literals +from django.apps import AppConfig +from django.utils.translation import ugettext_lazy as _ + + +class RegistrasionConfig(AppConfig): + name = "pinaxcon.registrasion" + label = "pinaxcon_registrasion" + verbose_name = _("Pinaxcon Registrasion") diff --git a/pinaxcon/registrasion/forms.py b/pinaxcon/registrasion/forms.py new file mode 100644 index 0000000000000000000000000000000000000000..8cf785ff32174269f1537d3cfce7b079bab94c1d --- /dev/null +++ b/pinaxcon/registrasion/forms.py @@ -0,0 +1,10 @@ +import models + +from django import forms + +class ProfileForm(forms.ModelForm): + ''' A form for requesting badge and profile information. ''' + + class Meta: + model = models.AttendeeProfile + exclude = ['attendee'] diff --git a/pinaxcon/registrasion/migrations/0001_initial.py b/pinaxcon/registrasion/migrations/0001_initial.py new file mode 100644 index 0000000000000000000000000000000000000000..4e83eceda65eed74ad943484c10e715186c94682 --- /dev/null +++ b/pinaxcon/registrasion/migrations/0001_initial.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.2 on 2016-04-01 10:05 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('registrasion', '0011_auto_20160401_0943'), + ] + + operations = [ + migrations.CreateModel( + name='AttendeeProfile', + fields=[ + ('attendeeprofilebase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registrasion.AttendeeProfileBase')), + ('name', models.CharField(help_text=b"Your name, as you'd like it to appear on your badge. ", max_length=64, verbose_name=b'Your name (for your conference nametag)')), + ('company', models.CharField(blank=True, help_text=b"The name of your company, as you'd like it on your badge", max_length=64)), + ('free_text_1', models.CharField(blank=True, help_text=b"A line of free text that will appear on your badge. Use this for your Twitter handle, IRC nick, your preferred pronouns or anything else you'd like people to see on your badge.", max_length=64, verbose_name=b'Free text line 1')), + ('free_text_2', models.CharField(blank=True, max_length=64, verbose_name=b'Free text line 2')), + ('name_per_invoice', models.CharField(blank=True, help_text=b"If your legal name is different to the name on your badge, fill this in, and we'll put it on your invoice. Otherwise, leave it blank.", max_length=64, verbose_name=b'Your legal name (for invoicing purposes)')), + ('of_legal_age', models.BooleanField(default=False, verbose_name=b'18+?')), + ('dietary_requirements', models.CharField(blank=True, max_length=256)), + ('accessibility_requirements', models.CharField(blank=True, max_length=256)), + ('gender', models.CharField(blank=True, max_length=64)), + ], + bases=('registrasion.attendeeprofilebase',), + ), + ] diff --git a/pinaxcon/registrasion/migrations/0002_demopayment.py b/pinaxcon/registrasion/migrations/0002_demopayment.py new file mode 100644 index 0000000000000000000000000000000000000000..ccff847c1b73b682d90ab12e0305d7acd4723b47 --- /dev/null +++ b/pinaxcon/registrasion/migrations/0002_demopayment.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.2 on 2016-04-07 07:41 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('registrasion', '0013_auto_20160406_2228_squashed_0015_auto_20160406_1942'), + ('pinaxcon_registrasion', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='DemoPayment', + fields=[ + ('paymentbase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registrasion.PaymentBase')), + ], + bases=('registrasion.paymentbase',), + ), + ] diff --git a/pinaxcon/registrasion/migrations/__init__.py b/pinaxcon/registrasion/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/pinaxcon/registrasion/models.py b/pinaxcon/registrasion/models.py new file mode 100644 index 0000000000000000000000000000000000000000..392b69d73cee3f974821b2267e045c860f9256eb --- /dev/null +++ b/pinaxcon/registrasion/models.py @@ -0,0 +1,82 @@ +from django.db import models +from registrasion import models as rego + +class AttendeeProfile(rego.AttendeeProfileBase): + + @classmethod + def name_field(cls): + ''' This is used to pre-fill the attendee's name from the + speaker profile. If it's None, that functionality is disabled. ''' + return "name" + + def invoice_recipient(self): + if self.company: + base = "%(name_per_invoice)s c/- %(company)s" + else: + base = "%(name_per_invoice)s" + return base % self.__dict__ + + def save(self): + if not self.name_per_invoice: + self.name_per_invoice = self.name + super(AttendeeProfile, self).save() + + # Things that appear on badge + name = models.CharField( + verbose_name="Your name (for your conference nametag)", + max_length=64, + help_text="Your name, as you'd like it to appear on your badge. ", + ) + + company = models.CharField( + max_length=64, + help_text="The name of your company, as you'd like it on your badge", + blank=True, + ) + free_text_1 = models.CharField( + max_length=64, + verbose_name="Free text line 1", + help_text="A line of free text that will appear on your badge. Use " + "this for your Twitter handle, IRC nick, your preferred " + "pronouns or anything else you'd like people to see on " + "your badge.", + blank=True, + ) + free_text_2 = models.CharField( + max_length=64, + verbose_name="Free text line 2", + blank=True, + ) + + # Other important Information + name_per_invoice = models.CharField( + verbose_name="Your legal name (for invoicing purposes)", + max_length=64, + help_text="If your legal name is different to the name on your badge, " + "fill this in, and we'll put it on your invoice. Otherwise, " + "leave it blank.", + blank=True, + ) + of_legal_age = models.BooleanField( + default=False, + verbose_name="18+?", + blank=True, + ) + dietary_requirements = models.CharField( + max_length=256, + blank=True, + ) + accessibility_requirements = models.CharField( + max_length=256, + blank=True, + ) + gender = models.CharField( + max_length=64, + blank=True, + ) + + +class DemoPayment(rego.PaymentBase): + ''' A subclass of PaymentBase for use in our demo payments function. ''' + + pass # No custom features here, but yours could be here. diff --git a/pinaxcon/registrasion/urls.py b/pinaxcon/registrasion/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..e609e522ada5c3515207861ad8f32121000645e3 --- /dev/null +++ b/pinaxcon/registrasion/urls.py @@ -0,0 +1,8 @@ +from django.conf.urls import url, patterns + +import views + +urlpatterns = patterns( + "pinaxcon.registrasion.views", + url(r"^demopay/([0-9]+)/([A-Z0-9]+)$", views.demopay, name="demopay"), +) diff --git a/pinaxcon/registrasion/views.py b/pinaxcon/registrasion/views.py new file mode 100644 index 0000000000000000000000000000000000000000..636707cc59cd822a8fdaaaeee75cd7c6605aefea --- /dev/null +++ b/pinaxcon/registrasion/views.py @@ -0,0 +1,47 @@ +from django.conf import settings +from django.contrib.auth.decorators import login_required +from django.contrib import messages +from django.core.exceptions import ObjectDoesNotExist +from django.core.exceptions import ValidationError +from django.http import Http404 +from django.shortcuts import get_object_or_404 +from django.shortcuts import redirect +from django.shortcuts import render + +from registrasion import models as rego +from registrasion.controllers.invoice import InvoiceController + +import models + + +def demopay(request, invoice_id, access_code): + ''' Marks the invoice with the given invoice id as paid. + ''' + invoice_id = int(invoice_id) + inv = get_object_or_404(rego.Invoice.objects,pk=invoice_id) + + invoice = InvoiceController(inv) + + if not invoice.can_view(user=request.user, access_code=access_code): + raise Http404() + + to_invoice = redirect("invoice", invoice.invoice.id, access_code) + + try: + invoice.validate_allowed_to_pay() # Verify that we're allowed to do this. + except ValidationError as ve: + messages.error(request, ve.message) + return to_invoice + + # Create the payment object + models.DemoPayment.objects.create( + invoice=invoice.invoice, + reference="Demo payment by user: " + request.user.username, + amount=invoice.invoice.value, + ) + + invoice.update_status() + + messages.success(request, "This invoice was successfully paid.") + + return to_invoice diff --git a/pinaxcon/settings.py b/pinaxcon/settings.py index b2d9a2cb85dfa3a4da491182b7325d912aa801ff..c157ca36648b65aa46f2b3d7494cb92a6c33c9cc 100644 --- a/pinaxcon/settings.py +++ b/pinaxcon/settings.py @@ -9,13 +9,13 @@ BASE_DIR = PACKAGE_ROOT DEBUG = bool(int(os.environ.get("DEBUG", "1"))) DATABASES = { - "default": dj_database_url.config(default="postgres://localhost/pinaxcon") + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(PROJECT_ROOT, "dev.db"), + } } -ALLOWED_HOSTS = [ - os.environ.get("GONDOR_INSTANCE_DOMAIN"), - "conference.pinaxproject.com" -] +ALLOWED_HOSTS = [] # Local time zone for this installation. Choices can be found here: # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name @@ -153,9 +153,16 @@ INSTALLED_APPS = [ "symposion.sponsorship", "symposion.teams", + # Registrasion + "registrasion", + + #admin - required by registrasion ?? + "nested_admin", + # project "pinaxcon", - "pinaxcon.proposals" + "pinaxcon.proposals", + "pinaxcon.registrasion", ] # A sample logging configuration. The only tangible logging @@ -212,3 +219,5 @@ PROPOSAL_FORMS = { } PINAX_PAGES_HOOKSET = "pinaxcon.hooks.PinaxPagesHookSet" PINAX_BOXES_HOOKSET = "pinaxcon.hooks.PinaxBoxesHookSet" + +ATTENDEE_PROFILE_FORM = "pinaxcon.registrasion.forms.ProfileForm" diff --git a/pinaxcon/templates/dashboard.html b/pinaxcon/templates/dashboard.html index 9e4f2de4088af788228264f7891af3185a149757..350c9d63ae8f707c846b2a50ce3bcde3f20fcb6f 100644 --- a/pinaxcon/templates/dashboard.html +++ b/pinaxcon/templates/dashboard.html @@ -4,6 +4,7 @@ {% load proposal_tags %} {% load review_tags %} {% load teams_tags %} +{% load registrasion_tags %} {% block head_title %}Dashboard{% endblock %} @@ -93,6 +94,79 @@ {% endif %} +
+
+
+ {% if not user.attendee.completed_registration %} + + Register for the conference + + {% else %} + + Edit your attendee profile + + + {% items_pending as pending %} + {% if pending %} + + Pay your registration + + {% endif %} + {% endif %} +
+

+ + {% trans "Registration" %} +

+ +
+ +
+ {% if not user.attendee.completed_registration %} +

To attend the conference, you must purchase a ticket. Use our registration form to purchase your ticket. + {% else %} +

Your registration

+ {% items_pending as pending %} + {% if pending %} +
Items pending payment
+ {% include "registrasion/items_list.html" with items=pending %} + {% endif %} + {% items_purchased as purchased %} + {% if purchased %} +
Paid items
+ {% include "registrasion/items_list.html" with items=purchased %} + {% endif %} +
Add/Update items
+ {% available_categories as categories %} + {% for category in categories %} +
  • {{ category.name }}
  • + {% endfor %} + + + {% invoices as invoices %} + {% if invoices %} +
    Invoices
    + + {% endif %} + + {% available_credit as credit %} + {% if credit %} +

    You have ${{ credit }} leftover from refunded invoices. Contact the conference organisers + to put this toward other purchases, or to refund it.

    + {% endif %} + {% endif %} +
    +
    +
    diff --git a/pinaxcon/templates/registrasion/base.html b/pinaxcon/templates/registrasion/base.html new file mode 100644 index 0000000000000000000000000000000000000000..d6a6615bb97e98750bea7887467c6406180605b1 --- /dev/null +++ b/pinaxcon/templates/registrasion/base.html @@ -0,0 +1,5 @@ +{% extends "site_base.html" %} + +{% block body_outer %} + {% block body %}{% endblock %} +{% endblock %} diff --git a/pinaxcon/templates/registrasion/checkout_errors.html b/pinaxcon/templates/registrasion/checkout_errors.html new file mode 100644 index 0000000000000000000000000000000000000000..5a1bcd4c984e6eee92aff9ef1e6838986f6be442 --- /dev/null +++ b/pinaxcon/templates/registrasion/checkout_errors.html @@ -0,0 +1,25 @@ +{% extends "registrasion/base.html" %} +{% load bootstrap %} +{% load registrasion_tags %} + +{% block body %} + +

    Oh No!

    + +

    We can't produce an invoice for you because of the following errors:

    + +
      + {% for error in error_list %} +
    • {{ error.message }}
    • + {% endfor %} +
    + +

    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 %} diff --git a/pinaxcon/templates/registrasion/credit_note.html b/pinaxcon/templates/registrasion/credit_note.html new file mode 100644 index 0000000000000000000000000000000000000000..5b9061547f15424ca0ac71f6526d3dd3d1d49660 --- /dev/null +++ b/pinaxcon/templates/registrasion/credit_note.html @@ -0,0 +1,40 @@ +{% extends "site_base.html" %} +{% load bootstrap %} +{% load registrasion_tags %} +{% block body %} + +

    Credit Note

    + +{% with note_user=credit_note.invoice.user %} +
      +
    • Number: {{ credit_note.id }} +
    • Attention: {{ credit_note.invoice.user.attendee.attendeeprofilebase.invoice_recipient }}
    • +
    • Value: {{ credit_note.value }}
    • +
    • Status: {{ credit_note.status }}
    • +
    +{% endwith %} + +

    This credit note was generated from funds excess from invoice {{ credit_note.invoice.id }}.

    + +{% if credit_note.is_unclaimed %} +
    + {% csrf_token %} +

    Apply to invoice

    +

    You can apply this credit note to an unpaid invoice.

    + + {{ apply_form|bootstrap }} +
    + +
    +

    Manual refund

    +

    You can mark this credit note as refunded, and handle the refund manually. +

    + + {{ refund_form|bootstrap }} +
    + +
    +
    +{% endif %} + +{% endblock %} diff --git a/pinaxcon/templates/registrasion/discount_list.html b/pinaxcon/templates/registrasion/discount_list.html new file mode 100644 index 0000000000000000000000000000000000000000..577c081be23d91957e4f23a96331a283f025bdae --- /dev/null +++ b/pinaxcon/templates/registrasion/discount_list.html @@ -0,0 +1,7 @@ +{% if discounts %} +
      + {% for discount in discounts %} +
    • {{ discount.quantity }} × {{ discount.clause }}
    • + {% endfor %} +
    +{% endif %} diff --git a/pinaxcon/templates/registrasion/guided_registration.html b/pinaxcon/templates/registrasion/guided_registration.html new file mode 100644 index 0000000000000000000000000000000000000000..24e22c39d31085127693f67b184438d8242b3586 --- /dev/null +++ b/pinaxcon/templates/registrasion/guided_registration.html @@ -0,0 +1,36 @@ +{% extends "registrasion/base.html" %} +{% load bootstrap %} + +{% block body %} + +

    Conference Registration – {{ title }}

    + +

    Step {{ current_step }} of {{ total_steps|add:1 }}

    + +
    + {% csrf_token %} + + {% for section in sections %} +

    {{ section.title }}

    + + {% if section.description %} +

    {{ section.description }}

    + {% endif %} + + {% if section.discounts %} +

    You have the following discounts available to you:

    + {% include "registrasion/discount_list.html" with discounts=section.discounts %} + {% endif %} + +
    + {{ section.form|bootstrap }} +
    + {% endfor %} + +
    + +
    +
    + + +{% endblock %} diff --git a/pinaxcon/templates/registrasion/guided_registration_complete.html b/pinaxcon/templates/registrasion/guided_registration_complete.html new file mode 100644 index 0000000000000000000000000000000000000000..2683957016d40d7929108ee7ee86e645feff80b3 --- /dev/null +++ b/pinaxcon/templates/registrasion/guided_registration_complete.html @@ -0,0 +1,35 @@ +{% extends "registrasion/base.html" %} +{% load bootstrap %} +{% load registrasion_tags %} + +{% block body %} + +

    Conference Registration – Review

    + + {% items_pending as pending %} + {% if pending %} + +

    Step 4 of 4

    + +

    You're almost done! You've selected the following items:

    + {% include "registrasion/items_list.html" with items=pending %} + +

    You can either generate an invoice and pay for your registration, or return to + the dashboard to make amendments.

    + + + + {% else %} + +

    You have no items that need to be paid.

    + + + + {% endif %} + +{% endblock %} diff --git a/pinaxcon/templates/registrasion/invoice.html b/pinaxcon/templates/registrasion/invoice.html new file mode 100644 index 0000000000000000000000000000000000000000..4e8612af8ae36bd5475c52563593d1b72faf7e2f --- /dev/null +++ b/pinaxcon/templates/registrasion/invoice.html @@ -0,0 +1,81 @@ +{% extends "site_base.html" %} +{% load bootstrap %} +{% load registrasion_tags %} +{% block body %} + + +{% if invoice.is_unpaid %} +

    NOTICE: 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.

    + + {% url "invoice_access" invoice.user.attendee.access_code as access_url %} +

    Your most recent unpaid invoice will be available at + {{ request.scheme }}://{{ request.get_host }}{{ access_url }} + You can give this URL to your accounts department to pay your registration.

    + +
    + Pay this invoice + {% if user.is_staff %} + Apply manual payment + {% endif %} +
    +{% elif invoice.is_paid %} + {% if user.is_staff %} +
    + {% if user.is_staff %} + Apply manual payment/refund + {% endif %} +
    + {% endif %} +{% endif %} + +
    + +

    Invoice

    + +{% with invoice_user=invoice.cart.user %} +
      +
    • Invoice number: {{ invoice.id }} +
    • Invoice status: {{ invoice.get_status_display }}
    • +
    • Issue date: {{ invoice.issue_time|date:"DATE_FORMAT" }} + {% if not invoice.is_void %} +
    • Due: {{ invoice.due_time|date:"DATETIME_FORMAT"}}
    • + {% endif %} +
    • Attention: {{ invoice_user.attendee.attendeeprofilebase.invoice_recipient }}
    • +
    +{% endwith %} + +

    This invoice has been issued as a result of an application to attend (conference name).

    + + + + + + + + + + {% for line_item in invoice.lineitem_set.all %} + + + + + + + {% endfor %} + + + + + + +
    DescriptionQuantityPrice/UnitTotal
    {{ line_item.description }}{{ line_item.quantity }}${{ line_item.price }}${{ line_item.price|multiply:line_item.quantity }}
    TOTAL${{ invoice.value }}
    + +{% if invoice.paymentbase_set.all %} +

    Payments received

    +{% include "registrasion/payment_list.html" with payments=invoice.paymentbase_set.all %} +{% endif %} + +{% endblock %} diff --git a/pinaxcon/templates/registrasion/items_list.html b/pinaxcon/templates/registrasion/items_list.html new file mode 100644 index 0000000000000000000000000000000000000000..1a214243f4ece54e91896098dcf490867de73fa9 --- /dev/null +++ b/pinaxcon/templates/registrasion/items_list.html @@ -0,0 +1,7 @@ +{% if items %} +
      + {% for item in items %} +
    • {{ item.quantity }} × {{ item.product }}
    • + {% endfor %} +
    +{% endif %} diff --git a/pinaxcon/templates/registrasion/manual_payment.html b/pinaxcon/templates/registrasion/manual_payment.html new file mode 100644 index 0000000000000000000000000000000000000000..cdfef2c21c6bb95176a63c32e9e20770cf4fd82a --- /dev/null +++ b/pinaxcon/templates/registrasion/manual_payment.html @@ -0,0 +1,31 @@ +{% extends "registrasion/base.html" %} +{% load bootstrap %} + +{% block body %} + +

    Invoice {{ invoice.id }} - {{ invoice.get_status_display }}

    + +

    Past payments

    + + {% include "registrasion/payment_list.html" with payments=invoice.paymentbase_set.all %} + +

    Apply manual payment

    + +

    Enter a reference and the amount of the payment. A refund is a negative + payment.

    + +
    + {% csrf_token %} + + + {{ form|bootstrap }} +
    + + +
    + + +{% endblock %} diff --git a/pinaxcon/templates/registrasion/payment_list.html b/pinaxcon/templates/registrasion/payment_list.html new file mode 100644 index 0000000000000000000000000000000000000000..7c2edbc39365eacda201fc92f4fdc8acd41f03c3 --- /dev/null +++ b/pinaxcon/templates/registrasion/payment_list.html @@ -0,0 +1,16 @@ +{% if payments %} + + + + + + + {% for payment in payments %} + + + + + + {% endfor %} +
    Payment timeReferenceAmount
    {{payment.time}}{{payment.reference}}{{payment.amount}}
    +{% endif %} diff --git a/pinaxcon/templates/registrasion/product_category.html b/pinaxcon/templates/registrasion/product_category.html new file mode 100644 index 0000000000000000000000000000000000000000..85c5a8909b1701e892523931b05135fab424279f --- /dev/null +++ b/pinaxcon/templates/registrasion/product_category.html @@ -0,0 +1,45 @@ +{% extends "registrasion/base.html" %} +{% load bootstrap %} +{% load registrasion_tags %} +{% block body %} + +

    Product Category: {{ category.name }}

    + +
    + {% csrf_token %} + + + {{ voucher_form | bootstrap }} +
    + +
    + +
    + + {% items_purchased category as items %} + {% if items %} +

    Paid items

    +

    You have already paid for the following items:

    + {% include "registrasion/items_list.html" with items=items %} + {% endif %} + + + {% if discounts %} +

    Available Discounts

    + {% include "registrasion/discount_list.html" with discounts=discounts %} + {% endif %} + +

    Available Products

    +

    {{ category.description }}

    + + {{ form | bootstrap }} +
    + +
    + +
    + +
    + + +{% endblock %} diff --git a/pinaxcon/templates/registrasion/profile_form.html b/pinaxcon/templates/registrasion/profile_form.html new file mode 100644 index 0000000000000000000000000000000000000000..103c2ad1e42f2f76a5dbe5f86ce5d4e5b12b73aa --- /dev/null +++ b/pinaxcon/templates/registrasion/profile_form.html @@ -0,0 +1,24 @@ +{% extends "registrasion/base.html" %} +{% load bootstrap %} + +{% block body %} + +

    Your profile

    + +

    These details will appear on your badge, your invoices, and will be used + to order catered food at the conference.

    + +
    + {% csrf_token %} + + + {{ form|bootstrap }} +
    + +
    + +
    +
    + + +{% endblock %} diff --git a/pinaxcon/urls.py b/pinaxcon/urls.py index 679742847dbac9659ee938d4387bd15e30de9bec..87de23ea41c4d50d0e2101ad837f6018b2f57681 100644 --- a/pinaxcon/urls.py +++ b/pinaxcon/urls.py @@ -26,6 +26,13 @@ urlpatterns = [ url(r"^boxes/", include("pinax.boxes.urls")), url(r"^", include("pinax.pages.urls")), + + # Required by registrasion + url(r'^register/', include('registrasion.urls')), + url(r'^nested_admin/', include('nested_admin.urls')), + + # Demo payment gateway and related features + url(r"^register/pinaxcon/", include("pinaxcon.registrasion.urls")), ] urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/requirements.txt b/requirements.txt index 89d29871e38546af895819f26e6b09b8027fbf8a..79344c0a002af4de8fb515ad1c63674de180f8b3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,10 +5,9 @@ metron==1.3.7 pinax-eventlog==1.1.1 dj-static==0.0.6 dj-database-url==0.4.0 -gunicorn==19.4.5 -psycopg2==2.6.1 pinax-pages==0.4.2 pinax-boxes==2.1.2 # run off of master for now git+https://github.com/pinax/symposion.git +git+https://github.com/chrisjrn/registrasion.git@demo_site_integration