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: if product.description: help_text = "$%d each -- %s" % (product.price, product.description) else: help_text = "$%d each" % product.price field = forms.IntegerField( label=product.name, help_text=help_text, min_value=0, ) 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, } # 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 VoucherForm(forms.Form): voucher = forms.CharField( label="Voucher code", help_text="If you have a voucher code, enter it here", required=False, )