symposion_app/registrasion/views.py
2016-04-01 16:54:40 +11:00

364 lines
11 KiB
Python

from registrasion import forms
from registrasion import models as rego
from registrasion.controllers import discount
from registrasion.controllers.cart import CartController
from registrasion.controllers.invoice import InvoiceController
from registrasion.controllers.product import ProductController
from collections import namedtuple
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.db import transaction
from django.http import Http404
from django.shortcuts import redirect
from django.shortcuts import render
GuidedRegistrationSection = namedtuple(
"GuidedRegistrationSection",
(
"title",
"discounts",
"description",
"form",
)
)
GuidedRegistrationSection.__new__.__defaults__ = (
(None,) * len(GuidedRegistrationSection._fields)
)
@login_required
def guided_registration(request, page_id=0):
''' Goes through the registration process in order,
making sure user sees all valid categories.
WORK IN PROGRESS: the finalised version of this view will allow
grouping of categories into a specific page. Currently, it just goes
through each category one by one
'''
dashboard = redirect("dashboard")
next_step = redirect("guided_registration")
sections = []
attendee = rego.Attendee.get_instance(request.user)
if attendee.completed_registration:
return render(
request,
"registrasion/guided_registration_complete.html",
{},
)
# Step 1: Fill in a badge and collect a voucher code
profile = rego.BadgeAndProfile.get_instance(attendee)
if profile is None:
# TODO: if voucherform is invalid, make sure that profileform does not save
voucher_form, voucher_handled = handle_voucher(request, "voucher")
profile_form, profile_handled = handle_profile(request, "profile")
voucher_section = GuidedRegistrationSection(
title="Voucher Code",
form=voucher_form,
)
profile_section = GuidedRegistrationSection(
title="Profile and Personal Information",
form=profile_form,
)
title = "Attendee information"
current_step = 1
sections.append(voucher_section)
sections.append(profile_section)
else:
# We're selling products
last_category = attendee.highest_complete_category
# Get the next category
cats = rego.Category.objects
cats = cats.filter(id__gt=last_category).order_by("order")
if cats.count() == 0:
# We've filled in every category
attendee.completed_registration = True
attendee.save()
return next_step
if last_category == 0:
# 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"
for category in cats:
products = ProductController.available_products(
request.user,
category=category,
)
prefix = "category_" + str(category.id)
p = handle_products(request, category, products, prefix)
products_form, discounts, products_handled = p
section = GuidedRegistrationSection(
title=category.name,
description=category.description,
discounts=discounts,
form=products_form,
)
sections.append(section)
if request.method == "POST" and not products_form.errors:
if category.id > attendee.highest_complete_category:
# This is only saved if we pass each form with no errors.
attendee.highest_complete_category = category.id
if sections and request.method == "POST":
for section in sections:
if section.form.errors:
break
else:
attendee.save()
# We've successfully processed everything
return next_step
data = {
"current_step": current_step,
"sections": sections,
"title" : title,
"total_steps": 3,
}
return render(request, "registrasion/guided_registration.html", data)
@login_required
def edit_profile(request):
form, handled = handle_profile(request, "profile")
data = {
"form": form,
}
return render(request, "registrasion/profile_form.html", data)
def handle_profile(request, prefix):
''' Returns a profile form instance, and a boolean which is true if the
form was handled. '''
attendee = rego.Attendee.get_instance(request.user)
try:
profile = rego.BadgeAndProfile.objects.get(attendee=attendee)
except ObjectDoesNotExist:
profile = None
# TODO: pull down the speaker's real name from the Speaker profile
form = forms.ProfileForm(
request.POST or None,
instance=profile,
prefix=prefix
)
handled = True if request.POST else False
if request.POST and form.is_valid():
form.instance.attendee = attendee
form.save()
return form, handled
@login_required
def product_category(request, category_id):
''' Registration selections form for a specific category of items.
'''
PRODUCTS_FORM_PREFIX = "products"
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
category_id = int(category_id) # Routing is [0-9]+
category = rego.Category.objects.get(pk=category_id)
products = ProductController.available_products(
request.user,
category=category,
)
p = handle_products(request, category, products, PRODUCTS_FORM_PREFIX)
products_form, discounts, products_handled = p
if request.POST and not voucher_handled and not products_form.errors:
# Only return to the dashboard if we didn't add a voucher code
# and if there's no errors in the products form
return redirect("dashboard")
data = {
"category": category,
"discounts": discounts,
"form": products_form,
"voucher_form": voucher_form,
}
return render(request, "registrasion/product_category.html", data)
def handle_products(request, category, products, prefix):
''' Handles a products list form in the given request. Returns the
form instance, the discounts applicable to this form, and whether the
contents were handled. '''
current_cart = CartController.for_user(request.user)
ProductsForm = forms.ProductsForm(category, products)
# Create initial data for each of products in category
items = rego.ProductItem.objects.filter(
product__in=products,
cart=current_cart.cart,
)
quantities = []
for product in products:
# Only add items that are enabled.
try:
quantity = items.get(product=product).quantity
except ObjectDoesNotExist:
quantity = 0
quantities.append((product, quantity))
products_form = ProductsForm(
request.POST or None,
product_quantities=quantities,
prefix=prefix,
)
if request.method == "POST" and products_form.is_valid():
try:
if products_form.has_changed():
set_quantities_from_products_form(products_form, current_cart)
except ValidationError:
# There were errors, but they've already been added to the form.
pass
# If category is required, the user must have at least one
# in an active+valid cart
if category.required:
carts = rego.Cart.objects.filter(user=request.user)
items = rego.ProductItem.objects.filter(
product__category=category,
cart=carts,
)
if len(items) == 0:
products_form.add_error(
None,
"You must have at least one item from this category",
)
handled = False if products_form.errors else True
discounts = discount.available_discounts(request.user, [], products)
return products_form, discounts, handled
@transaction.atomic
def set_quantities_from_products_form(products_form, current_cart):
# TODO: issue #8 is a problem here.
quantities = list(products_form.product_quantities())
quantities.sort(key=lambda item: item[1])
for product_id, quantity, field_name in quantities:
product = rego.Product.objects.get(pk=product_id)
try:
current_cart.set_quantity(product, quantity, batched=True)
except ValidationError as ve:
products_form.add_error(field_name, ve)
if products_form.errors:
raise ValidationError("Cannot add that stuff")
current_cart.end_batch()
def handle_voucher(request, prefix):
''' Handles a voucher form in the given request. Returns the voucher
form instance, and whether the voucher code was handled. '''
voucher_form = forms.VoucherForm(request.POST or None, prefix=prefix)
current_cart = CartController.for_user(request.user)
if (voucher_form.is_valid() and
voucher_form.cleaned_data["voucher"].strip()):
voucher = voucher_form.cleaned_data["voucher"]
voucher = rego.Voucher.normalise_code(voucher)
if len(current_cart.cart.vouchers.filter(code=voucher)) > 0:
# This voucher has already been applied to this cart.
# Do not apply code
handled = False
else:
try:
current_cart.apply_voucher(voucher)
except Exception as e:
voucher_form.add_error("voucher", e)
handled = True
else:
handled = False
return (voucher_form, handled)
@login_required
def checkout(request):
''' Runs checkout for the current cart of items, ideally generating an
invoice. '''
current_cart = CartController.for_user(request.user)
current_invoice = InvoiceController.for_cart(current_cart.cart)
return redirect("invoice", current_invoice.invoice.id)
@login_required
def invoice(request, invoice_id):
''' Displays an invoice for a given invoice id. '''
invoice_id = int(invoice_id)
inv = rego.Invoice.objects.get(pk=invoice_id)
if request.user != inv.cart.user and not request.user.is_staff:
raise Http404()
current_invoice = InvoiceController(inv)
data = {
"invoice": current_invoice.invoice,
}
return render(request, "registrasion/invoice.html", data)
@login_required
def pay_invoice(request, invoice_id):
''' Marks the invoice with the given invoice id as paid.
WORK IN PROGRESS FUNCTION. Must be replaced with real payment workflow.
'''
invoice_id = int(invoice_id)
inv = rego.Invoice.objects.get(pk=invoice_id)
current_invoice = InvoiceController(inv)
if not current_invoice.invoice.paid and not current_invoice.invoice.void:
current_invoice.pay("Demo invoice payment", inv.value)
return redirect("invoice", current_invoice.invoice.id)