Login

Formset Form

Author:
stephen_mcd
Posted:
February 24, 2010
Language:
Python
Version:
1.1
Score:
2 (after 2 ratings)

Template designers often require access to individual form fields or sets of form fields to control complex form layouts. While this is possible via looping through form fields in the template it can lead to ugly logic inside templates as well as losing the ability to use the as_* form rendering methods.

This class when mixed into a form class provides hooks for template designers to access individual fields or sets of fields based on various criteria such as field name prefix or fields appearing before or after certain fields in the form, while still being able to use the as_* form rendering methods against these fields.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
"""
This class when mixed into a form class provides hooks for template designers to 
access individual fields or sets of fields based on various criteria such as field 
name prefix or fields appearing before or after certain fields in the form, while 
still being able to use the as_* form rendering methods against these fields. 

Credits:
--------

Stephen McDonald <[email protected]>

License:
--------

Creative Commons Attribution-Share Alike 3.0 License
http://creativecommons.org/licenses/by-sa/3.0/

When attributing this work, you must maintain the Credits
paragraph above.
"""

from copy import copy
from itertools import dropwhile, takewhile
from re import match
 
from django.utils.datastructures import SortedDict
 

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)

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 11 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 6 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 7 months ago
  5. Help text hyperlinks by sa2812 1 year, 8 months ago

Comments

Please login first before commenting.