Login

FieldsetForm

Author:
Ciantic
Posted:
April 9, 2007
Language:
Python
Version:
.96
Score:
1 (after 1 ratings)

This is FieldsetForm for rendering forms with fieldsets. This is not in anyway final version of this snippet. This is preliminary version which just works. One can easilly implement the as_ul, as_p at the end with just looking the as_table definition.

Content developers should group information where natural and appropriate. When form controls can be grouped into logical units, use the FIELDSET element and label those units with the LEGEND element... - Web Content Accessibility Guidelines 1.0

Notice: Since this uses real fieldset elements of HTML we had to define form.as_table the way it includes the table element around it.

Usage in template

{{ form }}

and not <table>{{ form }}</table>

Usage in view

Following usage example is for django.contrib.flatpage, but naturally you can use it to whatever you want. Example assumes user knows about newforms and basics to how to use form_for_* functions

from THIS-SNIPPETS-POSITION-IN-PYTHON-PATH import form_fieldsets
...

    fieldsets = form_fieldsets(
            (None,               {'fields': ('url','title','content',)} ),
            ("Advanced options", {'fields': ('sites', 'template_name', 'registration_required',), 'classes':'collapse'})
        )
...
... forms.models.form_for_instance(flatpage, **fieldsets)
... forms.models.form_for_model(FlatPage, **fieldsets)
...

Above creates two fieldsets:

  1. "None" named fieldset (meaning no name is given) with fields 'url', 'title' and 'content'.
  2. "Advanced options" named fieldset with fields 'sites', 'template_name' and 'registration_required' and collapse class in place.

Syntax of form_fieldsets function is identical with models class Admin: fields = (...), actually you can use admins exact line here with adding it like form_fieldsets(*FlatPage.Admin.fields) if you prefer that, although it wrecks the point of having newforms admin, if one does identical forms as in admin part. Purpose of this is not to create identical forms as in admin but to provide easy fieldsets for all views and forms.

To follow DRY principle this should be part of Django and Django's newforms-admin branch should start to use some subclassing of this. But even if the newforms-admin branch never take this in, it is more DRY than without to use this in own projects, with this in place you can forget all template hazzles defining fieldset manually.

Some "counter" statemets for having this

S: "But I want to define the table tag myself in template!" A: Well, you understod something wrong; First of all, for example, if there is something missing from the output of this table tag, you can feel free and subclass from FieldsetForm and define own as_table.

Second of all, for having own table tag there can be only one reason to that, you want extra arguments, and that is TODO, but it is also easy piece. I haven't just needed extra stuff yet so they are not implemented.

S: "But, oh my! (newforms) admin does this already!" A: For the last time this is not meant for only newforms admin, you can use this on any given view or form renderition, since currently django does not give a simple way to use that handy fieldset automation in own views. And currently (9th of April, 2007) newforms admin does not do it this way.

S: "But, I want to use as_p, as_ul ..." A: Go ahead and fix them below... Should be pretty easy stuff.

S: "Each model should have only one fieldsets setting." A: I don't believe that; even if I did that is not convincing, since there are views that are not based on models, just bunch of fields, how would you define their fieldsets if it is not defined in the form renderition at the first place? This is the right place to define fieldsets. And the point of FieldsetForm is not just having multiple fieldsets per model, it is about where and how to render them.

 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
90
91
92
93
94
95
96
97
98
from django.newforms.forms import BaseForm, BoundField, ErrorList
from django.utils.html import escape
    
def include_formfield_callback(*alloweds):
    def newfunc(f, **kwargs):
        return [None, f.formfield(**kwargs)][f.name in alloweds]	
    newfunc.alloweds = alloweds
    return newfunc

def form_fieldsets(*fieldsets):
    allowed_fields = []
    for fieldset in fieldsets:
        allowed_fields += fieldset[1]['fields']
        
    # TODO: Study whether we need to add separate name for each type we produce dynamically
    return {
        'form': type("DynamicFieldsetForm", (FieldsetForm,), {"fieldsets":fieldsets}), 
        'formfield_callback' : include_formfield_callback(*allowed_fields) }

