Ticket #17301: ticket17301_v2.diff
File ticket17301_v2.diff, 37.4 KB (added by , 13 years ago) |
---|
-
django/forms/forms.py
diff --git a/django/forms/forms.py b/django/forms/forms.py index 94eb22d..1ea35f6 100644
a b def get_declared_fields(bases, attrs, with_base_fields=True): 54 54 55 55 return SortedDict(fields) 56 56 57 class DeclarativeFieldsMetaclass(type):58 """59 Metaclass that converts Field attributes to a dictionary called60 'base_fields', taking into account parent class 'base_fields' as well. 61 """ 57 class BaseFormOptions(object): 58 def __init__(self, options=None): 59 self.fieldsets = getattr(options, 'fieldsets', None) 60 61 class BaseFormMetaclass(type): 62 62 def __new__(cls, name, bases, attrs): 63 attrs['base_fields'] = get_declared_fields(bases, attrs) 64 new_class = super(DeclarativeFieldsMetaclass, 65 cls).__new__(cls, name, bases, attrs) 63 parents = [b for b in bases if isinstance(b, cls)] 64 new_class = super(BaseFormMetaclass, cls).__new__(cls, name, bases, attrs) 65 if not parents: 66 return new_class 66 67 if 'media' not in attrs: 67 68 new_class.media = media_property(new_class) 69 new_class._meta = cls.make_options(getattr(new_class, 'Meta', None)) 68 70 return new_class 69 71 72 @classmethod 73 def make_options(cls, meta): 74 return BaseFormOptions(meta) 75 76 77 class DeclarativeFieldsMetaclass(BaseFormMetaclass): 78 def __new__(cls, name, bases, attrs): 79 attrs['base_fields'] = get_declared_fields(bases, attrs) 80 return super(DeclarativeFieldsMetaclass, cls).__new__(cls, name, bases, attrs) 81 82 70 83 class BaseForm(StrAndUnicode): 71 84 # This is the main implementation of all the Form logic. Note that this 72 85 # class is different than Form. See the comments by the Form class for more … … class BaseForm(StrAndUnicode): 109 122 raise KeyError('Key %r not found in Form' % name) 110 123 return BoundField(self, field, name) 111 124 112 def _get_errors(self): 125 @property 126 def errors(self): 113 127 "Returns an ErrorDict for the data provided for the form" 114 128 if self._errors is None: 115 129 self.full_clean() 116 130 return self._errors 117 errors = property(_get_errors)118 131 119 132 def is_valid(self): 120 133 """ … … class BaseForm(StrAndUnicode): 138 151 """ 139 152 return u'initial-%s' % self.add_prefix(field_name) 140 153 141 def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row):154 def _html_output(self, fieldset_method, error_row, before_fieldset=u'', after_fieldset=u''): 142 155 "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()." 143 156 top_errors = self.non_field_errors() # Errors that should be displayed above all fields. 144 output, hidden_fields = [], [] 145 146 for name, field in self.fields.items(): 147 html_class_attr = '' 148 bf = self[name] 149 bf_errors = self.error_class([conditional_escape(error) for error in bf.errors]) # Escape and cache in local variable. 150 if bf.is_hidden: 151 if bf_errors: 152 top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors]) 153 hidden_fields.append(unicode(bf)) 154 else: 155 # Create a 'class="..."' atribute if the row should have any 156 # CSS classes applied. 157 css_classes = bf.css_classes() 158 if css_classes: 159 html_class_attr = ' class="%s"' % css_classes 160 161 if errors_on_separate_row and bf_errors: 162 output.append(error_row % force_unicode(bf_errors)) 163 164 if bf.label: 165 label = conditional_escape(force_unicode(bf.label)) 166 # Only add the suffix if the label does not end in 167 # punctuation. 168 if self.label_suffix: 169 if label[-1] not in ':?.!': 170 label += self.label_suffix 171 label = bf.label_tag(label) or '' 172 else: 173 label = '' 174 175 if field.help_text: 176 help_text = help_text_html % force_unicode(field.help_text) 177 else: 178 help_text = u'' 179 180 output.append(normal_row % { 181 'errors': force_unicode(bf_errors), 182 'label': force_unicode(label), 183 'field': unicode(bf), 184 'help_text': help_text, 185 'html_class_attr': html_class_attr 186 }) 157 output = [] 158 159 for fieldset in self.fieldsets: 160 fieldset_html = [getattr(fieldset, fieldset_method)()] 161 if not fieldset.dummy: 162 fieldset_html.insert(0, u'<fieldset>') 163 fieldset_html.insert(1, before_fieldset) 164 fieldset_html.append(after_fieldset) 165 fieldset_html.append(u'</fieldset>') 166 if fieldset.legend: 167 fieldset_html.insert(1, fieldset.legend_tag()) 168 if top_errors: 169 output.insert(0, force_unicode(top_errors)) 170 output.extend(fieldset_html) 187 171 188 172 if top_errors: 189 173 output.insert(0, error_row % force_unicode(top_errors)) 190 174 191 if hidden_fields: # Insert any hidden fields in the last row.192 str_hidden = u''.join(hidden_fields)193 if output:194 last_row = output[-1]195 # Chop off the trailing row_ender (e.g. '</td></tr>') and196 # insert the hidden fields.197 if not last_row.endswith(row_ender):198 # This can happen in the as_p() case (and possibly others199 # that users write): if there are only top errors, we may200 # not be able to conscript the last row for our purposes,201 # so insert a new, empty row.202 last_row = (normal_row % {'errors': '', 'label': '',203 'field': '', 'help_text':'',204 'html_class_attr': html_class_attr})205 output.append(last_row)206 output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender207 else:208 # If there aren't any rows in the output, just append the209 # hidden fields.210 output.append(str_hidden)211 175 return mark_safe(u'\n'.join(output)) 212 176 213 177 def as_table(self): 214 178 "Returns this form rendered as HTML <tr>s -- excluding the <table></table>." 215 179 return self._html_output( 216 normal_row = u'<tr%(html_class_attr)s><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>',180 fieldset_method='as_table', 217 181 error_row = u'<tr><td colspan="2">%s</td></tr>', 218 row_ender = u'</td></tr>', 219 help_text_html = u'<br /><span class="helptext">%s</span>', 220 errors_on_separate_row = False) 182 before_fieldset=u'<table>', 183 after_fieldset=u'</table>') 221 184 222 185 def as_ul(self): 223 186 "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>." 224 187 return self._html_output( 225 normal_row = u'<li%(html_class_attr)s>%(errors)s%(label)s %(field)s%(help_text)s</li>',188 fieldset_method='as_ul', 226 189 error_row = u'<li>%s</li>', 227 row_ender = '</li>', 228 help_text_html = u' <span class="helptext">%s</span>', 229 errors_on_separate_row = False) 190 before_fieldset=u'<ul>', 191 after_fieldset=u'</ul>') 230 192 231 193 def as_p(self): 232 194 "Returns this form rendered as HTML <p>s." 233 195 return self._html_output( 234 normal_row = u'<p%(html_class_attr)s>%(label)s %(field)s%(help_text)s</p>', 235 error_row = u'%s', 236 row_ender = '</p>', 237 help_text_html = u' <span class="helptext">%s</span>', 238 errors_on_separate_row = True) 196 fieldset_method='as_p', 197 error_row = u'%s') 239 198 240 199 def non_field_errors(self): 241 200 """ … … class BaseForm(StrAndUnicode): 322 281 """ 323 282 return bool(self.changed_data) 324 283 325 def _get_changed_data(self): 284 @property 285 def changed_data(self): 326 286 if self._changed_data is None: 327 287 self._changed_data = [] 328 288 # XXX: For now we're asking the individual widgets whether or not the … … class BaseForm(StrAndUnicode): 344 304 if field.widget._has_changed(initial_value, data_value): 345 305 self._changed_data.append(name) 346 306 return self._changed_data 347 changed_data = property(_get_changed_data)348 307 349 def _get_media(self): 308 @property 309 def media(self): 350 310 """ 351 311 Provide a description of all media required to render the widgets on this form 352 312 """ … … class BaseForm(StrAndUnicode): 354 314 for field in self.fields.values(): 355 315 media = media + field.widget.media 356 316 return media 357 media = property(_get_media)358 317 359 318 def is_multipart(self): 360 319 """ … … class BaseForm(StrAndUnicode): 380 339 """ 381 340 return [field for field in self if not field.is_hidden] 382 341 342 @property 343 def fieldsets(self): 344 """ 345 Returns a list of Fieldset objects for each fieldset 346 defined in Form's Meta options. If no fieldsets were defined, 347 returns a list containing single, 'dummy' Fieldset with 348 all form fields. 349 """ 350 if self._meta.fieldsets: 351 return [Fieldset(self, legend, attrs.get('fields', tuple())) 352 for legend, attrs in self._meta.fieldsets] 353 return [Fieldset(self, None, self.fields.keys(), dummy=True)] 354 383 355 class Form(BaseForm): 384 356 "A collection of Fields, plus their associated data." 385 357 # This is a separate class from BaseForm in order to abstract the way … … class Form(BaseForm): 389 361 # BaseForm itself has no way of designating self.fields. 390 362 __metaclass__ = DeclarativeFieldsMetaclass 391 363 364 class Fieldset(StrAndUnicode): 365 366 def __init__(self, form, legend, fields, dummy=False): 367 """ 368 Arguments: 369 form -- form this fieldset belongs to 370 legend -- fieldset's legend (used in <legend> tag) 371 fields -- list containing names of fields in this fieldset 372 373 Keyword arguments: 374 dummy -- flag informing that the fieldset was created automatically 375 from all fields of form, because user has not defined 376 custom fieldsets 377 """ 378 self.form = form 379 self.legend = legend 380 self.fields = fields 381 self.dummy = dummy 382 383 def __unicode__(self): 384 return self.as_table() 385 386 def __iter__(self): 387 for name in self.fields: 388 yield BoundField(self.form, self.form.fields[name], name) 389 390 def __getitem__(self, name): 391 "Returns a BoundField with the given name." 392 if name not in self.fields: 393 raise KeyError('Key %r not found in Fieldset' % name) 394 return self.form[name] 395 396 def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row): 397 "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()." 398 output, hidden_fields = [], [] 399 top_errors = self.form.error_class() 400 401 for name in self.fields: 402 field = self.form.fields[name] 403 html_class_attr = '' 404 bf = self.form[name] 405 bf_errors = self.form.error_class([conditional_escape(error) for error in bf.errors]) # Escape and cache in local variable. 406 if bf.is_hidden: 407 if bf_errors: 408 top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors]) 409 hidden_fields.append(unicode(bf)) 410 else: 411 # Create a 'class="..."' atribute if the row should have any 412 # CSS classes applied. 413 css_classes = bf.css_classes() 414 if css_classes: 415 html_class_attr = ' class="%s"' % css_classes 416 417 if errors_on_separate_row and bf_errors: 418 output.append(error_row % force_unicode(bf_errors)) 419 420 if bf.label: 421 label = conditional_escape(force_unicode(bf.label)) 422 # Only add the suffix if the label does not end in 423 # punctuation. 424 if self.form.label_suffix: 425 if label[-1] not in ':?.!': 426 label += self.form.label_suffix 427 label = bf.label_tag(label) or '' 428 else: 429 label = '' 430 431 if field.help_text: 432 help_text = help_text_html % force_unicode(field.help_text) 433 else: 434 help_text = u'' 435 436 output.append(normal_row % { 437 'errors': force_unicode(bf_errors), 438 'label': force_unicode(label), 439 'field': unicode(bf), 440 'help_text': help_text, 441 'html_class_attr': html_class_attr 442 }) 443 444 if top_errors: 445 output.insert(0, error_row % force_unicode(top_errors)) 446 447 if hidden_fields: # Insert any hidden fields in the last row. 448 str_hidden = u''.join(hidden_fields) 449 if output: 450 last_row = output[-1] 451 # Chop off the trailing row_ender (e.g. '</td></tr>') and 452 # insert the hidden fields. 453 if not last_row.endswith(row_ender): 454 # This can happen in the as_p() case (and possibly others 455 # that users write): if there are only top errors, we may 456 # not be able to conscript the last row for our purposes, 457 # so insert a new, empty row. 458 last_row = (normal_row % {'errors': '', 'label': '', 459 'field': '', 'help_text':'', 460 'html_class_attr': html_class_attr}) 461 output.append(last_row) 462 output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender 463 else: 464 # If there aren't any rows in the output, just append the 465 # hidden fields. 466 output.append(str_hidden) 467 468 return mark_safe(u'\n'.join(output)) 469 470 def as_table(self): 471 "Returns this fieldset rendered as HTML <tr>s -- excluding the <table>, <fieldset> and <legend> tags." 472 return self._html_output( 473 normal_row = u'<tr%(html_class_attr)s><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>', 474 error_row = u'<tr><td colspan="2">%s</td></tr>', 475 row_ender = u'</td></tr>', 476 help_text_html = u'<br /><span class="helptext">%s</span>', 477 errors_on_separate_row = False) 478 479 def as_ul(self): 480 "Returns this fieldset rendered as HTML <li>s -- excluding the <ul>, <fieldset> and <legend> tags." 481 return self._html_output( 482 normal_row = u'<li%(html_class_attr)s>%(errors)s%(label)s %(field)s%(help_text)s</li>', 483 error_row = u'<li>%s</li>', 484 row_ender = '</li>', 485 help_text_html = u' <span class="helptext">%s</span>', 486 errors_on_separate_row = False) 487 488 def as_p(self): 489 "Returns this fieldset rendered as HTML <p>s -- excluding the <fieldset> and <legend> tags." 490 return self._html_output( 491 normal_row = u'<p%(html_class_attr)s>%(label)s %(field)s%(help_text)s</p>', 492 error_row = u'%s', 493 row_ender = '</p>', 494 help_text_html = u' <span class="helptext">%s</span>', 495 errors_on_separate_row = True) 496 497 def legend_tag(self, contents=None, attrs=None): 498 """ 499 Wraps the given contents in a <legend>. Does not HTML-escape the contents. 500 If contents aren't given, uses the fieldset's HTML-escaped legend. 501 502 If attrs are given, they're used as HTML attributes on the <legend> tag. 503 """ 504 if contents is None and self.legend is not None: 505 contents = conditional_escape(self.legend) 506 attrs = attrs and flatatt(attrs) or '' 507 if contents is not None: 508 return mark_safe(u'<legend%s>%s</legend>' % (attrs, force_unicode(self.legend))) 509 return None 510 511 def hidden_fields(self): 512 """ 513 Returns a list of all the BoundField objects that are hidden fields. 514 Useful for manual form layout in templates. 515 """ 516 return [field for field in self if field.is_hidden] 517 518 def visible_fields(self): 519 """ 520 Returns a list of BoundField objects that aren't hidden fields. 521 The opposite of the hidden_fields() method. 522 """ 523 return [field for field in self if not field.is_hidden] 524 392 525 class BoundField(StrAndUnicode): 393 526 "A Field plus data" 394 527 def __init__(self, form, field, name): … … class BoundField(StrAndUnicode): 426 559 def __getitem__(self, idx): 427 560 return list(self.__iter__())[idx] 428 561 429 def _errors(self): 562 @property 563 def errors(self): 430 564 """ 431 565 Returns an ErrorList for this field. Returns an empty ErrorList 432 566 if there are none. 433 567 """ 434 568 return self.form.errors.get(self.name, self.form.error_class()) 435 errors = property(_errors)436 569 437 570 def as_widget(self, widget=None, attrs=None, only_initial=False): 438 571 """ … … class BoundField(StrAndUnicode): 473 606 """ 474 607 return self.as_widget(self.field.hidden_widget(), attrs, **kwargs) 475 608 476 def _data(self): 609 @property 610 def data(self): 477 611 """ 478 612 Returns the data for this BoundField, or None if it wasn't given. 479 613 """ 480 614 return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name) 481 data = property(_data)482 615 483 616 def value(self): 484 617 """ … … class BoundField(StrAndUnicode): 524 657 extra_classes.add(self.form.required_css_class) 525 658 return ' '.join(extra_classes) 526 659 527 def _is_hidden(self): 660 @property 661 def is_hidden(self): 528 662 "Returns True if this BoundField's widget is hidden." 529 663 return self.field.widget.is_hidden 530 is_hidden = property(_is_hidden)531 664 532 def _auto_id(self): 665 @property 666 def auto_id(self): 533 667 """ 534 668 Calculates and returns the ID attribute for this BoundField, if the 535 669 associated Form has specified auto_id. Returns an empty string otherwise. … … class BoundField(StrAndUnicode): 540 674 elif auto_id: 541 675 return self.html_name 542 676 return '' 543 auto_id = property(_auto_id)544 677 545 def _id_for_label(self): 678 @property 679 def id_for_label(self): 546 680 """ 547 681 Wrapper around the field widget's `id_for_label` method. 548 682 Useful, for example, for focusing on this field regardless of whether … … class BoundField(StrAndUnicode): 551 685 widget = self.field.widget 552 686 id_ = widget.attrs.get('id') or self.auto_id 553 687 return widget.id_for_label(id_) 554 id_for_label = property(_id_for_label) -
django/forms/models.py
diff --git a/django/forms/models.py b/django/forms/models.py index cd8f027..ccec2e5 100644
a b from __future__ import absolute_import 8 8 from django.core.exceptions import ValidationError, NON_FIELD_ERRORS, FieldError 9 9 from django.core.validators import EMPTY_VALUES 10 10 from django.forms.fields import Field, ChoiceField 11 from django.forms.forms import BaseForm, get_declared_fields 11 from django.forms.forms import (BaseForm, BaseFormOptions, BaseFormMetaclass, 12 get_declared_fields) 12 13 from django.forms.formsets import BaseFormSet, formset_factory 13 14 from django.forms.util import ErrorList 14 15 from django.forms.widgets import (SelectMultiple, HiddenInput, 15 MultipleHiddenInput , media_property)16 MultipleHiddenInput) 16 17 from django.utils.encoding import smart_unicode, force_unicode 17 18 from django.utils.datastructures import SortedDict 18 19 from django.utils.text import get_text_list, capfirst … … def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_c 175 176 ) 176 177 return field_dict 177 178 178 class ModelFormOptions( object):179 class ModelFormOptions(BaseFormOptions): 179 180 def __init__(self, options=None): 181 super(ModelFormOptions, self).__init__(options) 180 182 self.model = getattr(options, 'model', None) 181 183 self.fields = getattr(options, 'fields', None) 182 184 self.exclude = getattr(options, 'exclude', None) 183 185 self.widgets = getattr(options, 'widgets', None) 184 186 187 class ModelFormMetaclass(BaseFormMetaclass): 185 188 186 class ModelFormMetaclass(type):187 189 def __new__(cls, name, bases, attrs): 188 190 formfield_callback = attrs.pop('formfield_callback', None) 189 try: 190 parents = [b for b in bases if issubclass(b, ModelForm)] 191 except NameError: 192 # We are defining ModelForm itself. 193 parents = None 194 declared_fields = get_declared_fields(bases, attrs, False) 195 new_class = super(ModelFormMetaclass, cls).__new__(cls, name, bases, 196 attrs) 197 if not parents: 191 192 new_class = super(ModelFormMetaclass, cls).__new__(cls, name, bases, attrs) 193 opts = getattr(new_class, "_meta", None) 194 if not opts: # no parents 198 195 return new_class 196 197 declared_fields = get_declared_fields(bases, attrs, False) 199 198 200 if 'media' not in attrs:201 new_class.media = media_property(new_class)202 opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None))203 199 if opts.model: 204 200 # If a model is defined, extract form fields from it. 205 201 fields = fields_for_model(opts.model, opts.fields, … … class ModelFormMetaclass(type): 222 218 new_class.base_fields = fields 223 219 return new_class 224 220 221 @classmethod 222 def make_options(cls, meta): 223 return ModelFormOptions(meta) 224 225 225 226 class BaseModelForm(BaseForm): 226 227 def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, 227 228 initial=None, error_class=ErrorList, label_suffix=':', … … class BaseModelForm(BaseForm): 305 306 306 307 def _post_clean(self): 307 308 opts = self._meta 308 # Update the model instance with self.cleaned_data. 309 # Update the model instance with self.cleaned_data.ModelForm 309 310 self.instance = construct_instance(self, self.instance, opts.fields, opts.exclude) 310 311 311 312 exclude = self._get_validation_exclusions() -
django/forms/widgets.py
diff --git a/django/forms/widgets.py b/django/forms/widgets.py index 1fbef98..f95a16d 100644
a b class Media(StrAndUnicode): 70 70 return path 71 71 if prefix is None: 72 72 if settings.STATIC_URL is None: 73 73 # backwards compatibility 74 74 prefix = settings.MEDIA_URL 75 75 else: 76 76 prefix = settings.STATIC_URL -
docs/topics/forms/index.txt
diff --git a/docs/topics/forms/index.txt b/docs/topics/forms/index.txt index 18e55f5..bd47732 100644
a b tag:: 388 388 If you find yourself doing this often, you might consider creating a custom 389 389 :ref:`inclusion tag<howto-custom-template-tags-inclusion-tags>`. 390 390 391 Fieldsets 392 --------- 393 394 .. versionadded:: development 395 396 Having a complex form you may want to organize its fields in logical groups. 397 In Django you can do that using fieldsets. Fieldsets allow you to iterate 398 through their fields and are autmatically rendered by form using ``<fieldset>`` 399 HTML tag and its subtag ``<legend>``. 400 401 402 Fieldsets are defined using ``Meta`` options class. If you're familiar 403 with :attr:`~django.contrib.admin.ModelAdmin.fieldsets` from Django admin 404 options, you alreadyknow the syntax: 405 406 .. code-block:: python 407 408 class PersonForm(forms.Form): 409 home_phone = CharField() 410 cell_phone = CharField() 411 first_name = CharField() 412 last_name = CharField() 413 414 class Meta: 415 fieldsets = ( 416 (None, { 417 'fields': ('first_name', 'last_name',), 418 }), 419 ("Phone numbers", { 420 'fields': ('cell_phone', 'home_phone',), 421 }), 422 ) 423 424 Having above example form you may render it in a template just like a normal form:: 425 426 <form action="" method="post"> 427 {{ form.as_table }} 428 <input type="submit" value="Send" /> 429 </form> 430 431 Except now instead of one ``<table>`` element, ``as_table`` method will 432 print two tables wrapped up in ``<fieldset>`` tags:: 433 434 <form action="" method="post"> 435 <fieldset> 436 <table> 437 <tr> 438 <th><label for="id_first_name">First name:</label></th> 439 <td><input type="text" name="first_name" id="id_first_name" /></td> 440 </tr> 441 <tr> 442 <th><label for="id_last_name">Last name:</label></th> 443 <td><input type="text" name="last_name" id="id_last_name" /></td> 444 </tr> 445 </table> 446 </fieldset> 447 <fieldset> 448 <legend>Phone numbers</legend> 449 <table> 450 <tr> 451 <th><label for="id_cell_phone">Cell phone:</label></th> 452 <td><input type="text" name="cell_phone" id="id_cell_phone" /></td> 453 </tr> 454 <tr> 455 <th><label for="id_home_phone">Home phone:</label></th> 456 <td><input type="text" name="home_phone" id="id_home_phone" /></td> 457 </tr> 458 </table> 459 </fieldset> 460 <input type="submit" value="Send" /> 461 </form> 462 463 You can also customize your output looping through form's fieldsets and using 464 their methods -- ``as_table``, ``as_ul`` and ``as_p`` -- which behave just like 465 their equivalents from ``Form`` class and using a ``legend_tag`` method:: 466 467 <form action="" method="post"> 468 {% for fieldset in form.fieldsets %} 469 <fieldset> 470 {{ fieldset.legend_tag }} 471 <table> 472 {{ fieldset.as_table }} 473 </table> 474 </fieldset> 475 {% endfor %} 476 <input type="submit" value="Send" /> 477 </form> 478 479 You can be even more specific and loop through all fields of all fieldsets:: 480 481 <form action="" method="post"> 482 {% for fieldset in form.fieldsets %} 483 <fieldset> 484 {{ fieldset.legend_tag }} 485 <ul> 486 {% for field in fieldset %} 487 <li> 488 {{ field.label_tag }} 489 {{ field }} 490 </li> 491 {% endfor %} 492 </ul> 493 </fieldset> 494 {% endfor %} 495 <input type="submit" value="Send" /> 496 </form> 497 498 You can also loop though fieldset ``hidden_fields`` and ``visible_fields`` just 499 line in a form class. 500 391 501 Further topics 392 502 ============== 393 503 -
tests/regressiontests/forms/tests/__init__.py
diff --git a/tests/regressiontests/forms/tests/__init__.py b/tests/regressiontests/forms/tests/__init__.py index 8e2150c..8c3d0fb 100644
a b from .util import FormsUtilTestCase 19 19 from .validators import TestFieldWithValidators 20 20 from .widgets import (FormsWidgetTestCase, FormsI18NWidgetsTestCase, 21 21 WidgetTests, ClearableFileInputTests) 22 from .fieldsets import FieldsetsTestCase -
new file tests/regressiontests/forms/tests/fieldsets.py
diff --git a/tests/regressiontests/forms/tests/fieldsets.py b/tests/regressiontests/forms/tests/fieldsets.py new file mode 100644 index 0000000..79ccabb
- + 1 # -*- coding: utf-8 -*- 2 import datetime 3 4 from django.core.files.uploadedfile import SimpleUploadedFile 5 from django.forms import * 6 from django import forms 7 from django.http import QueryDict 8 from django.template import Template, Context 9 from django.utils.datastructures import MultiValueDict, MergeDict 10 from django.utils.safestring import mark_safe 11 from django.utils.unittest import TestCase 12 13 14 class PersonWithoutFormfields(Form): 15 first_name = CharField() 16 last_name = CharField() 17 birthday = DateField() 18 band = CharField() 19 secret = CharField(widget=HiddenInput) 20 21 class Person(PersonWithoutFormfields): 22 class Meta: 23 fieldsets = ( 24 (None, { 25 'fields': ('first_name', 'last_name', 'birthday'), 26 }), 27 ("Additional fields", { 28 'fields': ('band', 'secret'), 29 }), 30 ) 31 32 class FieldsetsTestCase(TestCase): 33 34 some_data = { 35 'first_name': u'John', 36 'last_name': u'Lennon', 37 'birthday': u'1940-10-9', 38 'band': u'The Beatles', 39 'secret': u'he didnt say', 40 } 41 42 def test_simple_rendering(self): 43 # Pass a dictionary to a Form's __init__(). 44 p = Person(self.some_data) 45 # as_table 46 self.assertEqual(str(p), """<fieldset> 47 <table> 48 <tr><th><label for="id_first_name">First name:</label></th><td><input type="text" name="first_name" value="John" id="id_first_name" /></td></tr> 49 <tr><th><label for="id_last_name">Last name:</label></th><td><input type="text" name="last_name" value="Lennon" id="id_last_name" /></td></tr> 50 <tr><th><label for="id_birthday">Birthday:</label></th><td><input type="text" name="birthday" value="1940-10-9" id="id_birthday" /></td></tr> 51 </table> 52 </fieldset> 53 <fieldset> 54 <legend>Additional fields</legend> 55 <table> 56 <tr><th><label for="id_band">Band:</label></th><td><input type="text" name="band" value="The Beatles" id="id_band" /><input type="hidden" name="secret" value="he didnt say" id="id_secret" /></td></tr> 57 </table> 58 </fieldset>""") 59 self.assertEqual(str(p), unicode(p)) 60 self.assertEqual(str(p), p.as_table()) 61 # as_ul 62 self.assertEqual(p.as_ul(), """<fieldset> 63 <ul> 64 <li><label for="id_first_name">First name:</label> <input type="text" name="first_name" value="John" id="id_first_name" /></li> 65 <li><label for="id_last_name">Last name:</label> <input type="text" name="last_name" value="Lennon" id="id_last_name" /></li> 66 <li><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" value="1940-10-9" id="id_birthday" /></li> 67 </ul> 68 </fieldset> 69 <fieldset> 70 <legend>Additional fields</legend> 71 <ul> 72 <li><label for="id_band">Band:</label> <input type="text" name="band" value="The Beatles" id="id_band" /><input type="hidden" name="secret" value="he didnt say" id="id_secret" /></li> 73 </ul> 74 </fieldset>""") 75 # as_p 76 self.assertEqual(p.as_p(), """<fieldset> 77 78 <p><label for="id_first_name">First name:</label> <input type="text" name="first_name" value="John" id="id_first_name" /></p> 79 <p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" value="Lennon" id="id_last_name" /></p> 80 <p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" value="1940-10-9" id="id_birthday" /></p> 81 82 </fieldset> 83 <fieldset> 84 <legend>Additional fields</legend> 85 86 <p><label for="id_band">Band:</label> <input type="text" name="band" value="The Beatles" id="id_band" /><input type="hidden" name="secret" value="he didnt say" id="id_secret" /></p> 87 88 </fieldset>""") # Additional blank lines are ok 89 90 def test_single_fieldset_rendering(self): 91 # Pass a dictionary to a Form's __init__(). 92 p = Person(self.some_data) 93 # as_table 94 self.assertEqual(str(p.fieldsets[0]), """<tr><th><label for="id_first_name">First name:</label></th><td><input type="text" name="first_name" value="John" id="id_first_name" /></td></tr> 95 <tr><th><label for="id_last_name">Last name:</label></th><td><input type="text" name="last_name" value="Lennon" id="id_last_name" /></td></tr> 96 <tr><th><label for="id_birthday">Birthday:</label></th><td><input type="text" name="birthday" value="1940-10-9" id="id_birthday" /></td></tr>""") 97 self.assertEqual(str(p.fieldsets[0]), unicode(p.fieldsets[0])) 98 self.assertEqual(str(p.fieldsets[0]), p.fieldsets[0].as_table()) 99 self.assertEqual(str(p.fieldsets[1]), """<tr><th><label for="id_band">Band:</label></th><td><input type="text" name="band" value="The Beatles" id="id_band" /><input type="hidden" name="secret" value="he didnt say" id="id_secret" /></td></tr>""") 100 self.assertEqual(str(p.fieldsets[1]), p.fieldsets[1].as_table()) 101 # as_ul 102 self.assertEqual(p.fieldsets[0].as_ul(), """<li><label for="id_first_name">First name:</label> <input type="text" name="first_name" value="John" id="id_first_name" /></li> 103 <li><label for="id_last_name">Last name:</label> <input type="text" name="last_name" value="Lennon" id="id_last_name" /></li> 104 <li><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" value="1940-10-9" id="id_birthday" /></li>""") 105 self.assertEqual(p.fieldsets[1].as_ul(), """<li><label for="id_band">Band:</label> <input type="text" name="band" value="The Beatles" id="id_band" /><input type="hidden" name="secret" value="he didnt say" id="id_secret" /></li>""") 106 # as_p 107 self.assertEqual(p.fieldsets[0].as_p(), """<p><label for="id_first_name">First name:</label> <input type="text" name="first_name" value="John" id="id_first_name" /></p> 108 <p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" value="Lennon" id="id_last_name" /></p> 109 <p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" value="1940-10-9" id="id_birthday" /></p>""") 110 self.assertEqual(p.fieldsets[1].as_p(), """<p><label for="id_band">Band:</label> <input type="text" name="band" value="The Beatles" id="id_band" /><input type="hidden" name="secret" value="he didnt say" id="id_secret" /></p>""") 111 112 def test_fieldset_fields_iteration(self): 113 # Pass a dictionary to a Form's __init__(). 114 p = Person(self.some_data) 115 for fieldset in p.fieldsets: 116 for field in fieldset: 117 pass 118 self.assertEqual(set([field.name for field in p.fieldsets[0].visible_fields()]), set(['first_name', 'last_name', 'birthday'])) 119 self.assertEqual(len(p.fieldsets[0].hidden_fields()), 0) 120 self.assertEqual(set([field.name for field in p.fieldsets[1].visible_fields()]), set(['band'])) 121 self.assertEqual(set([field.name for field in p.fieldsets[1].hidden_fields()]), set(['secret'])) 122 123 def test_legend_tag(self): 124 # Pass a dictionary to a Form's __init__(). 125 p = Person(self.some_data) 126 self.assertIsNone(p.fieldsets[0].legend_tag()) 127 self.assertEqual(p.fieldsets[1].legend_tag(), """<legend>Additional fields</legend>""") 128 -
tests/regressiontests/forms/tests/forms.py
diff --git a/tests/regressiontests/forms/tests/forms.py b/tests/regressiontests/forms/tests/forms.py index 3f529f2..4b0faa6 100644
a b class FormsTestCase(TestCase): 1807 1807 form = NameForm(data={'name' : ['fname', 'lname']}) 1808 1808 self.assertTrue(form.is_valid()) 1809 1809 self.assertEqual(form.cleaned_data, {'name' : 'fname lname'}) 1810 1811 def test_meta_options(self): 1812 class MetaOptionsForm(Form): 1813 class Meta: 1814 fieldsets = 0xDEADBEEF 1815 some_nonexising_option = True 1816 class MetaOptionsDerivantForm(MetaOptionsForm): 1817 pass 1818 # Test classes 1819 self.assertEqual(MetaOptionsForm._meta.fieldsets, 0xDEADBEEF) 1820 self.assertEqual(MetaOptionsDerivantForm._meta.fieldsets, 0xDEADBEEF) 1821 self.assertFalse(hasattr(MetaOptionsForm._meta, 'some_nonexising_option')) 1822 self.assertFalse(hasattr(MetaOptionsDerivantForm._meta, 'some_nonexising_option')) 1823 # Test instances 1824 meta_options_form = MetaOptionsForm() 1825 meta_options_derivant_form = MetaOptionsDerivantForm() 1826 self.assertEqual(meta_options_form._meta.fieldsets, 0xDEADBEEF) 1827 self.assertEqual(meta_options_derivant_form._meta.fieldsets, 0xDEADBEEF) 1828 self.assertFalse(hasattr(meta_options_form._meta, 'some_nonexising_option')) 1829 self.assertFalse(hasattr(meta_options_derivant_form._meta, 'some_nonexising_option')) 1830 1831 def test_meta_options_override(self): 1832 class MetaOptionsForm(Form): 1833 class Meta: 1834 fieldsets = 0xDEADBEEF 1835 class MetaOptionsDerivantForm(MetaOptionsForm): 1836 class Meta: 1837 fieldsets = 0xCAFEBABE 1838 # Test classes 1839 self.assertEqual(MetaOptionsForm._meta.fieldsets, 0xDEADBEEF) 1840 self.assertEqual(MetaOptionsDerivantForm._meta.fieldsets, 0xCAFEBABE) 1841 # Test instances 1842 meta_options_form = MetaOptionsForm() 1843 meta_options_derivant_form = MetaOptionsDerivantForm() 1844 self.assertEqual(meta_options_form._meta.fieldsets, 0xDEADBEEF) 1845 self.assertEqual(meta_options_derivant_form._meta.fieldsets, 0xCAFEBABE) 1846 1847 def test_meta_option_defaults(self): 1848 class MetaOptionsForm(Form): 1849 pass 1850 # Test classes 1851 self.assertIsNone(MetaOptionsForm._meta.fieldsets) 1852 # Test instance 1853 meta_options_form = MetaOptionsForm() 1854 self.assertIsNone(meta_options_form._meta.fieldsets)