from django import forms from django.template import loader, Context from django.core.context_processors import media as media_processor class StackedItem(object): """ An item inside a ``FieldStack`` Can be either an actual form field, or a group of fields """ def __init__(self, form, stackitem): self.form = form self.stackitem = stackitem def is_group(self): if isinstance(self.stackitem, tuple): return True return False def __iter__(self): if not self.is_group(): raise AttributeError('This stacked item is not a group and therefore not iterable') for stackitem in self.stackitem: yield StackedItem(self.form, stackitem) def __unicode__(self): """ Either render the html, or print some information about this group of fields """ if self.is_group(): return u'StackGroup %s' % self.stackitem tpl = self.form.get_field_template() context_dict = dict( form=self.form, field=self.form[self.stackitem], ) context_dict.update(media_processor(None)) return tpl.render( Context( context_dict ) ) return unicode(self.form[self.stackitem]) class FieldStack(object): """ A stack of fields yielded by ``StackedForm`` """ def __init__(self, form, stack): self.form = form self.stack = stack def __iter__(self): for stackitem in self.stack.get('fields', ()): yield StackedItem(self.form, stackitem) def __len__(self): # ... or the for templatetag throws an error return len(self.stack.get('fields', ())) def __getattr__(self, name): return self.stack.get(name, None) class StackedForm(object): """ Mixin to provide support for stacked forms with or without grouped fields. One particular example of such a form (without groups though) is the Basecamp signup page https://signup.37signals.com/basecamp/Plus/signup/new Example: # ---------------------------------------------------------- Django form from django import forms from toolbox.forms import StackedForm class MyForm(forms.Form, StackedForm): username = forms.CharField() pw1 = forms.CharField() pw2 = forms.CharField() email1 = forms.CharField() email2 = forms.CharField() first_name = forms.CharField() last_name = forms.CharField() website = forms.CharField() twitter = forms.CharField() facebook = forms.CharField() class Stack: stack = ( dict( label = 'User Information', fields = ('username',('first_name','last_name')) ), dict( label = 'Security Information', css_class = 'smaller-h1', fields = (('email1','email2'),('pw1','pw2')) ), dict( label = 'Elsewhere', fields = ('website','twitter','facebook') ) ) # ------------------------------------------------------------- Template
{{ form.as_stack }}
# ---------------------------------------------------- stacked_form.html # ----------------------------------------------------- stack_field.html {{ field }} {{ field.help_text }}

# --------------------------------------------------------------- Output """ form_template = 'toolbox/forms/stacked_form.html' field_template = 'toolbox/forms/stack_field.html' def get_template(self): if getattr(self, '_form_tpl', None) is None: self._form_tpl = loader.get_template(self.form_template) return self._form_tpl def get_field_template(self): if getattr(self, '_field_tpl', None) is None: self._field_tpl = loader.get_template(self.field_template) return self._field_tpl def get_stacks(self): for stack in self.Stack.stack: yield FieldStack(self, stack) def __iter__(self): """ If this is the first inherit we can loop directly, else we'd have to use ``get_stacks`` """ return self.get_stacks() def as_stack(self): """ Renders a form like specified by the inner class ``Stack`` """ if not hasattr(self, 'Stack'): raise AttributeError('No inner class ``Stack`` defined') if not hasattr(self.Stack, 'stack'): raise AttributeError('No attribute ``stack`` on the inner class ``Stack`` defined') tpl = self.get_template() context_dict = dict( form=self, ) context_dict.update(media_processor(None)) return tpl.render( Context(context_dict) )