Adds Registrasion support to the templates and apps.

This commit is contained in:
Christopher Neugebauer 2016-03-30 15:13:50 +11:00
parent 770e2ca88d
commit 6c94eb9e91
27 changed files with 1060 additions and 8 deletions

3
.gitignore vendored
View file

@ -1,3 +1,6 @@
*.pyc
node_modules/
_build/
.DS_Store
dev.db

393
fixtures/registrasion.json Normal file
View file

@ -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
]
}
}
]

View file

@ -0,0 +1 @@
default_app_config = "pinaxcon.registrasion.apps.RegistrasionConfig"

View file

@ -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")

View file

@ -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']

View file

@ -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',),
),
]

View file

@ -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',),
),
]

View file

@ -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.

View file

@ -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"),
)

View file

@ -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

View file

@ -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"

View file

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

View file

@ -0,0 +1,5 @@
{% extends "site_base.html" %}
{% block body_outer %}
{% block body %}{% endblock %}
{% endblock %}

View file

@ -0,0 +1,25 @@
{% extends "registrasion/base.html" %}
{% load bootstrap %}
{% load registrasion_tags %}
{% block body %}
<h1>Oh No!</h1>
<p>We can't produce an invoice for you because of the following errors:</p>
<ul>
{% for error in error_list %}
<li>{{ error.message }}</li>
{% endfor %}
</ul>
<p>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.</p>
<div class="form-actions">
<a class="btn btn-default" href="{% url "checkout" %}?fix_errors=true">Try fixing these errors</a>
<a class="btn btn-default" href="{% url "dashboard" %}">Return to dashboard</a>
</div>
{% endblock %}

View file

@ -0,0 +1,40 @@
{% extends "site_base.html" %}
{% load bootstrap %}
{% load registrasion_tags %}
{% block body %}
<h2>Credit Note</h2>
{% with note_user=credit_note.invoice.user %}
<ul>
<li><strong>Number:</strong> {{ credit_note.id }}
<li><strong>Attention:</strong> {{ credit_note.invoice.user.attendee.attendeeprofilebase.invoice_recipient }}</li>
<li><strong>Value:</strong> {{ credit_note.value }}</li>
<li><strong>Status:</strong> {{ credit_note.status }}</li>
</ul>
{% endwith %}
<p>This credit note was generated from funds excess from invoice {{ credit_note.invoice.id }}.</p>
{% if credit_note.is_unclaimed %}
<form method="post" action="">
{% csrf_token %}
<h3>Apply to invoice</h3>
<p>You can apply this credit note to an unpaid invoice.</p>
{{ apply_form|bootstrap }}
<div class="form-actions">
<input class="btn btn-primary" type="submit" value="Apply to invoice" />
</div>
<h3>Manual refund</h3>
<p>You can mark this credit note as refunded, and handle the refund manually.
</p>
{{ refund_form|bootstrap }}
<div class="form-actions">
<input class="btn btn-primary" type="submit" value="Mark as refunded" />
</div>
</form>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,7 @@
{% if discounts %}
<ul>
{% for discount in discounts %}
<li>{{ discount.quantity }} &times; {{ discount.clause }}</li>
{% endfor %}
</ul>
{% endif %}

View file

@ -0,0 +1,36 @@
{% extends "registrasion/base.html" %}
{% load bootstrap %}
{% block body %}
<h1>Conference Registration {{ title }}</h1>
<p><em>Step {{ current_step }} of {{ total_steps|add:1 }}</em></p>
<form method="post" action="">
{% csrf_token %}
{% for section in sections %}
<h3>{{ section.title }}</h3>
{% if section.description %}
<p>{{ section.description }}</p>
{% endif %}
{% if section.discounts %}
<p>You have the following discounts available to you:</p>
{% include "registrasion/discount_list.html" with discounts=section.discounts %}
{% endif %}
<fieldset>
{{ section.form|bootstrap }}
</fieldset>
{% endfor %}
<div class="form-actions">
<input class="btn btn-primary" type="submit" value="Next" />
</div>
</form>
{% endblock %}

View file

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

View file