class FieldsetForm(BaseForm):
    def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row, normal_fieldset, fieldset_nameholder):
        top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
        output = []
        
        for fieldset in self.fieldsets:
            fieldset_name = fieldset[0]
            fieldset_content = fieldset[1]
            fieldset_field_names = []
            fieldset_classes = ""
            normal_fieldset_rows = []
            # TODO: Test hidden fields
            hidden_fields = []
            
            # TODO: Implement error check (fields supposed to be list, and classes supposed to be string)
            if fieldset_content.has_key('fields'):
                fieldset_field_names = fieldset_content['fields']
            if fieldset_content.has_key('classes'):
                fieldset_classes = fieldset_content['classes']
            
            normal_fieldset_nameholder = ""
            if fieldset_name:
                normal_fieldset_nameholder = fieldset_nameholder % {'fieldset_name':fieldset_name}
            
            # Contents of this for loop is almost identical with BaseForm loop (except that I use normal_fieldset_rows at the end)
            for field_name in fieldset_field_names:
                name = field_name
                field = self.fields[field_name]
                
                bf = BoundField(self, field, name)
                bf_errors = ErrorList([escape(error) for error in bf.errors]) # Escape and cache in local variable.
                if bf.is_hidden:
                    if bf_errors:
                        top_errors.extend(['(Hidden field %s) %s' % (name, e) for e in bf_errors])
                    hidden_fields.append(unicode(bf))
                else:
                    if errors_on_separate_row and bf_errors:
                        output.append(error_row % bf_errors)
                    label = bf.label and bf.label_tag(escape(bf.label + ':')) or ''
                    if field.help_text:
                        help_text = help_text_html % field.help_text
                    else:
                        help_text = u''
                    normal_fieldset_rows.append(normal_row % {'errors': bf_errors, 'label': label, 'field': unicode(bf), 'help_text': help_text})
                        
            output.append(normal_fieldset % {
                'fieldset_nameholder':      normal_fieldset_nameholder, 
                'fieldset_rows' :           "\n".join(normal_fieldset_rows), 
                'fieldset_hidden_fields':   "\n".join(hidden_fields), 
                'fieldset_classes' :        fieldset_classes})

        if top_errors:
            output.insert(0, error_row % top_errors)

        return u'\n'.join(output)

    def as_table(self):
        "Returns this form rendered as HTML <table> -- including the <table></table>."
        # TODO: Is class attribute allowed to be empty?
        # TODO: Add ability to add extra attributes to table element and fieldset element
        # TODO: Think about moving all this to templates (since it sounds more sane)
        #   It is not done that way yet because this is just first scetch (and BaseForm did this way)
        
        return self._html_output(u'<tr><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>', u'<tr><td colspan="2">%s</td></tr>', '</td></tr>', u'<br />%s', False, u'<fieldset class="%(fieldset_classes)s">%(fieldset_nameholder)s<table class="form">%(fieldset_rows)s</table>%(fieldset_hidden_fields)s</fieldset>', u"<legend>%(fieldset_name)s</legend>")

    # I'm too excited to define the following, so we shall just pass at this point:
    def as_ul(self):
        pass
        '''
        "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
        return self._html_output(u'<li>%(errors)s%(label)s %(field)s%(help_text)s</li>', u'<li>%s</li>', '</li>', u' %s', False)
        '''

    def as_p(self):
        pass
        '''
        "Returns this form rendered as HTML <p>s."
        return self._html_output(u'<p>%(label)s %(field)s%(help_text)s</p>', u'<p>%s</p>', '</p>', u' %s', True)
        '''

More like this

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

Comments

gderazon (on November 14, 2008):

To make this work with django 1.0 wrap line 74 with mark_safe: mark_safe(u'\n'.join(output))

In addition it was not clear to me how to use it in my form, so here is an example:

class MyForm(FieldsetForm):

def __init__(self, *args, **kwargs):

    super(FieldsetForm, self).__init__(*args, **kwargs)

    self.fieldsets = ((None,{'fields': ('name1','email1')} ),
            ("Advanced options", {'fields': ('name1','email1'), 'classes':'collapse'})
        )

name1 = forms.CharField(label="Name (1)")
email1 = forms.EmailField(label="E-mail address (1)")

name2 = forms.CharField(label="Name (2)")
email2 = forms.EmailField(label="E-mail address (2)")

#

gderazon (on November 18, 2008):

More changes in FieldsetForm to make it work

class FieldsetForm(BaseForm): metaclass = DeclarativeFieldsMetaclass

def __init__(self, *args, **kwargs):
    super(FieldsetForm, self).__init__(*args, **kwargs)
    self.fieldsets = None

#

Ciantic (on February 16, 2009):

Heh, I can't believe someone used this. I rediscovered this using Google.

I discarded this because it should be inbuilt to Django, and I don't mind upgrading these helpers too much.

#

akaihola (on October 6, 2011):

@Ciantic, umm, I wouldn't call my fork of WTForm maintained...

#

Please login first before commenting.