Changeset - d2d2a1b0ecdd
[Not reviewed]
0 2 0
Christopher Neugebauer - 8 years ago 2016-04-01 05:54:40
chrisjrn@gmail.com
Work for making invoices contain complete profile information
2 files changed with 11 insertions and 2 deletions:
0 comments (0 inline, 0 general)
registrasion/models.py
Show inline comments
 
from __future__ import unicode_literals
 

	
 
import datetime
 
import itertools
 

	
 
from django.core.exceptions import ValidationError
 
from django.core.exceptions import ObjectDoesNotExist
 
from django.contrib.auth.models import User
 
from django.db import models
 
from django.db.models import F, Q
 
from django.utils import timezone
 
from django.utils.encoding import python_2_unicode_compatible
 
from django.utils.translation import ugettext_lazy as _
 
from model_utils.managers import InheritanceManager
 

	
 

	
 
# User models
 

	
 
@python_2_unicode_compatible
 
class Attendee(models.Model):
 
    ''' Miscellaneous user-related data. '''
 

	
 
    def __str__(self):
 
        return "%s" % self.user
 

	
 
    @staticmethod
 
    def get_instance(user):
 
        ''' Returns the instance of attendee for the given user, or creates
 
        a new one. '''
 
        attendees = Attendee.objects.filter(user=user)
 
        if len(attendees) > 0:
 
            return attendees[0]
 
        else:
 
            attendee = Attendee(user=user)
 
            attendee.save()
 
            return attendee
 

	
 
    user = models.OneToOneField(User, on_delete=models.CASCADE)
 
    # Badge/profile is linked
 
    completed_registration = models.BooleanField(default=False)
 
    highest_complete_category = models.IntegerField(default=0)
 

	
 

	
 
@python_2_unicode_compatible
 
class BadgeAndProfile(models.Model):
 
    ''' Information for an attendee's badge and related preferences '''
 

	
 
    def __str__(self):
 
        return "Badge for: %s of %s" % (self.name, self.company)
 

	
 
    @staticmethod
 
    def get_instance(attendee):
 
        ''' Returns either None, or the instance that belongs
 
        to this attendee. '''
 
        try:
 
            return BadgeAndProfile.objects.get(attendee=attendee)
 
        except ObjectDoesNotExist:
 
            return None
 

	
 
    def save(self):
 
        if not self.name_per_invoice:
 
            self.name_per_invoice = self.name
 
        super(BadgeAndProfile, self).save()
 

	
 
    attendee = models.OneToOneField(Attendee, on_delete=models.CASCADE)
 

	
 
    # 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,
 
    )
 

	
 

	
 
# Inventory Models
 

	
 
@python_2_unicode_compatible
 
class Category(models.Model):
 
    ''' Registration product categories '''
 

	
 
    class Meta:
 
        verbose_name_plural = _("categories")
 

	
 
    def __str__(self):
 
        return self.name
 

	
 
    RENDER_TYPE_RADIO = 1
 
    RENDER_TYPE_QUANTITY = 2
 

	
 
    CATEGORY_RENDER_TYPES = [
 
        (RENDER_TYPE_RADIO, _("Radio button")),
 
        (RENDER_TYPE_QUANTITY, _("Quantity boxes")),
 
    ]
 

	
 
    name = models.CharField(
 
        max_length=65,
 
        verbose_name=_("Name"),
 
    )
 
    description = models.CharField(
 
        max_length=255,
 
        verbose_name=_("Description"),
 
    )
 
    limit_per_user = models.PositiveIntegerField(
 
        null=True,
 
        blank=True,
 
        verbose_name=_("Limit per user"),
 
        help_text=_("The total number of items from this category one "
 
                    "attendee may purchase."),
 
    )
 
    required = models.BooleanField(
 
        blank=True,
 
        help_text=_("If enabled, a user must select an "
 
                    "item from this category."),
 
    )
registrasion/views.py
Show inline comments
 
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)
...
 
@@ -242,119 +243,122 @@ def handle_products(request, category, products, prefix):
 
    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 inv.paid and current_invoice.is_valid():
 
    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)
0 comments (0 inline, 0 general)