Ticket #6630: 00-fieldsets.diff
File 00-fieldsets.diff, 21.3 KB (added by , 16 years ago) |
---|
-
django/contrib/admin/options.py
=== modified file 'django/contrib/admin/options.py'
267 267 exclude = [] 268 268 else: 269 269 exclude = list(self.exclude) 270 exclude += kwargs.get("exclude", []) 271 if not exclude: 272 exclude = None 270 273 defaults = { 271 274 "form": self.form, 272 275 "fields": fields, 273 "exclude": exclude + kwargs.get("exclude", []),276 "exclude": exclude, 274 277 "formfield_callback": self.formfield_for_dbfield, 275 278 } 276 279 defaults.update(kwargs) -
django/contrib/auth/forms.py
=== modified file 'django/contrib/auth/forms.py'
19 19 20 20 class Meta: 21 21 model = User 22 fields = ("username", )22 fields = ("username", "password1", "password2") 23 23 24 24 def clean_username(self): 25 25 username = self.cleaned_data["username"] -
django/forms/forms.py
=== modified file 'django/forms/forms.py'
4 4 5 5 from copy import deepcopy 6 6 7 from django.core.exceptions import ImproperlyConfigured 7 8 from django.utils.datastructures import SortedDict 8 9 from django.utils.html import escape 9 10 from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode … … 22 23 name = name[0].upper() + name[1:] 23 24 return name.replace('_', ' ') 24 25 25 def get_declared_fields(bases, attrs, with_base_fields=True):26 """27 Create a list of form field instances from the passed in 'attrs', plus any28 similar fields on the base classes (in 'bases'). This is used by both the29 Form and ModelForm metclasses.26 class FormOptions(object): 27 def __init__(self, options=None): 28 self.fieldsets = getattr(options, 'fieldsets', None) 29 self.fields = getattr(options, 'fields', None) 30 self.exclude = getattr(options, 'exclude', None) 30 31 31 If 'with_base_fields' is True, all fields from the bases are used. 32 Otherwise, only fields in the 'declared_fields' attribute on the bases are 33 used. The distinction is useful in ModelForm subclassing. 34 Also integrates any additional media definitions 35 """ 36 fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)] 32 def create_declared_fields(cls, attrs): 33 """ 34 Create a list of form field instances from the passed in 'attrs'. 35 This is used by both the Form and ModelForm metaclasses. 36 """ 37 fields = [] 38 for name, possible_field in attrs.items(): 39 if isinstance(possible_field, Field): 40 fields.append((name, possible_field)) 41 delattr(cls, name) 37 42 fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter)) 38 39 # If this class is subclassing another Form, add that Form's fields. 40 # Note that we loop over the bases in *reverse*. This is necessary in 41 # order to preserve the correct order of fields. 42 if with_base_fields: 43 for base in bases[::-1]: 44 if hasattr(base, 'base_fields'): 45 fields = base.base_fields.items() + fields 43 cls.declared_fields = SortedDict(fields) 44 45 def create_base_fields_pool_from_declared_fields(cls, attrs): 46 """ 47 Create a list of form field instances which are declared in the form and 48 its superclasses (from 'csl.__mro__'). This is used by the Form metaclass. 49 50 Note that we loop over the bases in *reverse*. This is necessary in 51 order to preserve the correct order of fields. 52 """ 53 fields = [] 54 for base in cls.__mro__[::-1]: 55 try: 56 fields += base.declared_fields.items() # Raise AttributeError if the base is not a form. 57 except AttributeError: 58 pass 59 cls.base_fields_pool = SortedDict(fields) 60 61 def create_base_fields_from_base_fields_pool(cls, attrs): 62 """ 63 Create a list of form field instances from the base fields pool. Select 64 only the fields which are defined in one of the options 'fieldsets', 65 'fields' and 'exclude'. If no option is set, select all fields. 66 This is used by both the Form and ModelForm metaclasses. 67 68 Also check that only one option is used. 69 """ 70 if (cls._meta.fieldsets is None) + (cls._meta.fields is None) + (cls._meta.exclude is None) < 2: 71 raise ImproperlyConfigured("%s cannot have more than one option from fieldsets, fields and exclude." % cls.__name__) 72 if cls._meta.fieldsets: 73 names = [] 74 for fieldset in cls._meta.fieldsets: 75 names.extend(fieldset['fields']) 76 elif cls._meta.fields: 77 names = cls._meta.fields 78 elif cls._meta.exclude: 79 names = [name for name in cls.base_fields_pool if name not in cls._meta.exclude] 46 80 else: 47 for base in bases[::-1]: 48 if hasattr(base, 'declared_fields'): 49 fields = base.declared_fields.items() + fields 50 51 return SortedDict(fields) 52 53 class DeclarativeFieldsMetaclass(type): 81 names = cls.base_fields_pool.keys() 82 cls.base_fields = SortedDict([(name, cls.base_fields_pool[name]) for name in names]) 83 84 class FormMetaclass(type): 54 85 """ 55 86 Metaclass that converts Field attributes to a dictionary called 56 87 'base_fields', taking into account parent class 'base_fields' as well. 88 89 Also integrates any additional media definitions 57 90 """ 58 91 def __new__(cls, name, bases, attrs): 59 attrs['base_fields'] = get_declared_fields(bases, attrs) 60 new_class = super(DeclarativeFieldsMetaclass, 61 cls).__new__(cls, name, bases, attrs) 92 new_class = type.__new__(cls, name, bases, attrs) 93 new_class._meta = FormOptions(getattr(new_class, 'Meta', None)) 94 create_declared_fields(new_class, attrs) 95 create_base_fields_pool_from_declared_fields(new_class, attrs) 96 create_base_fields_from_base_fields_pool(new_class, attrs) 62 97 if 'media' not in attrs: 63 98 new_class.media = media_property(new_class) 64 99 return new_class … … 105 140 raise KeyError('Key %r not found in Form' % name) 106 141 return BoundField(self, field, name) 107 142 143 def has_fieldsets(self): 144 return self._meta.fieldsets is not None 145 146 def fieldsets(self): 147 if self.has_fieldsets(): 148 for fieldset in self._meta.fieldsets: 149 yield { 150 'attrs': fieldset.get('attrs', {}), 151 'legend': fieldset.get('legend', u''), 152 'fields': [self[name] for name in fieldset['fields']], 153 } 154 108 155 def _get_errors(self): 109 156 "Returns an ErrorDict for the data provided for the form" 110 157 if self._errors is None: … … 310 357 # fancy metaclass stuff purely for the semantic sugar -- it allows one 311 358 # to define a form using declarative syntax. 312 359 # BaseForm itself has no way of designating self.fields. 313 __metaclass__ = DeclarativeFieldsMetaclass360 __metaclass__ = FormMetaclass 314 361 315 362 class BoundField(StrAndUnicode): 316 363 "A Field plus data" -
django/forms/models.py
=== modified file 'django/forms/models.py'
9 9 from django.utils.translation import ugettext_lazy as _ 10 10 11 11 from util import ValidationError, ErrorList 12 from forms import BaseForm, get_declared_fields 12 from forms import FormOptions, BaseForm, create_declared_fields 13 from forms import create_base_fields_from_base_fields_pool 13 14 from fields import Field, ChoiceField, IntegerField, EMPTY_VALUES 14 15 from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput 15 16 from widgets import media_property … … 154 155 field_list.append((f.name, formfield)) 155 156 return SortedDict(field_list) 156 157 157 class ModelFormOptions( object):158 class ModelFormOptions(FormOptions): 158 159 def __init__(self, options=None): 160 super(ModelFormOptions, self).__init__(options) 159 161 self.model = getattr(options, 'model', None) 160 self.fields = getattr(options, 'fields', None) 161 self.exclude = getattr(options, 'exclude', None) 162 162 163 def create_model_fields(cls, attrs): 164 """ 165 Create a list of form field instances from the option 'model'. 166 This is used by the ModelForm metaclass. 167 """ 168 formfield_callback = attrs.pop('formfield_callback', lambda f: f.formfield()) 169 fields = [] 170 if cls._meta.model: 171 for dbfield in cls._meta.model._meta.fields + cls._meta.model._meta.many_to_many: 172 if dbfield.editable: 173 formfield = formfield_callback(dbfield) 174 if formfield: 175 fields.append((dbfield.name, formfield)) 176 cls.model_fields = SortedDict(fields) 177 178 def create_base_fields_pool_from_model_fields_and_declared_fields(cls, attrs): 179 """ 180 Create a list of form field instances which are declared in the form and 181 its superclasses (from 'csl.__mro__'). Add fields from the last form 182 with a model. This is used by the MetaclasForm metaclass. 183 184 Note that we loop over the bases in *reverse*. This is necessary in 185 order to preserve the correct order of fields. 186 """ 187 model_fields, declared_fields = [], [] 188 for base in cls.__mro__[::-1]: 189 try: 190 declared_fields += base.declared_fields.items() # Raise AttributeError if the base is not a form. 191 if base._meta.model: # Raise AttributeError if the base is not a model form. 192 model_fields = base.model_fields.items() 193 except AttributeError: 194 pass 195 cls.base_fields_pool = SortedDict(model_fields + declared_fields) 163 196 164 197 class ModelFormMetaclass(type): 198 """ 199 Metaclass that converts Field attributes to a dictionary called 200 'base_fields', taking into account parent class 'base_fields' as well. 201 Add fields from the class' model. 202 203 Also integrates any additional media definitions 204 """ 165 205 def __new__(cls, name, bases, attrs): 166 formfield_callback = attrs.pop('formfield_callback', 167 lambda f: f.formfield()) 168 try: 169 parents = [b for b in bases if issubclass(b, ModelForm)] 170 except NameError: 171 # We are defining ModelForm itself. 172 parents = None 173 declared_fields = get_declared_fields(bases, attrs, False) 174 new_class = super(ModelFormMetaclass, cls).__new__(cls, name, bases, 175 attrs) 176 if not parents: 177 return new_class 178 206 new_class = type.__new__(cls, name, bases, attrs) 207 new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None)) 208 create_model_fields(new_class, attrs) 209 create_declared_fields(new_class, attrs) 210 create_base_fields_pool_from_model_fields_and_declared_fields(new_class, attrs) 211 create_base_fields_from_base_fields_pool(new_class, attrs) 179 212 if 'media' not in attrs: 180 213 new_class.media = media_property(new_class) 181 opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None))182 if opts.model:183 # If a model is defined, extract form fields from it.184 fields = fields_for_model(opts.model, opts.fields,185 opts.exclude, formfield_callback)186 # Override default model fields with any custom declared ones187 # (plus, include all the other declared fields).188 fields.update(declared_fields)189 else:190 fields = declared_fields191 new_class.declared_fields = declared_fields192 new_class.base_fields = fields193 214 return new_class 194 215 195 216 class BaseModelForm(BaseForm): 196 217 def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, 197 218 initial=None, error_class=ErrorList, label_suffix=':', 198 219 empty_permitted=False, instance=None): 199 opts = self._meta200 220 if instance is None: 201 221 # if we didn't get an instance, instantiate a new one 202 self.instance = opts.model()222 self.instance = self._meta.model() 203 223 object_data = {} 204 224 else: 205 225 self.instance = instance 206 object_data = model_to_dict(instance , opts.fields, opts.exclude)226 object_data = model_to_dict(instance) 207 227 # if initial was provided, it should override the values from instance 208 228 if initial is not None: 209 229 object_data.update(initial) … … 309 329 fail_message = 'created' 310 330 else: 311 331 fail_message = 'changed' 312 return save_instance(self, self.instance, self. _meta.fields, fail_message, commit)332 return save_instance(self, self.instance, self.fields.keys(), fail_message, commit) 313 333 314 334 class ModelForm(BaseModelForm): 315 335 __metaclass__ = ModelFormMetaclass 316 336 317 def modelform_factory(model, form=ModelForm, fields=None, exclude=None, 337 def modelform_factory(model, form=ModelForm, fields=None, exclude=None, fieldsets=None, 318 338 formfield_callback=lambda f: f.formfield()): 319 339 # HACK: we should be able to construct a ModelForm without creating 320 340 # and passing in a temporary inner class 321 341 class Meta: 322 342 pass 323 343 setattr(Meta, 'model', model) 344 setattr(Meta, 'fieldsets', fieldsets) 324 345 setattr(Meta, 'fields', fields) 325 346 setattr(Meta, 'exclude', exclude) 326 347 class_name = model.__name__ + 'Form' … … 430 451 def modelformset_factory(model, form=ModelForm, formfield_callback=lambda f: f.formfield(), 431 452 formset=BaseModelFormSet, 432 453 extra=1, can_delete=False, can_order=False, 433 max_num=0, fields=None, exclude=None ):454 max_num=0, fields=None, exclude=None, fieldsets=None): 434 455 """ 435 456 Returns a FormSet class for the given Django model class. 436 457 """ 437 458 form = modelform_factory(model, form=form, fields=fields, exclude=exclude, 459 fieldsets=fieldsets, 438 460 formfield_callback=formfield_callback) 439 461 FormSet = formset_factory(form, formset, extra=extra, max_num=max_num, 440 462 can_order=can_order, can_delete=can_delete) … … 524 546 525 547 def inlineformset_factory(parent_model, model, form=ModelForm, 526 548 formset=BaseInlineFormSet, fk_name=None, 527 fields=None, exclude=None, 549 fields=None, exclude=None, fieldsets=None, 528 550 extra=3, can_order=False, can_delete=True, max_num=0, 529 551 formfield_callback=lambda f: f.formfield()): 530 552 """ … … 549 571 'extra': extra, 550 572 'can_delete': can_delete, 551 573 'can_order': can_order, 574 'fieldsets': fieldsets, 552 575 'fields': fields, 553 576 'exclude': exclude, 554 577 'max_num': max_num, -
tests/modeltests/model_forms/models.py
=== modified file 'tests/modeltests/model_forms/models.py'
217 217 ... model = Category 218 218 ... fields = ['name', 'url'] 219 219 ... exclude = ['url'] 220 221 >>> CategoryForm.base_fields.keys() 222 ['name'] 220 Traceback (most recent call last): 221 File "/home/petr/django/local2/00-forms-fieldsets/django/test/_doctest.py", line 1267, in __run 222 compileflags, 1) in test.globs 223 File "<doctest modeltests.model_forms.models.__test__.API_TESTS[12]>", line 1, in ? 224 class CategoryForm(ModelForm): 225 File "/home/petr/django/local2/00-forms-fieldsets/django/forms/models.py", line 220, in __new__ 226 metaclassing.create_base_fields_from_base_fields_pool(new_class) 227 File "/home/petr/django/local2/00-forms-fieldsets/django/forms/metaclassing.py", line 50, in create_base_fields_from_base_fields_pool 228 raise ImproperlyConfigured("%s cannot have more than one option from fieldsets, fields and exclude." % cls.__name__) 229 ImproperlyConfigured: CategoryForm cannot have more than one option from fieldsets, fields and exclude. 223 230 224 231 Don't allow more than one 'model' definition in the inheritance hierarchy. 225 232 Technically, it would generate a valid form, but the fact that the resulting -
tests/regressiontests/forms/forms.py
=== modified file 'tests/regressiontests/forms/forms.py'
1265 1265 ... haircut_type = CharField() 1266 1266 >>> b = Beatle(auto_id=False) 1267 1267 >>> print b.as_ul() 1268 <li>Instrument: <input type="text" name="instrument" /></li> 1268 1269 <li>First name: <input type="text" name="first_name" /></li> 1269 1270 <li>Last name: <input type="text" name="last_name" /></li> 1270 1271 <li>Birthday: <input type="text" name="birthday" /></li> 1271 <li>Instrument: <input type="text" name="instrument" /></li>1272 1272 <li>Haircut type: <input type="text" name="haircut_type" /></li> 1273 1273 1274 1274 # Forms with prefixes ######################################################### … … 1749 1749 >>> form.is_valid() 1750 1750 True 1751 1751 1752 # Forms with meta attributes fields, exclude and fielsets ##################### 1753 1754 >>> class UserForm(Form): 1755 ... username = CharField() 1756 ... email = CharField(widget=PasswordInput) 1757 ... first_name = CharField() 1758 ... last_name = CharField() 1759 1760 >>> t = Template(''' 1761 ... <form action=""> 1762 ... {% if form.has_fieldsets %} 1763 ... {% for fieldset in form.fieldsets %} 1764 ... <fieldset> 1765 ... {% if fieldset.legend %} 1766 ... <legend>{{ fieldset.legend }}</legend> 1767 ... {% endif %} 1768 ... {% for field in fieldset.fields %} 1769 ... <p><label>{{ field.label }}: {{ field }}</label></p> 1770 ... {% endfor %} 1771 ... </fieldset> 1772 ... {% endfor %} 1773 ... {% else %} 1774 ... {% for field in form %} 1775 ... <p><label>{{ field.label }}: {{ field }}</label></p> 1776 ... {% endfor %} 1777 ... {% endif %} 1778 ... <input type="submit" /> 1779 ... </form>''') 1780 1781 >>> clean_re = re.compile(r'\n( *\n)+') 1782 >>> clean = lambda text: clean_re.sub('\n', text).strip() 1783 1784 >>> print clean(t.render(Context({'form': UserForm()}))) 1785 <form action=""> 1786 <p><label>Username: <input type="text" name="username" id="id_username" /></label></p> 1787 <p><label>Email: <input type="password" name="email" id="id_email" /></label></p> 1788 <p><label>First name: <input type="text" name="first_name" id="id_first_name" /></label></p> 1789 <p><label>Last name: <input type="text" name="last_name" id="id_last_name" /></label></p> 1790 <input type="submit" /> 1791 </form> 1792 1793 >>> class OrderingUserForm(UserForm): 1794 ... class Meta: 1795 ... fields = ('first_name', 'last_name', 'username', 'email') 1796 1797 >>> print clean(t.render(Context({'form': OrderingUserForm()}))) 1798 <form action=""> 1799 <p><label>First name: <input type="text" name="first_name" id="id_first_name" /></label></p> 1800 <p><label>Last name: <input type="text" name="last_name" id="id_last_name" /></label></p> 1801 <p><label>Username: <input type="text" name="username" id="id_username" /></label></p> 1802 <p><label>Email: <input type="password" name="email" id="id_email" /></label></p> 1803 <input type="submit" /> 1804 </form> 1805 1806 >>> class FilteringUserForm(UserForm): 1807 ... class Meta: 1808 ... fields = ('first_name', 'last_name') 1809 1810 >>> print clean(t.render(Context({'form': FilteringUserForm()}))) 1811 <form action=""> 1812 <p><label>First name: <input type="text" name="first_name" id="id_first_name" /></label></p> 1813 <p><label>Last name: <input type="text" name="last_name" id="id_last_name" /></label></p> 1814 <input type="submit" /> 1815 </form> 1816 1817 >>> class ExcludingUserForm(UserForm): 1818 ... class Meta: 1819 ... exclude = ('first_name', 'last_name') 1820 1821 >>> print clean(t.render(Context({'form': ExcludingUserForm()}))) 1822 <form action=""> 1823 <p><label>Username: <input type="text" name="username" id="id_username" /></label></p> 1824 <p><label>Email: <input type="password" name="email" id="id_email" /></label></p> 1825 <input type="submit" /> 1826 </form> 1827 1828 >>> class FieldsetUserForm(UserForm): 1829 ... class Meta: 1830 ... fieldsets = ( 1831 ... {'fields': ('username', 'email')}, 1832 ... {'fields': ('first_name', 'last_name'), 'legend': 'Name'}, 1833 ... ) 1834 1835 >>> print clean(t.render(Context({'form': FieldsetUserForm()}))) 1836 <form action=""> 1837 <fieldset> 1838 <p><label>Username: <input type="text" name="username" id="id_username" /></label></p> 1839 <p><label>Email: <input type="password" name="email" id="id_email" /></label></p> 1840 </fieldset> 1841 <fieldset> 1842 <legend>Name</legend> 1843 <p><label>First name: <input type="text" name="first_name" id="id_first_name" /></label></p> 1844 <p><label>Last name: <input type="text" name="last_name" id="id_last_name" /></label></p> 1845 </fieldset> 1846 <input type="submit" /> 1847 </form> 1848 1752 1849 """ -
tests/regressiontests/modeladmin/models.py
=== modified file 'tests/regressiontests/modeladmin/models.py'
132 132 >>> ma.get_form(request).base_fields.keys() 133 133 ['name', 'sign_date'] 134 134 135 # Using `fields` and `exclude`.136 137 >>> class BandAdmin(ModelAdmin):138 ... fields = ['name', 'bio']139 ... exclude = ['bio']140 >>> ma = BandAdmin(Band, site)141 >>> ma.get_form(request).base_fields.keys()142 ['name']143 144 135 If we specify a form, it should use it allowing custom validation to work 145 136 properly. This won't, however, break any of the admin widgets or media. 146 137