Ticket #17924: 17924.diff
File 17924.diff, 13.2 KB (added by , 12 years ago) |
---|
-
django/forms/models.py
diff --git a/django/forms/models.py b/django/forms/models.py index 4d56f1d..0686525 100644
a b and database field objects. 5 5 6 6 from __future__ import absolute_import, unicode_literals 7 7 8 from django.core.exceptions import ValidationError, NON_FIELD_ERRORS, FieldError 8 from django.core.exceptions import ( 9 ValidationError, NON_FIELD_ERRORS, FieldError, ImproperlyConfigured) 9 10 from django.core.validators import EMPTY_VALUES 10 11 from django.forms.fields import Field, ChoiceField 11 12 from django.forms.forms import BaseForm, get_declared_fields … … def model_to_dict(instance, fields=None, exclude=None): 131 132 data[f.name] = f.value_from_object(instance) 132 133 return data 133 134 134 def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_callback=None ):135 def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_callback=None, overrides=None): 135 136 """ 136 137 Returns a ``SortedDict`` containing form fields for the given model. 137 138 … … def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_c 144 145 """ 145 146 field_list = [] 146 147 ignored = [] 148 applied_overrides = [] 147 149 opts = model._meta 148 150 for f in sorted(opts.fields + opts.many_to_many): 149 151 if not f.editable: … … def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_c 157 159 else: 158 160 kwargs = {} 159 161 162 if overrides: 163 for key, kwargs_overrides in overrides.iteritems(): 164 if (key == f.name or 165 (isinstance(key, type) and isinstance(f, key)) or 166 (callable(key) and key(f))): 167 kwargs.update(kwargs_overrides) 168 applied_overrides.append(key) 169 160 170 if formfield_callback is None: 161 171 formfield = f.formfield(**kwargs) 162 172 elif not callable(formfield_callback): … … def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_c 168 178 field_list.append((f.name, formfield)) 169 179 else: 170 180 ignored.append(f.name) 181 182 if overrides: 183 for key in overrides.keys(): 184 if not key in applied_overrides: 185 if isinstance(key, (type, basestring)): 186 # TBC: should these pass silently? 187 raise ImproperlyConfigured( 188 'ModelForm.Meta.overrides contained unused key: %r' % 189 key) 190 else: 191 pass # TBC: ignore callables either way? 192 171 193 field_dict = SortedDict(field_list) 172 194 if fields: 173 195 field_dict = SortedDict( … … class ModelFormOptions(object): 182 204 self.fields = getattr(options, 'fields', None) 183 205 self.exclude = getattr(options, 'exclude', None) 184 206 self.widgets = getattr(options, 'widgets', None) 207 self.overrides = getattr(options, 'overrides', None) 185 208 186 209 187 210 class ModelFormMetaclass(type): … … class ModelFormMetaclass(type): 203 226 opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None)) 204 227 if opts.model: 205 228 # If a model is defined, extract form fields from it. 206 fields = fields_for_model(opts.model, opts.fields, 207 opts.exclude, opts.widgets, formfield_callback) 229 fields = fields_for_model( 230 opts.model, opts.fields, opts.exclude, opts.widgets, 231 formfield_callback, overrides=opts.overrides) 208 232 # make sure opts.fields doesn't specify an invalid field 209 233 none_model_fields = [k for k, v in fields.iteritems() if not v] 210 234 missing_fields = set(none_model_fields) - \ -
docs/topics/forms/modelforms.txt
diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index 0ca3774..85c3e3a 100644
a b widget:: 388 388 The ``widgets`` dictionary accepts either widget instances (e.g., 389 389 ``Textarea(...)``) or classes (e.g., ``Textarea``). 390 390 391 .. versionadded:: 1.5 392 391 393 If you want to further customize a field -- including its type, label, etc. -- 392 you can do this by declaratively specifying fields like you would in a regular 393 ``Form``. Declared fields will override the default ones generated by using the 394 ``model`` attribute. 394 you can use the ``overrides`` attribute of the inner ``Meta`` class. Using 395 this you can not only change the widget [#]_ but also override any other 396 keyword argument that the form field's ``__init__`` accepts, such as 397 ``label``, ``help_text``, ``widget``, ``required``, ``form_class``, etc. The 398 ``overrides`` attribute is a dictionary with keys which can be either: 399 400 * a field name such as ``'name'``, ``'birth_date'`` to override a specific 401 field, or 402 * a model field class as ``models.CharField``, ``models.FloatField`` to 403 override all fields of a particular type, or 404 * a callable to perform more complex matching, such as 405 ``lambda f: f.name.startswith('question_')`` 406 407 The values of the ``overrides`` dictionary are simply dictionaries of keyword 408 arguments that are passed to :meth:`~django.forms.Field.__init__` (or subclass thereof). 409 410 .. [#] FIXME: "There should be one-- and preferably only one --obvious way to do it." 411 412 You can also completely override form fields by declaratively specifying fields like 413 you would in a regular ``Form``. Declared fields will override the default ones 414 generated by using the ``model`` attribute. 395 415 396 416 For example, if you wanted to use ``MyDateFormField`` for the ``pub_date`` 397 417 field, you could do the following:: -
tests/regressiontests/forms/models.py
diff --git a/tests/regressiontests/forms/models.py b/tests/regressiontests/forms/models.py index 18e6ddc..ffc3d32 100644
a b class Group(models.Model): 74 74 75 75 76 76 class Cheese(models.Model): 77 name = models.CharField(max_length=100) 77 name = models.CharField(max_length=100) 78 79 80 class WalkCategory(models.Model): 81 name = models.CharField(max_length=100) 82 83 def __unicode__(self): 84 return self.name 85 86 87 class Walk(models.Model): 88 name = models.CharField(max_length=255) 89 silliness = models.FloatField( 90 help_text='0 = not silly at all, 1 = completely silly') 91 description = models.CharField(max_length=1024, blank=True) 92 difficulty = models.PositiveSmallIntegerField( 93 choices=tuple( 94 enumerate(['Easy', 'Moderate', 'Difficult', 'Impossible']) 95 ) 96 ) 97 category = models.ForeignKey(WalkCategory) 98 99 def __unicode__(self): 100 return self.name -
tests/regressiontests/forms/tests/__init__.py
diff --git a/tests/regressiontests/forms/tests/__init__.py b/tests/regressiontests/forms/tests/__init__.py index 8e2150c..1784cb1 100644
a b from .input_formats import (LocalizedTimeTests, CustomTimeInputFormatsTests, 13 13 CustomDateTimeInputFormatsTests, SimpleDateTimeFormatTests) 14 14 from .media import FormsMediaTestCase, StaticFormsMediaTestCase 15 15 from .models import (TestTicket12510, ModelFormCallableModelDefault, 16 FormsModelTestCase, RelatedModelFormTests )16 FormsModelTestCase, RelatedModelFormTests, OverridesTest) 17 17 from .regressions import FormsRegressionsTestCase 18 18 from .util import FormsUtilTestCase 19 19 from .validators import TestFieldWithValidators -
tests/regressiontests/forms/tests/models.py
diff --git a/tests/regressiontests/forms/tests/models.py b/tests/regressiontests/forms/tests/models.py index 7687335..204b125 100644
a b import datetime 5 5 6 6 from django.core.files.uploadedfile import SimpleUploadedFile 7 7 from django.db import models 8 from django.forms import Form, ModelForm, FileField, ModelChoiceField 8 from django.db.models.fields import BLANK_CHOICE_DASH 9 from django.forms import Form, ModelForm, FileField, ModelChoiceField, widgets 9 10 from django.forms.models import ModelFormMetaclass 10 11 from django.test import TestCase 11 12 from django.utils import six 13 from django.utils.encoding import smart_unicode 12 14 13 15 from ..models import (ChoiceOptionModel, ChoiceFieldModel, FileModel, Group, 14 BoundaryModel, Defaults )16 BoundaryModel, Defaults, Walk, WalkCategory) 15 17 16 18 17 19 class ChoiceFieldForm(ModelForm): … … class RelatedModelFormTests(TestCase): 197 199 model=A 198 200 199 201 self.assertTrue(issubclass(ModelFormMetaclass(b'Form', (ModelForm,), {'Meta': Meta}), ModelForm)) 202 203 204 class WalkForm(ModelForm): 205 class Meta: 206 model = Walk 207 208 class StrictWalkForm(WalkForm): 209 class Meta(WalkForm.Meta): 210 overrides = { 211 'description': { 212 'required': True, 213 }, 214 } 215 216 class TextareaWalkForm(WalkForm): 217 class Meta(WalkForm.Meta): 218 overrides = { 219 (lambda f: getattr(f, 'max_length', 0) > 300): { 220 'widget': widgets.Textarea, 221 }, 222 } 223 224 class HelpfulWalkForm(WalkForm): 225 class Meta(WalkForm.Meta): 226 overrides = { 227 'silliness': { 228 'help_text': 'Range: 0.0 -> 1.0', 229 }, 230 'description': { 231 'help_text': '(optional)', 232 }, 233 'category': { 234 'empty_label': '--[ choose one ]--', 235 }, 236 } 237 238 class ShortWalkForm(WalkForm): 239 class Meta(WalkForm.Meta): 240 overrides = { 241 'name': { 242 'label': 'Short name', 243 'max_length': 50, 244 }, 245 } 246 247 class RelaxedWalkForm(WalkForm): 248 class Meta(WalkForm.Meta): 249 overrides = { 250 'name': { 251 'required': False, 252 }, 253 } 254 255 class ModelChoiceFieldWithKeys(ModelChoiceField): 256 def label_from_instance(self, obj): 257 return u'[%s] %s' % ( 258 smart_unicode(obj.pk), 259 smart_unicode(obj)) 260 261 class FancyWalkForm(WalkForm): 262 class Meta(WalkForm.Meta): 263 overrides = { 264 'category': { 265 'form_class': ModelChoiceFieldWithKeys, 266 }, 267 } 268 269 270 class OverridesTest(TestCase): 271 def setUp(self): 272 WalkCategory.objects.create(id=1, name='Long-legged') 273 WalkCategory.objects.create(id=2, name='All-fours') 274 self.normal_form = WalkForm() 275 self.strict_form = StrictWalkForm() 276 self.textarea_form = TextareaWalkForm() 277 self.short_form = ShortWalkForm() 278 self.relaxed_form = RelaxedWalkForm() 279 self.help_form = HelpfulWalkForm() 280 self.fancy_form = FancyWalkForm() 281 282 def test_required_false(self): 283 self.assertTrue(self.normal_form.fields['name'].required) 284 self.assertTrue(self.normal_form.fields['name'].widget.is_required) 285 286 self.assertFalse(self.relaxed_form.fields['name'].required) 287 self.assertFalse(self.relaxed_form.fields['name'].widget.is_required) 288 289 def test_required_true(self): 290 self.assertFalse( 291 self.normal_form.fields['description'].required) 292 self.assertFalse( 293 self.normal_form.fields['description'].widget.is_required) 294 295 self.assertTrue( 296 self.strict_form.fields['description'].required) 297 self.assertTrue( 298 self.strict_form.fields['description'].widget.is_required) 299 300 def test_max_length(self): 301 self.assertEqual(self.normal_form.fields['name'].max_length, 255) 302 self.assertEqual( 303 self.normal_form.fields['name'].widget.attrs['maxlength'], '255') 304 305 self.assertEqual(self.short_form.fields['name'].max_length, 50) 306 self.assertEqual( 307 self.short_form.fields['name'].widget.attrs['maxlength'], '50') 308 309 def test_textarea(self): 310 self.assertEqual( 311 type(self.normal_form.fields['description'].widget), 312 widgets.TextInput) 313 self.assertEqual( 314 type(self.textarea_form.fields['description'].widget), 315 widgets.Textarea) 316 317 def test_helptext(self): 318 self.assertRegexpMatches( 319 self.normal_form.fields['silliness'].help_text, r'^0 = not silly') 320 self.assertRegexpMatches( 321 self.help_form.fields['silliness'].help_text, r'^Range: 0') 322 323 self.assertEqual( 324 self.normal_form.fields['description'].help_text, '') 325 self.assertEqual( 326 self.help_form.fields['description'].help_text, '(optional)') 327 328 def test_empty_label(self): 329 self.assertRegexpMatches( 330 self.normal_form.fields['category'].empty_label, r'^\-+$') 331 self.assertEqual( 332 self.help_form.fields['category'].empty_label, 333 '--[ choose one ]--') 334 335 def test_label(self): 336 self.assertEqual(self.normal_form.fields['name'].label, 'Name') 337 self.assertEqual(self.short_form.fields['name'].label, 'Short name') 338 339 def test_form_class(self): 340 self.assertEqual( 341 list(self.normal_form.fields['category'].choices)[1:], 342 [(1, u'Long-legged'), (2, u'All-fours')] 343 ) 344 self.assertEqual( 345 list(self.fancy_form.fields['category'].choices)[1:], 346 [(1, u'[1] Long-legged'), (2, u'[2] All-fours')] 347 )