git subrepo clone git@gitlab.com:tchaypo/registrasion.git vendor/registrasion
subrepo: subdir: "vendor/registrasion" merged: "7cf314a" upstream: origin: "git@gitlab.com:tchaypo/registrasion.git" branch: "lca2018" commit: "7cf314a" git-subrepo: version: "0.3.1" origin: "???" commit: "???"
This commit is contained in:
parent
2580584597
commit
162b5edc20
19 changed files with 477 additions and 211 deletions
4
vendor/registrasion/.gitrepo
vendored
4
vendor/registrasion/.gitrepo
vendored
|
@ -6,6 +6,6 @@
|
||||||
[subrepo]
|
[subrepo]
|
||||||
remote = git@gitlab.com:tchaypo/registrasion.git
|
remote = git@gitlab.com:tchaypo/registrasion.git
|
||||||
branch = lca2018
|
branch = lca2018
|
||||||
commit = c1e194aef92e4c06a8855fad22ca819c08736dad
|
commit = 7cf314adae9f8de1519db63828f55a10aa09f0ee
|
||||||
parent = dd8a42e9f67cfebc39347cb87bacf79046bfbfec
|
parent = 7c5c6c02f20ddcbc6c54b3065311880fce586192
|
||||||
cmdver = 0.3.1
|
cmdver = 0.3.1
|
||||||
|
|
|
@ -9,6 +9,8 @@ from django.db.models import Value
|
||||||
|
|
||||||
from .batch import BatchController
|
from .batch import BatchController
|
||||||
|
|
||||||
|
from operator import attrgetter
|
||||||
|
|
||||||
|
|
||||||
class AllProducts(object):
|
class AllProducts(object):
|
||||||
pass
|
pass
|
||||||
|
@ -26,7 +28,7 @@ class CategoryController(object):
|
||||||
products, otherwise it'll do all. '''
|
products, otherwise it'll do all. '''
|
||||||
|
|
||||||
# STOPGAP -- this needs to be elsewhere tbqh
|
# STOPGAP -- this needs to be elsewhere tbqh
|
||||||
from registrasion.controllers.product import ProductController
|
from .product import ProductController
|
||||||
|
|
||||||
if products is AllProducts:
|
if products is AllProducts:
|
||||||
products = inventory.Product.objects.all().select_related(
|
products = inventory.Product.objects.all().select_related(
|
||||||
|
@ -38,7 +40,7 @@ class CategoryController(object):
|
||||||
products=products,
|
products=products,
|
||||||
)
|
)
|
||||||
|
|
||||||
return set(i.category for i in available)
|
return sorted(set(i.category for i in available), key=attrgetter("order"))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@BatchController.memoise
|
@BatchController.memoise
|
||||||
|
|
|
@ -4,7 +4,7 @@ from django.db import transaction
|
||||||
|
|
||||||
from registrasion.models import commerce
|
from registrasion.models import commerce
|
||||||
|
|
||||||
from registrasion.controllers.for_id import ForId
|
from .for_id import ForId
|
||||||
|
|
||||||
|
|
||||||
class CreditNoteController(ForId, object):
|
class CreditNoteController(ForId, object):
|
||||||
|
@ -40,8 +40,8 @@ class CreditNoteController(ForId, object):
|
||||||
paid.
|
paid.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# Circular Import
|
# Local import to fix import cycles. Can we do better?
|
||||||
from registrasion.controllers.invoice import InvoiceController
|
from .invoice import InvoiceController
|
||||||
inv = InvoiceController(invoice)
|
inv = InvoiceController(invoice)
|
||||||
inv.validate_allowed_to_pay()
|
inv.validate_allowed_to_pay()
|
||||||
|
|
||||||
|
@ -65,8 +65,8 @@ class CreditNoteController(ForId, object):
|
||||||
a cancellation fee. Must be 0 <= percentage <= 100.
|
a cancellation fee. Must be 0 <= percentage <= 100.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# Circular Import
|
# Local import to fix import cycles. Can we do better?
|
||||||
from registrasion.controllers.invoice import InvoiceController
|
from .invoice import InvoiceController
|
||||||
|
|
||||||
assert(percentage >= 0 and percentage <= 100)
|
assert(percentage >= 0 and percentage <= 100)
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,28 @@ class FlagController(object):
|
||||||
else:
|
else:
|
||||||
all_conditions = []
|
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
|
# All disable-if-false conditions on a product need to be met
|
||||||
do_not_disable = defaultdict(lambda: True)
|
do_not_disable = defaultdict(lambda: True)
|
||||||
# At least one enable-if-true condition on a product must be met
|
# 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
|
# Get all products covered by this condition, and the products
|
||||||
# from the categories covered by this condition
|
# from the categories covered by this condition
|
||||||
|
|
||||||
ids = [product.id for product in products]
|
condition_products = condition.products.all()
|
||||||
|
category_products = (
|
||||||
# TODO: This is re-evaluated a lot.
|
product for cat in condition.categories.all() for product in products_by_category[cat.id]
|
||||||
all_products = inventory.Product.objects.filter(id__in=ids)
|
|
||||||
cond = (
|
|
||||||
Q(flagbase_set=condition) |
|
|
||||||
Q(category__in=condition.categories.all())
|
|
||||||
)
|
)
|
||||||
|
|
||||||
all_products = all_products.filter(cond)
|
all_products = itertools.chain(
|
||||||
all_products = all_products.select_related("category")
|
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:
|
if quantities:
|
||||||
consumed = sum(quantities[i] for i in all_products)
|
consumed = sum(quantities[i] for i in all_products)
|
||||||
|
|
|
@ -10,9 +10,9 @@ from registrasion.models import commerce
|
||||||
from registrasion.models import conditions
|
from registrasion.models import conditions
|
||||||
from registrasion.models import people
|
from registrasion.models import people
|
||||||
|
|
||||||
from registrasion.controllers.cart import CartController
|
from .cart import CartController
|
||||||
from registrasion.controllers.credit_note import CreditNoteController
|
from .credit_note import CreditNoteController
|
||||||
from registrasion.controllers.for_id import ForId
|
from .for_id import ForId
|
||||||
|
|
||||||
|
|
||||||
class InvoiceController(ForId, object):
|
class InvoiceController(ForId, object):
|
||||||
|
|
44
vendor/registrasion/registrasion/forms.py
vendored
44
vendor/registrasion/registrasion/forms.py
vendored
|
@ -1,6 +1,6 @@
|
||||||
from registrasion.controllers.product import ProductController
|
from .controllers.product import ProductController
|
||||||
from registrasion.models import commerce
|
from .models import commerce
|
||||||
from registrasion.models import inventory
|
from .models import inventory
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
@ -31,12 +31,13 @@ class ApplyCreditNoteForm(forms.Form):
|
||||||
"user_email": users[invoice["user_id"]].email,
|
"user_email": users[invoice["user_id"]].email,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
key = lambda inv: (0 - (inv["user_id"] == self.user.id), inv["id"]) # noqa
|
key = lambda inv: (0 - (inv["user_id"] == self.user.id), inv["id"]) # noqa
|
||||||
invoices_annotated.sort(key=key)
|
invoices_annotated.sort(key=key)
|
||||||
|
|
||||||
template = ('Invoice %(id)d - user: %(user_email)s (%(user_id)d) '
|
template = (
|
||||||
'- $%(value)d')
|
'Invoice %(id)d - user: %(user_email)s (%(user_id)d) '
|
||||||
|
'- $%(value)d'
|
||||||
|
)
|
||||||
return [
|
return [
|
||||||
(invoice["id"], template % invoice)
|
(invoice["id"], template % invoice)
|
||||||
for invoice in invoices_annotated
|
for invoice in invoices_annotated
|
||||||
|
@ -94,6 +95,7 @@ def ProductsForm(category, products):
|
||||||
cat.RENDER_TYPE_QUANTITY: _QuantityBoxProductsForm,
|
cat.RENDER_TYPE_QUANTITY: _QuantityBoxProductsForm,
|
||||||
cat.RENDER_TYPE_RADIO: _RadioButtonProductsForm,
|
cat.RENDER_TYPE_RADIO: _RadioButtonProductsForm,
|
||||||
cat.RENDER_TYPE_ITEM_QUANTITY: _ItemQuantityProductsForm,
|
cat.RENDER_TYPE_ITEM_QUANTITY: _ItemQuantityProductsForm,
|
||||||
|
cat.RENDER_TYPE_CHECKBOX: _CheckboxProductsForm,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Produce a subclass of _ProductsForm which we can alter the base_fields on
|
# Produce a subclass of _ProductsForm which we can alter the base_fields on
|
||||||
|
@ -252,6 +254,35 @@ class _RadioButtonProductsForm(_ProductsForm):
|
||||||
self.add_error(self.FIELD, error)
|
self.add_error(self.FIELD, error)
|
||||||
|
|
||||||
|
|
||||||
|
class _CheckboxProductsForm(_ProductsForm):
|
||||||
|
''' Products entry form that allows users to say yes or no
|
||||||
|
to desired products. Basically, it's a quantity form, but the quantity
|
||||||
|
is either zero or one.'''
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def set_fields(cls, category, products):
|
||||||
|
for product in products:
|
||||||
|
field = forms.BooleanField(
|
||||||
|
label='%s -- %s' % (product.name, product.price),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
cls.base_fields[cls.field_name(product)] = field
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def initial_data(cls, product_quantities):
|
||||||
|
initial = {}
|
||||||
|
for product, quantity in product_quantities:
|
||||||
|
initial[cls.field_name(product)] = bool(quantity)
|
||||||
|
|
||||||
|
return initial
|
||||||
|
|
||||||
|
def product_quantities(self):
|
||||||
|
for name, value in self.cleaned_data.items():
|
||||||
|
if name.startswith(self.PRODUCT_PREFIX):
|
||||||
|
product_id = int(name[len(self.PRODUCT_PREFIX):])
|
||||||
|
yield (product_id, int(value))
|
||||||
|
|
||||||
|
|
||||||
class _ItemQuantityProductsForm(_ProductsForm):
|
class _ItemQuantityProductsForm(_ProductsForm):
|
||||||
''' Products entry form that allows users to select a product type, and
|
''' Products entry form that allows users to select a product type, and
|
||||||
enter a quantity of that product. This version _only_ allows a single
|
enter a quantity of that product. This version _only_ allows a single
|
||||||
|
@ -449,7 +480,6 @@ class InvoicesWithProductAndStatusForm(forms.Form):
|
||||||
product = [int(i) for i in product]
|
product = [int(i) for i in product]
|
||||||
|
|
||||||
super(InvoicesWithProductAndStatusForm, self).__init__(*a, **k)
|
super(InvoicesWithProductAndStatusForm, self).__init__(*a, **k)
|
||||||
print(status)
|
|
||||||
|
|
||||||
qs = commerce.Invoice.objects.filter(
|
qs = commerce.Invoice.objects.filter(
|
||||||
status=status or commerce.Invoice.STATUS_UNPAID,
|
status=status or commerce.Invoice.STATUS_UNPAID,
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.11.5 on 2017-09-29 12:59
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('registrasion', '0006_auto_20170526_1624'),
|
|
||||||
('registrasion', '0006_auto_20170702_2233'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
]
|
|
|
@ -1,4 +1,4 @@
|
||||||
from registrasion.models.commerce import * # NOQA
|
from .commerce import * # NOQA
|
||||||
from registrasion.models.conditions import * # NOQA
|
from .conditions import * # NOQA
|
||||||
from registrasion.models.inventory import * # NOQA
|
from .inventory import * # NOQA
|
||||||
from registrasion.models.people import * # NOQA
|
from .people import * # NOQA
|
||||||
|
|
|
@ -324,7 +324,6 @@ class CreditNote(PaymentBase):
|
||||||
|
|
||||||
elif hasattr(self, 'creditnoterefund'):
|
elif hasattr(self, 'creditnoterefund'):
|
||||||
reference = self.creditnoterefund.reference
|
reference = self.creditnoterefund.reference
|
||||||
print(reference)
|
|
||||||
return "Refunded with reference: %s" % reference
|
return "Refunded with reference: %s" % reference
|
||||||
|
|
||||||
raise ValueError("This should never happen.")
|
raise ValueError("This should never happen.")
|
||||||
|
|
|
@ -42,6 +42,8 @@ class Category(models.Model):
|
||||||
have a lot of options, from which the user is not going to select
|
have a lot of options, from which the user is not going to select
|
||||||
all of the options.
|
all of the options.
|
||||||
|
|
||||||
|
``RENDER_TYPE_CHECKBOX`` shows a checkbox beside each product.
|
||||||
|
|
||||||
limit_per_user (Optional[int]): This restricts the number of items
|
limit_per_user (Optional[int]): This restricts the number of items
|
||||||
from this Category that each attendee may claim. This extends
|
from this Category that each attendee may claim. This extends
|
||||||
across multiple Invoices.
|
across multiple Invoices.
|
||||||
|
@ -63,11 +65,13 @@ class Category(models.Model):
|
||||||
RENDER_TYPE_RADIO = 1
|
RENDER_TYPE_RADIO = 1
|
||||||
RENDER_TYPE_QUANTITY = 2
|
RENDER_TYPE_QUANTITY = 2
|
||||||
RENDER_TYPE_ITEM_QUANTITY = 3
|
RENDER_TYPE_ITEM_QUANTITY = 3
|
||||||
|
RENDER_TYPE_CHECKBOX = 4
|
||||||
|
|
||||||
CATEGORY_RENDER_TYPES = [
|
CATEGORY_RENDER_TYPES = [
|
||||||
(RENDER_TYPE_RADIO, _("Radio button")),
|
(RENDER_TYPE_RADIO, _("Radio button")),
|
||||||
(RENDER_TYPE_QUANTITY, _("Quantity boxes")),
|
(RENDER_TYPE_QUANTITY, _("Quantity boxes")),
|
||||||
(RENDER_TYPE_ITEM_QUANTITY, _("Product selector and quantity box")),
|
(RENDER_TYPE_ITEM_QUANTITY, _("Product selector and quantity box")),
|
||||||
|
(RENDER_TYPE_CHECKBOX, _("Checkbox button")),
|
||||||
]
|
]
|
||||||
|
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
|
|
|
@ -177,7 +177,6 @@ class Links(Report):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def rows(self, content_type):
|
def rows(self, content_type):
|
||||||
print(self._links)
|
|
||||||
for url, link_text in self._links:
|
for url, link_text in self._links:
|
||||||
yield [
|
yield [
|
||||||
self._linked_text(content_type, url, link_text)
|
self._linked_text(content_type, url, link_text)
|
||||||
|
@ -299,9 +298,10 @@ class ReportView(object):
|
||||||
response = HttpResponse(content_type='text/csv')
|
response = HttpResponse(content_type='text/csv')
|
||||||
|
|
||||||
writer = csv.writer(response)
|
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():
|
for row in report.rows():
|
||||||
writer.writerow(row)
|
writer.writerow(list(encode(i) for i in row))
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from registrasion.reporting import forms
|
from . import forms
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
import datetime
|
import datetime
|
||||||
|
@ -24,11 +24,11 @@ from registrasion import views
|
||||||
|
|
||||||
from symposion.schedule import models as schedule_models
|
from symposion.schedule import models as schedule_models
|
||||||
|
|
||||||
from registrasion.reporting.reports import get_all_reports
|
from .reports import get_all_reports
|
||||||
from registrasion.reporting.reports import Links
|
from .reports import Links
|
||||||
from registrasion.reporting.reports import ListReport
|
from .reports import ListReport
|
||||||
from registrasion.reporting.reports import QuerysetReport
|
from .reports import QuerysetReport
|
||||||
from registrasion.reporting.reports import report_view
|
from .reports import report_view
|
||||||
|
|
||||||
|
|
||||||
def CURRENCY():
|
def CURRENCY():
|
||||||
|
@ -95,8 +95,6 @@ def items_sold():
|
||||||
total_quantity=Sum("quantity"),
|
total_quantity=Sum("quantity"),
|
||||||
)
|
)
|
||||||
|
|
||||||
print(line_items)
|
|
||||||
|
|
||||||
headings = ["Description", "Quantity", "Price", "Total"]
|
headings = ["Description", "Quantity", "Price", "Total"]
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
|
@ -312,6 +310,55 @@ def discount_status(request, form):
|
||||||
return ListReport("Usage by item", headings, data)
|
return ListReport("Usage by item", headings, data)
|
||||||
|
|
||||||
|
|
||||||
|
@report_view("Product Line Items By Date & Customer", form_type=forms.ProductAndCategoryForm)
|
||||||
|
def product_line_items(request, form):
|
||||||
|
''' Shows each product line item from invoices, including their date and
|
||||||
|
purchashing customer. '''
|
||||||
|
|
||||||
|
products = form.cleaned_data["product"]
|
||||||
|
categories = form.cleaned_data["category"]
|
||||||
|
|
||||||
|
invoices = commerce.Invoice.objects.filter(
|
||||||
|
(
|
||||||
|
Q(lineitem__product__in=products) |
|
||||||
|
Q(lineitem__product__category__in=categories)
|
||||||
|
),
|
||||||
|
status=commerce.Invoice.STATUS_PAID,
|
||||||
|
).select_related(
|
||||||
|
"cart",
|
||||||
|
"user",
|
||||||
|
"user__attendee",
|
||||||
|
"user__attendee__attendeeprofilebase"
|
||||||
|
).order_by("issue_time")
|
||||||
|
|
||||||
|
headings = [
|
||||||
|
'Invoice', 'Invoice Date', 'Attendee', 'Qty', 'Product', 'Status'
|
||||||
|
]
|
||||||
|
|
||||||
|
data = []
|
||||||
|
for invoice in invoices:
|
||||||
|
for item in invoice.cart.productitem_set.all():
|
||||||
|
if item.product in products or item.product.category in categories:
|
||||||
|
output = []
|
||||||
|
output.append(invoice.id)
|
||||||
|
output.append(invoice.issue_time.strftime('%Y-%m-%d %H:%M:%S'))
|
||||||
|
output.append(
|
||||||
|
invoice.user.attendee.attendeeprofilebase.attendee_name()
|
||||||
|
)
|
||||||
|
output.append(item.quantity)
|
||||||
|
output.append(item.product)
|
||||||
|
cart = invoice.cart
|
||||||
|
if cart.status == commerce.Cart.STATUS_PAID:
|
||||||
|
output.append('PAID')
|
||||||
|
elif cart.status == commerce.Cart.STATUS_ACTIVE:
|
||||||
|
output.append('UNPAID')
|
||||||
|
elif cart.status == commerce.Cart.STATUS_RELEASED:
|
||||||
|
output.append('REFUNDED')
|
||||||
|
data.append(output)
|
||||||
|
|
||||||
|
return ListReport("Line Items", headings, data)
|
||||||
|
|
||||||
|
|
||||||
@report_view("Paid invoices by date", form_type=forms.ProductAndCategoryForm)
|
@report_view("Paid invoices by date", form_type=forms.ProductAndCategoryForm)
|
||||||
def paid_invoices_by_date(request, form):
|
def paid_invoices_by_date(request, form):
|
||||||
''' Shows the number of paid invoices containing given products or
|
''' Shows the number of paid invoices containing given products or
|
||||||
|
@ -353,8 +400,8 @@ def paid_invoices_by_date(request, form):
|
||||||
)
|
)
|
||||||
by_date[date] += 1
|
by_date[date] += 1
|
||||||
|
|
||||||
data = [(date, count) for date, count in sorted(by_date.items())]
|
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_.strftime("%Y-%m-%d"), count) for date_, count in data]
|
||||||
|
|
||||||
return ListReport(
|
return ListReport(
|
||||||
"Paid Invoices By Date",
|
"Paid Invoices By Date",
|
||||||
|
@ -417,8 +464,6 @@ def attendee(request, form, user_id=None):
|
||||||
if user_id is None:
|
if user_id is None:
|
||||||
return attendee_list(request)
|
return attendee_list(request)
|
||||||
|
|
||||||
print(user_id)
|
|
||||||
|
|
||||||
attendee = people.Attendee.objects.get(user__id=user_id)
|
attendee = people.Attendee.objects.get(user__id=user_id)
|
||||||
name = attendee.attendeeprofilebase.attendee_name()
|
name = attendee.attendeeprofilebase.attendee_name()
|
||||||
|
|
||||||
|
@ -846,9 +891,10 @@ def manifest(request, form):
|
||||||
headings = ["User ID", "Name", "Paid", "Unpaid", "Refunded"]
|
headings = ["User ID", "Name", "Paid", "Unpaid", "Refunded"]
|
||||||
|
|
||||||
def format_items(item_list):
|
def format_items(item_list):
|
||||||
strings = []
|
strings = [
|
||||||
for item in item_list:
|
'%d x %s' % (item.quantity, str(item.product))
|
||||||
strings.append('%d x %s' % (item.quantity, str(item.product)))
|
for item in item_list
|
||||||
|
]
|
||||||
return ", \n".join(strings)
|
return ", \n".join(strings)
|
||||||
|
|
||||||
output = []
|
output = []
|
||||||
|
|
|
@ -3,8 +3,9 @@ from registrasion.controllers.category import CategoryController
|
||||||
from registrasion.controllers.item import ItemController
|
from registrasion.controllers.item import ItemController
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
|
from django.conf import settings
|
||||||
from django.db.models import Sum
|
from django.db.models import Sum
|
||||||
from urllib.parse import urlencode
|
from urllib import urlencode # TODO: s/urllib/six.moves.urllib/
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
@ -117,3 +118,69 @@ def report_as_csv(context, section):
|
||||||
querystring = old_query + "&" + querystring
|
querystring = old_query + "&" + querystring
|
||||||
|
|
||||||
return context.request.path + "?" + 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)
|
||||||
|
|
|
@ -85,7 +85,7 @@ class RegistrationCartTestCase(MixInPatches, TestCase):
|
||||||
prod = inventory.Product.objects.create(
|
prod = inventory.Product.objects.create(
|
||||||
name="Product " + str(i + 1),
|
name="Product " + str(i + 1),
|
||||||
description="This is a test product.",
|
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"),
|
price=Decimal("10.00"),
|
||||||
reservation_duration=cls.RESERVATION,
|
reservation_duration=cls.RESERVATION,
|
||||||
limit_per_user=10,
|
limit_per_user=10,
|
||||||
|
|
|
@ -98,11 +98,7 @@ class InvoiceTestCase(TestHelperMixin, RegistrationCartTestCase):
|
||||||
|
|
||||||
def test_total_payments_balance_due(self):
|
def test_total_payments_balance_due(self):
|
||||||
invoice = self._invoice_containing_prod_1(2)
|
invoice = self._invoice_containing_prod_1(2)
|
||||||
# range only takes int, and the following logic fails if not a round
|
for i in xrange(0, invoice.invoice.value):
|
||||||
# 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)):
|
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
i + 1, invoice.invoice.total_payments()
|
i + 1, invoice.invoice.total_payments()
|
||||||
)
|
)
|
||||||
|
|
|
@ -67,7 +67,7 @@ class SpeakerTestCase(RegistrationCartTestCase):
|
||||||
kind=kind_1,
|
kind=kind_1,
|
||||||
title="Proposal 1",
|
title="Proposal 1",
|
||||||
abstract="Abstract",
|
abstract="Abstract",
|
||||||
private_abstract="Private Abstract",
|
description="Description",
|
||||||
speaker=speaker_1,
|
speaker=speaker_1,
|
||||||
)
|
)
|
||||||
proposal_models.AdditionalSpeaker.objects.create(
|
proposal_models.AdditionalSpeaker.objects.create(
|
||||||
|
@ -80,7 +80,7 @@ class SpeakerTestCase(RegistrationCartTestCase):
|
||||||
kind=kind_2,
|
kind=kind_2,
|
||||||
title="Proposal 2",
|
title="Proposal 2",
|
||||||
abstract="Abstract",
|
abstract="Abstract",
|
||||||
private_abstract="Private Abstract",
|
description="Description",
|
||||||
speaker=speaker_1,
|
speaker=speaker_1,
|
||||||
)
|
)
|
||||||
proposal_models.AdditionalSpeaker.objects.create(
|
proposal_models.AdditionalSpeaker.objects.create(
|
||||||
|
|
9
vendor/registrasion/registrasion/urls.py
vendored
9
vendor/registrasion/registrasion/urls.py
vendored
|
@ -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 include
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
@ -19,6 +19,7 @@ from .views import (
|
||||||
product_category,
|
product_category,
|
||||||
refund,
|
refund,
|
||||||
review,
|
review,
|
||||||
|
voucher_code,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,6 +44,7 @@ public = [
|
||||||
url(r"^profile$", edit_profile, name="attendee_edit"),
|
url(r"^profile$", edit_profile, name="attendee_edit"),
|
||||||
url(r"^register$", guided_registration, name="guided_registration"),
|
url(r"^register$", guided_registration, name="guided_registration"),
|
||||||
url(r"^review$", review, name="review"),
|
url(r"^review$", review, name="review"),
|
||||||
|
url(r"^voucher$", voucher_code, name="voucher_code"),
|
||||||
url(r"^register/([0-9]+)$", guided_registration,
|
url(r"^register/([0-9]+)$", guided_registration,
|
||||||
name="guided_registration"),
|
name="guided_registration"),
|
||||||
]
|
]
|
||||||
|
@ -55,6 +57,11 @@ reports = [
|
||||||
url(r"^attendee/([0-9]*)$", rv.attendee, name="attendee"),
|
url(r"^attendee/([0-9]*)$", rv.attendee, name="attendee"),
|
||||||
url(r"^credit_notes/?$", rv.credit_notes, name="credit_notes"),
|
url(r"^credit_notes/?$", rv.credit_notes, name="credit_notes"),
|
||||||
url(r"^manifest/?$", rv.manifest, name="manifest"),
|
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"^discount_status/?$", rv.discount_status, name="discount_status"),
|
||||||
url(r"^invoices/?$", rv.invoices, name="invoices"),
|
url(r"^invoices/?$", rv.invoices, name="invoices"),
|
||||||
url(
|
url(
|
||||||
|
|
2
vendor/registrasion/registrasion/util.py
vendored
2
vendor/registrasion/registrasion/util.py
vendored
|
@ -12,7 +12,7 @@ def generate_access_code():
|
||||||
|
|
||||||
length = 6
|
length = 6
|
||||||
# all upper-case letters + digits 1-9 (no 0 vs O confusion)
|
# all upper-case letters + digits 1-9 (no 0 vs O confusion)
|
||||||
chars = string.ascii_uppercase + string.digits[1:]
|
chars = string.uppercase + string.digits[1:]
|
||||||
# 6 chars => 35 ** 6 = 1838265625 (should be enough for anyone)
|
# 6 chars => 35 ** 6 = 1838265625 (should be enough for anyone)
|
||||||
return get_random_string(length=length, allowed_chars=chars)
|
return get_random_string(length=length, allowed_chars=chars)
|
||||||
|
|
||||||
|
|
303
vendor/registrasion/registrasion/views.py
vendored
303
vendor/registrasion/registrasion/views.py
vendored
|
@ -1,19 +1,20 @@
|
||||||
import datetime
|
import datetime
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
from registrasion import forms
|
from . import forms
|
||||||
from registrasion import util
|
from . import util
|
||||||
from registrasion.models import commerce
|
from .models import commerce
|
||||||
from registrasion.models import inventory
|
from .models import inventory
|
||||||
from registrasion.models import people
|
from .models import people
|
||||||
from registrasion.controllers.batch import BatchController
|
from .controllers.batch import BatchController
|
||||||
from registrasion.controllers.cart import CartController
|
from .controllers.cart import CartController
|
||||||
from registrasion.controllers.credit_note import CreditNoteController
|
from .controllers.category import CategoryController
|
||||||
from registrasion.controllers.discount import DiscountController
|
from .controllers.credit_note import CreditNoteController
|
||||||
from registrasion.controllers.invoice import InvoiceController
|
from .controllers.discount import DiscountController
|
||||||
from registrasion.controllers.item import ItemController
|
from .controllers.invoice import InvoiceController
|
||||||
from registrasion.controllers.product import ProductController
|
from .controllers.item import ItemController
|
||||||
from registrasion.exceptions import CartValidationError
|
from .controllers.product import ProductController
|
||||||
|
from .exceptions import CartValidationError
|
||||||
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
|
@ -64,12 +65,19 @@ class GuidedRegistrationSection(_GuidedRegistrationSection):
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def guided_registration(request):
|
def guided_registration(request, page_number=None):
|
||||||
''' Goes through the registration process in order, making sure user sees
|
''' Goes through the registration process in order, making sure user sees
|
||||||
all valid categories.
|
all valid categories.
|
||||||
|
|
||||||
The user must be logged in to see this view.
|
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:
|
Returns:
|
||||||
render: Renders ``registrasion/guided_registration.html``,
|
render: Renders ``registrasion/guided_registration.html``,
|
||||||
with the following data::
|
with the following data::
|
||||||
|
@ -85,87 +93,167 @@ def guided_registration(request):
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
SESSION_KEY = "guided_registration_categories"
|
PAGE_PROFILE = 1
|
||||||
ASK_FOR_PROFILE = 777 # Magic number. Meh.
|
PAGE_TICKET = 2
|
||||||
|
PAGE_PRODUCTS = 3
|
||||||
|
PAGE_PRODUCTS_MAX = 4
|
||||||
|
TOTAL_PAGES = 4
|
||||||
|
|
||||||
next_step = redirect("guided_registration")
|
ticket_category = inventory.Category.objects.get(
|
||||||
|
id=settings.TICKET_PRODUCT_CATEGORY
|
||||||
sections = []
|
)
|
||||||
|
cart = CartController.for_user(request.user)
|
||||||
|
|
||||||
attendee = people.Attendee.get_instance(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:
|
if attendee.completed_registration:
|
||||||
return redirect(review)
|
return redirect(review)
|
||||||
|
|
||||||
# Step 1: Fill in a badge and collect a voucher code
|
# Calculate the current maximum page number for this user.
|
||||||
try:
|
has_profile = hasattr(attendee, "attendeeprofilebase")
|
||||||
profile = attendee.attendeeprofilebase
|
if not has_profile:
|
||||||
except ObjectDoesNotExist:
|
# If there's no profile, they have to go to the profile page.
|
||||||
profile = None
|
max_page = PAGE_PROFILE
|
||||||
|
redirect_page = PAGE_PROFILE
|
||||||
# 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
|
|
||||||
else:
|
else:
|
||||||
if request.session[SESSION_KEY] == ASK_FOR_PROFILE:
|
# We have a profile.
|
||||||
show_profile_and_voucher = True
|
# Do they have a ticket?
|
||||||
|
products = inventory.Product.objects.filter(
|
||||||
if show_profile_and_voucher:
|
productitem__cart=cart.cart
|
||||||
# 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,
|
|
||||||
)
|
)
|
||||||
|
products = products.filter(category=ticket_category)
|
||||||
|
|
||||||
profile_section = GuidedRegistrationSection(
|
if products.count() == 0:
|
||||||
title="Profile and Personal Information",
|
# If no ticket, they can only see the profile or ticket page.
|
||||||
form=profile_form,
|
max_page = PAGE_TICKET
|
||||||
|
redirect_page = PAGE_TICKET
|
||||||
|
else:
|
||||||
|
# If there's a ticket, they should *see* the general products page#
|
||||||
|
# but be able to go to the overflow page if needs be.
|
||||||
|
max_page = PAGE_PRODUCTS_MAX
|
||||||
|
redirect_page = PAGE_PRODUCTS
|
||||||
|
|
||||||
|
if page_number is None or int(page_number) > max_page:
|
||||||
|
return redirect("guided_registration", redirect_page)
|
||||||
|
|
||||||
|
page_number = int(page_number)
|
||||||
|
|
||||||
|
next_step = redirect("guided_registration", page_number + 1)
|
||||||
|
|
||||||
|
with BatchController.batch(request.user):
|
||||||
|
|
||||||
|
# This view doesn't work if the conference has sold out.
|
||||||
|
available = ProductController.available_products(
|
||||||
|
request.user, category=ticket_category
|
||||||
)
|
)
|
||||||
|
if not available:
|
||||||
|
messages.error(request, "There are no more tickets available.")
|
||||||
|
return redirect("dashboard")
|
||||||
|
|
||||||
|
sections = []
|
||||||
|
|
||||||
|
# Build up the list of sections
|
||||||
|
if page_number == PAGE_PROFILE:
|
||||||
|
# Profile bit
|
||||||
title = "Attendee information"
|
title = "Attendee information"
|
||||||
current_step = 1
|
sections = _guided_registration_profile_and_voucher(request)
|
||||||
sections.append(voucher_section)
|
elif page_number == PAGE_TICKET:
|
||||||
sections.append(profile_section)
|
# Select ticket
|
||||||
else:
|
title = "Select ticket type"
|
||||||
# We're selling products
|
sections = _guided_registration_products(
|
||||||
|
request, GUIDED_MODE_TICKETS_ONLY
|
||||||
|
)
|
||||||
|
elif page_number == PAGE_PRODUCTS:
|
||||||
|
# Select additional items
|
||||||
|
title = "Additional items"
|
||||||
|
sections = _guided_registration_products(
|
||||||
|
request, GUIDED_MODE_ALL_ADDITIONAL
|
||||||
|
)
|
||||||
|
elif page_number == PAGE_PRODUCTS_MAX:
|
||||||
|
# Items enabled by things on page 3 -- only shows things
|
||||||
|
# that have not been marked as complete.
|
||||||
|
title = "More additional items"
|
||||||
|
sections = _guided_registration_products(
|
||||||
|
request, GUIDED_MODE_EXCLUDE_COMPLETE
|
||||||
|
)
|
||||||
|
|
||||||
starting = attendee.guided_categories_complete.count() == 0
|
if not sections:
|
||||||
|
# We've filled in every category
|
||||||
|
attendee.completed_registration = True
|
||||||
|
attendee.save()
|
||||||
|
return redirect("review")
|
||||||
|
|
||||||
|
if sections and request.method == "POST":
|
||||||
|
for section in sections:
|
||||||
|
if section.form.errors:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# We've successfully processed everything
|
||||||
|
return next_step
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"current_step": page_number,
|
||||||
|
"sections": sections,
|
||||||
|
"title": title,
|
||||||
|
"total_steps": TOTAL_PAGES,
|
||||||
|
}
|
||||||
|
return render(request, "registrasion/guided_registration.html", data)
|
||||||
|
|
||||||
|
|
||||||
|
GUIDED_MODE_TICKETS_ONLY = 2
|
||||||
|
GUIDED_MODE_ALL_ADDITIONAL = 3
|
||||||
|
GUIDED_MODE_EXCLUDE_COMPLETE = 4
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def _guided_registration_products(request, mode):
|
||||||
|
sections = []
|
||||||
|
|
||||||
|
SESSION_KEY = "guided_registration"
|
||||||
|
MODE_KEY = "mode"
|
||||||
|
CATS_KEY = "cats"
|
||||||
|
|
||||||
|
attendee = people.Attendee.get_instance(request.user)
|
||||||
|
|
||||||
# Get the next category
|
# Get the next category
|
||||||
cats = inventory.Category.objects
|
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:
|
if SESSION_KEY in request.session:
|
||||||
_cats = request.session[SESSION_KEY]
|
session_struct = request.session[SESSION_KEY]
|
||||||
cats = cats.filter(id__in=_cats)
|
old_mode = session_struct[MODE_KEY]
|
||||||
|
old_cats = session_struct[CATS_KEY]
|
||||||
else:
|
else:
|
||||||
cats = cats.exclude(
|
old_mode = None
|
||||||
id__in=attendee.guided_categories_complete.all(),
|
old_cats = []
|
||||||
)
|
|
||||||
|
|
||||||
cats = cats.order_by("order")
|
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)
|
||||||
|
|
||||||
request.session[SESSION_KEY] = []
|
# We update the session key at the end of this method
|
||||||
|
# once we've found all the categories that have available products
|
||||||
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(
|
all_products = inventory.Product.objects.filter(
|
||||||
category__in=cats,
|
category__in=cats,
|
||||||
).select_related("category")
|
).select_related("category")
|
||||||
|
|
||||||
|
seen_categories = []
|
||||||
|
|
||||||
with BatchController.batch(request.user):
|
with BatchController.batch(request.user):
|
||||||
available_products = set(ProductController.available_products(
|
available_products = set(ProductController.available_products(
|
||||||
request.user,
|
request.user,
|
||||||
|
@ -173,10 +261,9 @@ def guided_registration(request):
|
||||||
))
|
))
|
||||||
|
|
||||||
if len(available_products) == 0:
|
if len(available_products) == 0:
|
||||||
# We've filled in every category
|
return []
|
||||||
attendee.completed_registration = True
|
|
||||||
attendee.save()
|
has_errors = False
|
||||||
return next_step
|
|
||||||
|
|
||||||
for category in cats:
|
for category in cats:
|
||||||
products = [
|
products = [
|
||||||
|
@ -198,33 +285,31 @@ def guided_registration(request):
|
||||||
if products:
|
if products:
|
||||||
# This product category has items to show.
|
# This product category has items to show.
|
||||||
sections.append(section)
|
sections.append(section)
|
||||||
# Add this to the list of things to show if the form
|
seen_categories.append(category)
|
||||||
# errors.
|
|
||||||
request.session[SESSION_KEY].append(category.id)
|
|
||||||
|
|
||||||
if request.method == "POST" and not products_form.errors:
|
# Update the cache with the newly calculated values
|
||||||
# This is only saved if we pass each form with no
|
cat_ids = [cat.id for cat in seen_categories]
|
||||||
# errors, and if the form actually has products.
|
request.session[SESSION_KEY] = {MODE_KEY: mode, CATS_KEY: cat_ids}
|
||||||
attendee.guided_categories_complete.add(category)
|
|
||||||
|
|
||||||
if sections and request.method == "POST":
|
return sections
|
||||||
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,
|
@login_required
|
||||||
"sections": sections,
|
def _guided_registration_profile_and_voucher(request):
|
||||||
"title": title,
|
voucher_form, voucher_handled = _handle_voucher(request, "voucher")
|
||||||
"total_steps": 3,
|
profile_form, profile_handled = _handle_profile(request, "profile")
|
||||||
}
|
|
||||||
return render(request, "registrasion/guided_registration.html", data)
|
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
|
@login_required
|
||||||
|
@ -399,6 +484,28 @@ def product_category(request, category_id):
|
||||||
return render(request, "registrasion/product_category.html", data)
|
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):
|
def _handle_products(request, category, products, prefix):
|
||||||
''' Handles a products list form in the given request. Returns the
|
''' Handles a products list form in the given request. Returns the
|
||||||
form instance, the discounts applicable to this form, and whether the
|
form instance, the discounts applicable to this form, and whether the
|
||||||
|
|
Loading…
Reference in a new issue