Login

Stacked/Grouped Forms

Author:
flashingpumpkin
Posted:
November 5, 2009
Language:
Python
Version:
1.1
Score:
2 (after 2 ratings)

This snippet provides support to create a bit more advanced forms, eg group fields together vertically and horizontally. It's a bit similar to the options one has about displaying fields in the admin.

  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
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
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 action="." method="POST">
            {{ form.as_stack }}
            <input type="submit" value="Submit" />
        </form>
        
        # ---------------------------------------------------- stacked_form.html
        
        <ul>
        {% for stack in form.get_stacks %}
            {% if stack.label %}
                <li>
                    <h1{% if stack.css_class %} class="{{ stack.css_class }}"{% endif %}>{{ stack.label }}</h1>
                </li>
            {% endif %}
            <li>
                <ul>
                    {% for field in stack %}
                        <li>
                            {% if field.is_group %}
                                <ul>
                                    {% for f in field %}
                                        <li>{{ f }}</li>
                                    {% endfor %}
                                </ul>
                            {% else %}
                                {{ field }}
                            {% endif %}
                        </li>
                    {% endfor %}
                </ul>
            </li>
        {% endfor %}
        </ul>
        
        # ----------------------------------------------------- stack_field.html
        
        <p{% if field.errors %} class="error"{% endif %}>
            <label for="{{ field.auto_id }}">{{ field.label.title }}</label>
            {{ field }}
            <span class="tooltip-help">{{ field.help_text }}</span>
        </p>
        
        # --------------------------------------------------------------- Output
        <ul>
            <li>
                <h1>User Information</h1>
            </li>
            <li>
                <ul>
                    <li>
                        <p>
                            <label for="id_username">Username</label>
                            <input type="text" name="username" id="id_username" />
                            <span class="tooltip-help"></span>
                        </p>
                    </li>
                    <li>
                        <ul>
                            <li>
                                <p>
                                    <label for="id_first_name">First Name</label>
                                    <input type="text" name="first_name" id="id_first_name" />
                                    <span class="tooltip-help"></span>
                                </p>
                            </li>                                                    
                            <li>
                                <p>
                                    <label for="id_last_name">Last Name</label>
                                    <input type="text" name="last_name" id="id_last_name" />
                                    <span class="tooltip-help"></span>
                                </p>
                            </li>
                        </ul>
                    </li>
                </ul>
            </li>
            <li>
                <h1 class="smaller-h1">Security Information</h1>
            </li>
            <li>
                <ul>
                    <li>
                        <ul>
                            <li>
                                <p>
                                    <label for="id_email1">Email1</label>
                                    <input type="text" name="email1" id="id_email1" />
                                    <span class="tooltip-help"></span>
                                </p>
                            </li>
                            <li>
                                <p>
                                    <label for="id_email2">Email2</label>
                                    <input type="text" name="email2" id="id_email2" />
                                    <span class="tooltip-help"></span>
                                </p>
                            </li>                            
                        </ul>
                    </li>
                    <li>
                        <ul>
                            <li>
                                <p>
                                    <label for="id_pw1">Pw1</label>
                                    <input type="text" name="pw1" id="id_pw1" />
                                    <span class="tooltip-help"></span>
                                </p>
                            </li>
                            <li>
                                <p>
                                    <label for="id_pw2">Pw2</label>
                                    <input type="text" name="pw2" id="id_pw2" />
                                    <span class="tooltip-help"></span>
                                </p>    
                            </li>
                        </ul>
                    </li>            
                </ul>
            </li>
            <li>
                <h1>Elsewhere</h1>
            </li>
            <li>
                <ul>
                    <li>
                        <p>
                            <label for="id_website">Website</label>
                            <input type="text" name="website" id="id_website" />
                            <span class="tooltip-help"></span>
                        </p>
                    </li>
                    <li>                                            
                        <p>
                            <label for="id_twitter">Twitter</label>
                            <input type="text" name="twitter" id="id_twitter" />
                            <span class="tooltip-help"></span>
                        </p>                    
                    </li>            
                    <li>                    
                        <p>
                            <label for="id_facebook">Facebook</label>
                            <input type="text" name="facebook" id="id_facebook" />
                            <span class="tooltip-help"></span>
                        </p>            
                    </li>            
                </ul>
            </li>
        </ul>        
    """
    
    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)
        )
        
        

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

djfm (on November 9, 2009):

Nice, thanks for this.

Any reason get_stacks() shouldn't always return something iterable, so we could always skip the 'if field.is_group' check, and then always use the 'for f in field' for all cases?

#

Please login first before commenting.