Changeset - db332da9584d
[Not reviewed]
0 3 0
Christopher Neugebauer - 8 years ago 2016-03-27 02:12:33
chrisjrn@gmail.com
flake8
3 files changed with 9 insertions and 2 deletions:
0 comments (0 inline, 0 general)
registrasion/forms.py
Show inline comments
 
import models as rego
 

	
 
from django import forms
 

	
 

	
 
# Products forms -- none of these have any fields: they are to be subclassed
 
# and the fields added as needs be.
 

	
 
class _ProductsForm(forms.Form):
 

	
 
    PRODUCT_PREFIX = "product_"
 

	
 
    ''' Base class for product entry forms. '''
 
    def __init__(self, *a, **k):
 
        if "product_quantities" in k:
 
            initial = self.initial_data(k["product_quantities"])
 
            k["initial"] = initial
 
            del k["product_quantities"]
 
        super(_ProductsForm, self).__init__(*a, **k)
 

	
 
    @classmethod
 
    def field_name(cls, product):
 
        return cls.PRODUCT_PREFIX + ("%d" % product.id)
 

	
 
    @classmethod
 
    def set_fields(cls, category, products):
 
        ''' Sets the base_fields on this _ProductsForm to allow selecting
 
        from the provided products. '''
 
        pass
 

	
 
    @classmethod
 
    def initial_data(cls, product_quantites):
 
        ''' Prepares initial data for an instance of this form.
 
        product_quantities is a sequence of (product,quantity) tuples '''
 
        return {}
 

	
 
    def product_quantities(self):
 
        ''' Yields a sequence of (product, quantity) tuples from the
 
        cleaned form data. '''
 
        return iter([])
 

	
 

	
 
class _QuantityBoxProductsForm(_ProductsForm):
 
    ''' Products entry form that allows users to enter quantities
 
    of desired products. '''
 

	
 
    @classmethod
 
    def set_fields(cls, category, products):
 
        for product in products:
 
            help_text = "$%d -- %s" % (product.price, product.description)
 

	
 
            field = forms.IntegerField(
 
                label=product.name,
 
                help_text=help_text,
 
            )
 
            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)] = 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, value, name)
 

	
 

	
 
class _RadioButtonProductsForm(_ProductsForm):
 
    ''' Products entry form that allows users to enter quantities
 
    of desired products. '''
 

	
 
    FIELD = "chosen_product"
 

	
 
    @classmethod
 
    def set_fields(cls, category, products):
 
        choices = []
 
        for product in products:
 

	
 
            choice_text = "%s -- $%d" % (product.name, product.price)
 
            choices.append((product.id, choice_text))
 

	
 
        cls.base_fields[cls.FIELD] = forms.TypedChoiceField(
 
            label=category.name,
 
            widget=forms.RadioSelect,
 
            choices=choices,
 
            empty_value=0,
 
            coerce=int,
 
        )
 

	
 
    @classmethod
 
    def initial_data(cls, product_quantities):
 
        initial = {}
 

	
 
        for product, quantity in product_quantities:
 
            if quantity > 0:
 
                initial[cls.FIELD] = product.id
 
                break
 

	
 
        return initial
 

	
 
    def product_quantities(self):
 
        ours = self.cleaned_data[self.FIELD]
 
        choices = self.fields[self.FIELD].choices
 
        for choice_value, choice_display in choices:
 
            yield (
 
                choice_value,
 
                1 if ours == choice_value else 0,
 
                self.FIELD,
 
            )
 

	
 

	
 
def ProductsForm(category, products):
 
    ''' Produces an appropriate _ProductsForm subclass for the given render
 
    type. '''
 

	
 
    # Each Category.RENDER_TYPE value has a subclass here.
 
    RENDER_TYPES = {
 
        rego.Category.RENDER_TYPE_QUANTITY : _QuantityBoxProductsForm,
 
        rego.Category.RENDER_TYPE_RADIO : _RadioButtonProductsForm,
 
        rego.Category.RENDER_TYPE_QUANTITY: _QuantityBoxProductsForm,
 
        rego.Category.RENDER_TYPE_RADIO: _RadioButtonProductsForm,
 
    }
 

	
 
    # Produce a subclass of _ProductsForm which we can alter the base_fields on
 
    class ProductsForm(RENDER_TYPES[category.render_type]):
 
        pass
 

	
 
    ProductsForm.set_fields(category, products)
 
    return ProductsForm
 

	
 

	
 
class ProfileForm(forms.ModelForm):
 
    ''' A form for requesting badge and profile information. '''
 

	
 
    class Meta:
 
        model = rego.BadgeAndProfile
 
        exclude = ['attendee']
 

	
 

	
 
class VoucherForm(forms.Form):
 
    voucher = forms.CharField(
 
        label="Voucher code",
 
        help_text="If you have a voucher code, enter it here",
 
        required=False,
 
    )