@ -0,0 +1,81 @@
{% extends "site_base.html" %}
{% load bootstrap %}
{% load registrasion_tags %}
{% block body %}
{% if invoice.is_unpaid %}
<p><strong>NOTICE:</strong> The below invoice is automatically generated, and will be voided
if you amend your registration before payment, or if discounts or products contained in the
invoice become unavailable. The items and discounts are only reserved until
the invoice due time.</p>
{% url "invoice_access" invoice.user.attendee.access_code as access_url %}
<p>Your most recent unpaid invoice will be available at
<a href="{{ access_url }}">{{ request.scheme }}://{{ request.get_host }}{{ access_url }}</a>
You can give this URL to your accounts department to pay your registration.</p>
<div>
<a class="btn btn-default" href="{% url "demopay" invoice.id invoice.user.attendee.access_code %}">Pay this invoice</a>
{% if user.is_staff %}
<a class="btn btn-default" href="{% url "manual_payment" invoice.id %}">Apply manual payment</a>
{% endif %}
</div>
{% elif invoice.is_paid %}
{% if user.is_staff %}
<div>
{% if user.is_staff %}
<a class="btn btn-default" href="{% url "manual_payment" invoice.id %}">Apply manual payment/refund</a>
{% endif %}
</div>
{% endif %}
{% endif %}
<hr />
<h2>Invoice</h2>
{% with invoice_user=invoice.cart.user %}
<ul>
<li><strong>Invoice number:</strong> {{ invoice.id }}
<li><strong>Invoice status:</strong> {{ invoice.get_status_display }}</li>
<li><strong>Issue date:</strong> {{ invoice.issue_time|date:"DATE_FORMAT" }}
{% if not invoice.is_void %}
<li><strong>Due:</strong> {{ invoice.due_time|date:"DATETIME_FORMAT"}}</li>
{% endif %}
<li><strong>Attention:</strong> {{ invoice_user.attendee.attendeeprofilebase.invoice_recipient }}</li>
</ul>
{% endwith %}
<p>This invoice has been issued as a result of an application to attend (conference name).</p>
<table class="table table-striped">
<tr>
<th>Description</th>
<th class="text-right">Quantity</th>
<th class="text-right">Price/Unit</th>
<th class="text-right">Total</th>
</tr>
{% for line_item in invoice.lineitem_set.all %}
<tr>
<td>{{ line_item.description }}</td>
<td class="text-right">{{ line_item.quantity }}</td>
<td class="text-right">${{ line_item.price }}</td>
<td class="text-right">${{ line_item.price|multiply:line_item.quantity }}</td>
</tr>
{% endfor %}
<tr>
<th>TOTAL</th>
<td></td>
<td></td>
<td class="text-right">${{ invoice.value }}</td>
</tr>
</table>
{% if invoice.paymentbase_set.all %}
<h4>Payments received</h4>
{% include "registrasion/payment_list.html" with payments=invoice.paymentbase_set.all %}
{% endif %}
{% endblock %}

View file

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

View file

@ -0,0 +1,31 @@
{% extends "registrasion/base.html" %}
{% load bootstrap %}
{% block body %}
<h1>Invoice {{ invoice.id }} - {{ invoice.get_status_display }}</h1>
<h2>Past payments</h2>
{% include "registrasion/payment_list.html" with payments=invoice.paymentbase_set.all %}
<h2>Apply manual payment</h2>
<p>Enter a reference and the amount of the payment. A refund is a negative
payment.</p>
<form method="post" action="">
{% csrf_token %}
<table>
{{ form|bootstrap }}
</table>
<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>
{% endblock %}

View file

@ -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 %}

View file

@ -0,0 +1,45 @@
{% extends "registrasion/base.html" %}
{% load bootstrap %}
{% load registrasion_tags %}
{% block body %}
<h1>Product Category: {{ category.name }}</h1>
<form method="post" action="">
{% csrf_token %}
<table>
{{ voucher_form | bootstrap }}
</table>
<div class="form-actions">
<input class="btn btn-primary" type="submit" value="Add voucher" />
</div>
{% items_purchased category as items %}
{% if items %}
<h3>Paid items</h3>
<p>You have already paid for the following items:</p>
{% include "registrasion/items_list.html" with items=items %}
{% endif %}
{% if discounts %}
<h3>Available Discounts</h3>
{% include "registrasion/discount_list.html" with discounts=discounts %}
{% endif %}
<h3>Available Products</h3>
<p>{{ category.description }}</p>
<table>
{{ form | bootstrap }}
</table>
<div class="form-actions">
<input class="btn btn-primary" type="submit" value="Add to cart" />
</div>
</form>
{% endblock %}

View file

@ -0,0 +1,24 @@
{% extends "registrasion/base.html" %}
{% load bootstrap %}
{% block body %}
<h1>Your profile</h1>
<p>These details will appear on your badge, your invoices, and will be used
to order catered food at the conference.</p>
<form method="post" action="">
{% csrf_token %}
<table>
{{ form|bootstrap }}
</table>
<div class="form-actions">
<input class="btn btn-primary" type="submit" value="Save" />
</div>
</form>
{% endblock %}

View file

@ -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)

View file

@ -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