Ticket #6630: 02-add-fieldsets.diff
File 02-add-fieldsets.diff, 12.2 KB (added by , 14 years ago) |
---|
-
django/forms/forms.py
diff --git a/django/forms/forms.py b/django/forms/forms.py
a b 59 59 Option ``exclude`` is an optional list of field names. If provided, 60 60 the named fields will be excluded from the returned fields, even if they 61 61 are listed in the ``fields`` argument. 62 63 Option ``fieldsets`` is a list of two-tuples. The first item in each 64 two-tuple is a name for the fieldset, and the second is a dictionary 65 of fieldset options. Valid fieldset options in the dictionary include: 66 67 ``fields`` (required): A tuple of field names to display in the fieldset. 68 69 ``attrs``: A dictionary of HTML attributes to be used with the fieldset. 70 71 ``legend``: A string that should be the content of a ``legend`` tag 72 to open the fieldset. 73 74 ``description``: A string of optional extra text. 75 62 76 """ 63 77 selected_fields = SortedDict() 64 78 for field_name, field in fields.items(): … … 66 80 continue 67 81 if opts.exclude and field_name in opts.exclude: 68 82 continue 83 if opts.fieldsets: 84 for fieldset_name, fieldset_options in opts.fieldsets: 85 if field_name in fieldset_options['fields']: 86 # Stop iteration of fieldsets so else condition is not used. 87 break 88 else: 89 # Field is not used in any fieldset. 90 continue 69 91 selected_fields[field_name] = field 70 92 if opts.fields: 71 93 selected_fields = SortedDict((field_name, selected_fields.get(field_name)) … … 76 98 def __init__(self, options=None): 77 99 self.fields = getattr(options, 'fields', None) 78 100 self.exclude = getattr(options, 'exclude', None) 101 self.fieldsets = getattr(options, 'fieldsets', None) 79 102 80 103 class FormMetaclass(type): 81 104 """ … … 134 157 raise KeyError('Key %r not found in Form' % name) 135 158 return BoundField(self, field, name) 136 159 160 def _has_fieldsets(self): 161 """ 162 Returns True if form has fieldsets. Depends on _meta attribute giving 163 options which have to define fieldsets. If you do not use FormMetaclass 164 and want to use this method, you have to define options otherwise. 165 """ 166 return self._meta.fieldsets is not None 167 has_fieldsets = property(_has_fieldsets) 168 169 def fieldsets(self): 170 """ 171 Returns collection of FieldSets. Depends on _meta attribute giving 172 options which have to define fieldsets. If you do not use FormMetaclass 173 and want to use this method, you have to define options otherwise. 174 """ 175 return FieldSetCollection(self, self._meta.fieldsets) 176 137 177 def _get_errors(self): 138 178 "Returns an ErrorDict for the data provided for the form" 139 179 if self._errors is None: … … 414 454 # BaseForm itself has no way of designating self.fields. 415 455 __metaclass__ = FormMetaclass 416 456 457 class FieldSetCollection(object): 458 "A collection of FieldSets." 459 def __init__(self, form, fieldsets): 460 self.form = form 461 self.fieldsets = fieldsets 462 463 def __iter__(self): 464 if self.form.has_fieldsets: 465 for name, options in self.fieldsets: 466 yield self._construct_fieldset(name, options) 467 else: 468 # Construct dummy fieldset for form without any. 469 yield FieldSet(self.form, None, self.form.fields) 470 471 def __getitem__(self, key): 472 if self.form.has_fieldsets: 473 for name, options in self.fieldsets: 474 if name == key: 475 return self._construct_fieldset(name, options) 476 raise KeyError('FieldSet with key %r not found' % key) 477 478 def _construct_fieldset(self, name, options): 479 fields = SortedDict((field_name, self.form.fields[field_name]) 480 for field_name in options['fields'] if field_name in self.form.fields) 481 return FieldSet(self.form, name, fields, options.get('attrs'), 482 options.get('legend'), options.get('description')) 483 484 class FieldSet(object): 485 "Iterable FieldSet as a collection of BoundFields." 486 def __init__(self, form, name, fields, attrs=None, legend=None, description=None): 487 self.form = form 488 self.name = name 489 self.fields = fields 490 self.attrs = attrs 491 self.legend = legend 492 self.description = description 493 494 def __iter__(self): 495 for name, field in self.fields.items(): 496 yield BoundField(self.form, field, name) 497 498 def __getitem__(self, name): 499 try: 500 field = self.fields[name] 501 except KeyError: 502 raise KeyError('Key %r not found in FieldSet' % name) 503 return BoundField(self.form, field, name) 504 505 def _errors(self): 506 return ErrorDict((k, v) for (k, v) in self.form.errors.items() if k in self.fields) 507 errors = property(_errors) 508 417 509 class BoundField(StrAndUnicode): 418 510 "A Field plus data" 419 511 def __init__(self, form, field, name): -
django/forms/models.py
diff --git a/django/forms/models.py b/django/forms/models.py
a b 193 193 self.model = getattr(options, 'model', None) 194 194 self.fields = getattr(options, 'fields', None) 195 195 self.exclude = getattr(options, 'exclude', None) 196 self.fieldsets = getattr(options, 'fieldsets', None) 196 197 self.widgets = getattr(options, 'widgets', None) 197 198 198 199 class ModelFormMetaclass(type): -
tests/modeltests/model_forms/models.py
diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py
a b 322 322 >>> CategoryForm.base_fields.keys() 323 323 ['name'] 324 324 325 326 Using 'fieldsets' 327 328 >>> class CategoryForm(ModelForm): 329 ... 330 ... class Meta: 331 ... model = Category 332 ... fieldsets = ( 333 ... ('main', {'fields': ('slug',)}), 334 ... ('other', {'fields': ('name',)}), 335 ... ) 336 337 >>> CategoryForm.base_fields.keys() 338 ['name', 'slug'] 339 340 >>> for fieldset in CategoryForm().fieldsets(): 341 ... print fieldset.name, fieldset.fields.keys() 342 main ['slug'] 343 other ['name'] 344 345 346 Using 'fields' *and* 'exclude' *and* 'fieldsets' 347 348 >>> class CategoryForm(ModelForm): 349 ... 350 ... class Meta: 351 ... model = Category 352 ... fields = ['name', 'url'] 353 ... exclude = ['url'] 354 ... fieldsets = ( 355 ... ('main', {'fields': ('slug',)}), 356 ... ('other', {'fields': ('name',)}), 357 ... ) 358 359 >>> CategoryForm.base_fields.keys() 360 ['name'] 361 362 >>> for fieldset in CategoryForm().fieldsets(): 363 ... print fieldset.name, fieldset.fields.keys() 364 main [] 365 other ['name'] 366 367 325 368 Using 'widgets' 326 369 327 370 >>> class CategoryForm(ModelForm): -
tests/regressiontests/forms/forms.py
diff --git a/tests/regressiontests/forms/forms.py b/tests/regressiontests/forms/forms.py
a b 966 966 <tr><th>Field2:</th><td><input type="text" name="field2" /></td></tr> 967 967 <tr><th>Field11:</th><td><input type="text" name="field11" /></td></tr> 968 968 969 Or to specify fieldsets. 970 >>> class FieldsetsTestForm(TestForm): 971 ... class Meta: 972 ... fieldsets = ( 973 ... ('main', {'fields': ('field3', 'field8', 'field2')}), 974 ... ('other', {'fields': ('field5', 'field11', 'field14')}), 975 ... ) 976 >>> p = FieldsetsTestForm(auto_id=False) 977 >>> print p 978 <tr><th>Field2:</th><td><input type="text" name="field2" /></td></tr> 979 <tr><th>Field3:</th><td><input type="text" name="field3" /></td></tr> 980 <tr><th>Field5:</th><td><input type="text" name="field5" /></td></tr> 981 <tr><th>Field8:</th><td><input type="text" name="field8" /></td></tr> 982 <tr><th>Field11:</th><td><input type="text" name="field11" /></td></tr> 983 <tr><th>Field14:</th><td><input type="text" name="field14" /></td></tr> 984 >>> print p.has_fieldsets 985 True 986 >>> for fieldset in p.fieldsets(): 987 ... print fieldset.name 988 ... for field in fieldset: 989 ... print field 990 main 991 <input type="text" name="field3" /> 992 <input type="text" name="field8" /> 993 <input type="text" name="field2" /> 994 other 995 <input type="text" name="field5" /> 996 <input type="text" name="field11" /> 997 <input type="text" name="field14" /> 998 999 Fieldsets and fields in fieldsets can be identified by names. 1000 >>> print p.fieldsets()['main'].name 1001 main 1002 >>> print p.fieldsets()['other']['field5'] 1003 <input type="text" name="field5" /> 1004 1005 Each fieldset has attribute errors for current fields. 1006 >>> for fieldset in p.fieldsets(): 1007 ... fieldset.errors.as_ul() 1008 u'' 1009 u'' 1010 >>> p = FieldsetsTestForm({'field3': '3', 'field2': '2', 'field5': '5', 'field11': '11'}) 1011 >>> for fieldset in p.fieldsets(): 1012 ... fieldset.errors.as_ul() 1013 u'<ul class="errorlist"><li>field8<ul class="errorlist"><li>This field is required.</li></ul></li></ul>' 1014 u'<ul class="errorlist"><li>field14<ul class="errorlist"><li>This field is required.</li></ul></li></ul>' 1015 1016 Or to use fields and exlude at once. 1017 >>> class CombinedTestForm(TestForm): 1018 ... class Meta: 1019 ... fields = ('field4', 'field2', 'field11', 'field8') 1020 ... exclude = ('field14', 'field3', 'field5', 'field8', 'field1') 1021 ... fieldsets = ( 1022 ... ('main', {'fields': ('field3', 'field8', 'field2')}), 1023 ... ('other', {'fields': ('field5', 'field11', 'field14')}), 1024 ... ) 1025 >>> p = CombinedTestForm(auto_id=False) 1026 >>> print p 1027 <tr><th>Field2:</th><td><input type="text" name="field2" /></td></tr> 1028 <tr><th>Field11:</th><td><input type="text" name="field11" /></td></tr> 1029 >>> print p.has_fieldsets 1030 True 1031 >>> for fieldset in p.fieldsets(): 1032 ... print fieldset.name 1033 ... for field in fieldset: 1034 ... print field 1035 main 1036 <input type="text" name="field2" /> 1037 other 1038 <input type="text" name="field11" /> 1039 1040 You can use fieldsets in your general templates also if you do not define any. 1041 >>> class OrderedTestForm(TestForm): 1042 ... class Meta: 1043 ... fields = ('field4', 'field2', 'field11', 'field8') 1044 >>> p = OrderedTestForm(auto_id=False) 1045 >>> print p.has_fieldsets 1046 False 1047 >>> for fieldset in p.fieldsets(): 1048 ... print fieldset.name 1049 ... for field in fieldset: 1050 ... print field 1051 None 1052 <input type="text" name="field4" /> 1053 <input type="text" name="field2" /> 1054 <input type="text" name="field11" /> 1055 <input type="text" name="field8" /> 1056 1057 Fieldsets can have some additional options - attrs, legend and description. 1058 You can use them in your templates. 1059 >>> class FieldsetsTestForm(TestForm): 1060 ... class Meta: 1061 ... fieldsets = ( 1062 ... ('main', { 1063 ... 'fields': ('field3', 'field8', 'field2'), 1064 ... 'legend': 'Main fields', 1065 ... 'description': 'You should really fill these fields.', 1066 ... }), 1067 ... ('other', { 1068 ... 'fields': ('field5', 'field11', 'field14'), 1069 ... 'legend': 'Other fields', 1070 ... 'attrs': {'class': 'other'}, 1071 ... }), 1072 ... ) 1073 >>> p = FieldsetsTestForm(auto_id=False) 1074 >>> print p 1075 <tr><th>Field2:</th><td><input type="text" name="field2" /></td></tr> 1076 <tr><th>Field3:</th><td><input type="text" name="field3" /></td></tr> 1077 <tr><th>Field5:</th><td><input type="text" name="field5" /></td></tr> 1078 <tr><th>Field8:</th><td><input type="text" name="field8" /></td></tr> 1079 <tr><th>Field11:</th><td><input type="text" name="field11" /></td></tr> 1080 <tr><th>Field14:</th><td><input type="text" name="field14" /></td></tr> 1081 >>> print p.has_fieldsets 1082 True 1083 >>> for fieldset in p.fieldsets(): 1084 ... print 'name:', fieldset.name 1085 ... print 'attrs:', fieldset.attrs 1086 ... print 'description:', fieldset.description 1087 ... print 'legend:', fieldset.legend 1088 ... for field in fieldset: 1089 ... print field 1090 name: main 1091 attrs: None 1092 description: You should really fill these fields. 1093 legend: Main fields 1094 <input type="text" name="field3" /> 1095 <input type="text" name="field8" /> 1096 <input type="text" name="field2" /> 1097 name: other 1098 attrs: {'class': 'other'} 1099 description: None 1100 legend: Other fields 1101 <input type="text" name="field5" /> 1102 <input type="text" name="field11" /> 1103 <input type="text" name="field14" /> 1104 969 1105 Some Field classes have an effect on the HTML attributes of their associated 970 1106 Widget. If you set max_length in a CharField and its associated widget is 971 1107 either a TextInput or PasswordInput, then the widget's rendered HTML will