Skip to content

Instantly share code, notes, and snippets.

@stephenmcd
Created February 23, 2010 01:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stephenmcd/311723 to your computer and use it in GitHub Desktop.
Save stephenmcd/311723 to your computer and use it in GitHub Desktop.
Formset Forms for Django
from copy import copy
from itertools import dropwhile, takewhile
from re import match
from django import template
from django.utils.datastructures import SortedDict
from django.utils.translation import ugettext as _
register = template.Library()
class FormsetForm(object):
"""
Form mixin that gives template designers greater control over form field
rendering while still using the as_* methods. The following methods return
a copy of the form with only a subset of fields that can then be rendered
with as_* methods.
{{ form.PREFIX_fields.as_* }} - fields with a name starting with PREFIX
{{ form.FIELD_field.as_* }} - a single field with the name FIELD
{{ form.fields_before_FIELD.as_* }} - fields before the field named FIELD
{{ form.fields_after_FIELD.as_* }} - fields after the field named FIELD
{{ form.other_fields.as_* }} - fields not yet accessed as a formset
CSS classes are also added to the rendered fields. These are the lowercase
classname of the widget, eg "textinput" or "checkboxinput", and if the field
is required the class of "required" is also added.
"""
def _fieldset(self, field_names):
"""
Return a subset of fields by making a copy of the form containing only
the given field names and adding extra CSS classes to the fields.
"""
fieldset = copy(self)
if not hasattr(self, "_fields_done"):
self._fields_done = []
else:
# all fieldsets will contain all non-field errors, so for fieldsets
# other than the first ensure the call to non-field errors does nothing
fieldset.non_field_errors = lambda *args: None
field_names = filter(lambda f: f not in self._fields_done, field_names)
fieldset.fields = SortedDict([(f, self.fields[f]) for f in field_names])
for field in fieldset.fields.values():
field.widget.attrs["class"] = field.widget.__class__.__name__.lower()
if field.required:
field.widget.attrs["class"] += " required"
self._fields_done.extend(field_names)
return fieldset
def __getattr__(self, name):
"""
Dynamic fieldset caller - matches requested attribute name against
pattern for creating the list of field names to use for the fieldset.
"""
filters = (
("^other_fields$", lambda:
self.fields.keys()),
("^(\w*)_fields$", lambda name:
[f for f in self.fields.keys() if f.startswith(name)]),
("^(\w*)_field$", lambda name:
[f for f in self.fields.keys() if f == name]),
("^fields_before_(\w*)$", lambda name:
takewhile(lambda f: f != name, self.fields.keys())),
("^fields_after_(\w*)$", lambda name:
list(dropwhile(lambda f: f != name, self.fields.keys()))[1:]),
)
for filter_exp, filter_func in filters:
filter_args = match(filter_exp, name)
if filter_args is not None:
return self._fieldset(filter_func(*filter_args.groups()))
raise AttributeError(name)
def register_render_tag(renderer):
"""
Decorator that creates a template tag using the given renderer as the
render function for the template tag node - the render function takes two
arguments - the template context and the tag token
"""
def tag(parser, token):
class TagNode(template.Node):
def render(self, context):
return renderer(context, token)
return TagNode()
for copy_attr in ("__dict__", "__doc__", "__name__"):
setattr(tag, copy_attr, getattr(renderer, copy_attr))
return register.tag(tag)
@register_render_tag
def formset_form_for(context, token):
"""
Take a regular form and create a new FormsetForm for the form
Usage: {% formset_form_for form_var as formset_var %}
"""
bits = token.split_contents()
if len(bits) != 4 or bits[2] != "as":
raise template.TemplateSyntaxError("%s: %s" % (bits[0],
_("3 arguments are required in the format: " \
"{% formset_form_for form_var as formset_var %}")))
elif bits[1] not in context:
raise template.TemplateSyntaxError("%s: %s: %s" % (bits[0],
_("Could not find the variable"), bits[1]))
form = context[bits[1]]
formset = type("", (FormsetForm, form.__class__), {})()
formset.__dict__.update(form.__dict__)
context[bits[3]] = formset
return ""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment