Changeset - 3b5b958b78b6
[Not reviewed]
0 2 0
Christopher Neugebauer - 8 years ago 2016-04-29 01:09:08
chrisjrn@gmail.com
Makes the discounts section from _handle_products evaluate lazily, just in case it’s never displayed in a template (those are some very very expensive queries there).
2 files changed with 35 insertions and 1 deletions:
0 comments (0 inline, 0 general)
registrasion/util.py
Show inline comments
 
import string
 

	
 
from django.utils.crypto import get_random_string
 

	
 

	
 
def generate_access_code():
 
    ''' Generates an access code for users' payments as well as their
 
    fulfilment code for check-in.
 
    The access code will 4 characters long, which allows for 1,500,625
 
    unique codes, which really should be enough for anyone. '''
 

	
 
    length = 4
 
    # all upper-case letters + digits 1-9 (no 0 vs O confusion)
 
    chars = string.uppercase + string.digits[1:]
 
    # 4 chars => 35 ** 4 = 1500625 (should be enough for anyone)
 
    return get_random_string(length=length, allowed_chars=chars)
 

	
 

	
 
def all_arguments_optional(ntcls):
 
    ''' Takes a namedtuple derivative and makes all of the arguments optional.
 
    '''
 

	
 
    ntcls.__new__.__defaults__ = (
 
        (None,) * len(ntcls._fields)
 
    )
 

	
 
    return ntcls
 

	
 

	
 
def lazy(function, *args, **kwargs):
 
    ''' Produces a callable so that functions can be lazily evaluated in
 
    templates.
 

	
 
    Arguments:
 

	
 
        function (callable): The function to call at evaluation time.
 

	
 
        args: Positional arguments, passed directly to ``function``.
 

	
 
        kwargs: Keyword arguments, passed directly to ``function``.
 

	
 
    Return:
 

	
 
        callable: A callable that will evaluate a call to ``function`` with
 
            the specified arguments.
 

	
 
    '''
 

	
 
    NOT_EVALUATED = object()
 
    retval = [NOT_EVALUATED]
 

	
 
    def evaluate():
 
        if retval[0] is NOT_EVALUATED:
 
            retval[0] = function(*args, **kwargs)
 
        return retval[0]
 

	
 
    return evaluate
registrasion/views.py
Show inline comments
...
 
@@ -336,193 +336,197 @@ def product_category(request, category_id):
 

	
 
    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 = inventory.Category.objects.get(pk=category_id)
 

	
 
    products = ProductController.available_products(
 
        request.user,
 
        category=category,
 
    )
 

	
 
    if not products:
 
        messages.warning(
 
            request,
 
            "There are no products available from category: " + category.name,
 
        )
 
        return redirect("dashboard")
 

	
 
    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
 
        messages.success(
 
            request,
 
            "Your reservations have been updated.",
 
        )
 
        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 = commerce.ProductItem.objects.filter(
 
        product__in=products,
 
        cart=current_cart.cart,
 
    ).select_related("product")
 
    quantities = []
 
    seen = set()
 

	
 
    for item in items:
 
        quantities.append((item.product, item.quantity))
 
        seen.add(item.product)
 

	
 
    zeros = set(products) - seen
 
    for product in zeros:
 
        quantities.append((product, 0))
 

	
 
    products_form = ProductsForm(
 
        request.POST or None,
 
        product_quantities=quantities,
 
        prefix=prefix,
 
    )
 

	
 
    if request.method == "POST" and products_form.is_valid():
 
        if products_form.has_changed():
 
            _set_quantities_from_products_form(products_form, current_cart)
 

	
 
        # If category is required, the user must have at least one
 
        # in an active+valid cart
 
        if category.required:
 
            carts = commerce.Cart.objects.filter(user=request.user)
 
            items = commerce.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 = DiscountController.available_discounts(
 
    # Making this a function to lazily evaluate when it's displayed
 
    # in templates.
 

	
 
    discounts = util.lazy(
 
        DiscountController.available_discounts,
 
        request.user,
 
        [],
 
        products,
 
    )
 

	
 
    return products_form, discounts, handled
 

	
 

	
 
def _set_quantities_from_products_form(products_form, current_cart):
 

	
 
    quantities = list(products_form.product_quantities())
 

	
 
    pks = [i[0] for i in quantities]
 
    products = inventory.Product.objects.filter(
 
        id__in=pks,
 
    ).select_related("category")
 

	
 
    product_quantities = [
 
        (products.get(pk=i[0]), i[1]) for i in quantities
 
    ]
 
    field_names = dict(
 
        (i[0][0], i[1][2]) for i in zip(product_quantities, quantities)
 
    )
 

	
 
    try:
 
        current_cart.set_quantities(product_quantities)
 
    except CartValidationError as ve:
 
        for ve_field in ve.error_list:
 
            product, message = ve_field.message
 
            if product in field_names:
 
                field = field_names[product]
 
            elif isinstance(product, inventory.Product):
 
                continue
 
            else:
 
                field = None
 
            products_form.add_error(field, message)
 

	
 

	
 
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 = inventory.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 the checkout process for the current cart.
 

	
 
    If the query string contains ``fix_errors=true``, Registrasion will attempt
 
    to fix errors preventing the system from checking out, including by
 
    cancelling expired discounts and vouchers, and removing any unavailable
 
    products.
 

	
 
    Returns:
 
        render or redirect:
 
            If the invoice is generated successfully, or there's already a
 
            valid invoice for the current cart, redirect to ``invoice``.
 
            If there are errors when generating the invoice, render
 
            ``registrasion/checkout_errors.html`` with the following data::
 

	
 
                {
 
                    "error_list", [str, ...]  # The errors to display.
 
                }
 

	
 
    '''
 

	
 
    current_cart = CartController.for_user(request.user)
 

	
 
    if "fix_errors" in request.GET and request.GET["fix_errors"] == "true":
 
        current_cart.fix_simple_errors()
 

	
 
    try:
 
        current_invoice = InvoiceController.for_cart(current_cart.cart)
0 comments (0 inline, 0 general)