registrasion/templatetags/registrasion_tags.py
Show inline comments
 
from registrasion import models as rego
 

	
 
from collections import namedtuple
 
from django import template
 
from django.db.models import Sum
 

	
 
register = template.Library()
 

	
 
ProductAndQuantity = namedtuple("ProductAndQuantity", ["product", "quantity"])
 

	
 

	
 
@register.assignment_tag(takes_context=True)
 
def available_categories(context):
 
    ''' Returns all of the available product categories '''
 
    return rego.Category.objects.all()
 

	
 

	
 
@register.assignment_tag(takes_context=True)
 
def invoices(context):
 
    ''' Returns all of the invoices that this user has. '''
 
    return rego.Invoice.objects.filter(cart__user=context.request.user)
 

	
 

	
 
@register.assignment_tag(takes_context=True)
 
def items_pending(context):
 
    ''' Returns all of the items that this user has in their current cart,
 
    and is awaiting payment. '''
 

	
 
    all_items = rego.ProductItem.objects.filter(
 
        cart__user=context.request.user,
 
        cart__active=True,
 
    )
 
    return all_items
 

	
 

	
 
@register.assignment_tag(takes_context=True)
 
def items_purchased(context):
 
    ''' Returns all of the items that this user has purchased '''
 

	
 
    all_items = rego.ProductItem.objects.filter(
 
        cart__user=context.request.user,
 
        cart__active=False,
 
    )
 

	
 
    products = set(item.product for item in all_items)
 
    out = []
 
    for product in products:
 
        pp = all_items.filter(product=product)
 
        quantity = pp.aggregate(Sum("quantity"))["quantity__sum"]
 
        out.append(ProductAndQuantity(product, quantity))
 
    return out
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 django.contrib.auth.decorators import login_required
 
from django.core.exceptions import ObjectDoesNotExist
 
from django.core.exceptions import ValidationError
 
from django.db import transaction
 
from django.shortcuts import redirect
 
from django.shortcuts import render
 

	
 

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

	
 
    attendee = rego.Attendee.get_instance(request.user)
 
    if attendee.completed_registration:
 
        return dashboard
 

	
 
    # Step 1: Fill in a badge
 
    profile = rego.BadgeAndProfile.get_instance(attendee)
 

	
 
    if profile is None:
 
        ret = edit_profile(request)
 
        profile_new = rego.BadgeAndProfile.get_instance(attendee)
 
        if profile_new is None:
 
            # No new profile was created
 
            return ret
 
        else:
 
            return next_step
 

	
 
    # Step 2: Go through each of the categories in order
 
    category = attendee.highest_complete_category
 

	
 
    # Get the next category
 
    cats = rego.Category.objects
 
    cats = cats.filter(id__gt=category).order_by("order")
 

	
 
    if len(cats) == 0:
 
        # We've filled in every category
 
        attendee.completed_registration = True
 
        attendee.save()
 
        return dashboard
 

	
 
    ret = product_category(request, cats[0].id)
 
    attendee_new = rego.Attendee.get_instance(request.user)
 
    if attendee_new.highest_complete_category == category:
 
        # We've not yet completed this category
 
        return ret
 
    else:
 
        return next_step
 

	
 

	
 
@login_required
 
def edit_profile(request):
 
    attendee = rego.Attendee.get_instance(request.user)
 

	
 
    try:
 
        profile = rego.BadgeAndProfile.objects.get(attendee=attendee)
 
    except ObjectDoesNotExist:
 
        profile = None
 

	
 
    form = forms.ProfileForm(request.POST or None, instance=profile)
 

	
 
    if request.POST and form.is_valid():
 
        form.instance.attendee = attendee
 
        form.save()
 

	
 
    data = {
 
        "form": form,
 
    }
 
    return render(request, "profile_form.html", data)
 

	
 

	
 
@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
 

	
 
        attendee = rego.Attendee.get_instance(request.user)
 
        if category_id > attendee.highest_complete_category:
 
            attendee.highest_complete_category = category_id
 
            attendee.save()
 
        return redirect("dashboard")
 

	
 
    data = {
 
        "category": category,
 
        "discounts": discounts,
 
        "form": products_form,
 
        "voucher_form": voucher_form,
 
    }
 

	
 
    return render(request, "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):
 
    for product_id, quantity, field_name in products_form.product_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)
 
    current_invoice = InvoiceController(inv)
 

	
 
    data = {
 
        "invoice": current_invoice.invoice,
 
    }
 

	
 
    return render(request, "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():
 
        current_invoice.pay("Demo invoice payment", inv.value)
 

	
 
    return redirect("invoice", current_invoice.invoice.id)
0 comments (0 inline, 0 general)