Created
February 23, 2010 01:00
-
-
Save stephenmcd/311723 to your computer and use it in GitHub Desktop.
Formset Forms for Django
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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