Fixed #9223 -- Added support for declarative widgets to ModelForm. I declare thanks to isagalaev.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@12194 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Adrian Holovaty 2010-01-10 19:23:42 +00:00
parent 06645cbda7
commit 9bb1fa7251
3 changed files with 66 additions and 18 deletions

View File

@ -159,7 +159,7 @@ def model_to_dict(instance, fields=None, exclude=None):
data[f.name] = f.value_from_object(instance) data[f.name] = f.value_from_object(instance)
return data return data
def fields_for_model(model, fields=None, exclude=None, formfield_callback=lambda f: f.formfield()): def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)):
""" """
Returns a ``SortedDict`` containing form fields for the given model. Returns a ``SortedDict`` containing form fields for the given model.
@ -179,7 +179,11 @@ def fields_for_model(model, fields=None, exclude=None, formfield_callback=lambda
continue continue
if exclude and f.name in exclude: if exclude and f.name in exclude:
continue continue
formfield = formfield_callback(f) if widgets and f.name in widgets:
kwargs = {'widget': widgets[f.name]}
else:
kwargs = {}
formfield = formfield_callback(f, **kwargs)
if formfield: if formfield:
field_list.append((f.name, formfield)) field_list.append((f.name, formfield))
field_dict = SortedDict(field_list) field_dict = SortedDict(field_list)
@ -192,12 +196,13 @@ class ModelFormOptions(object):
self.model = getattr(options, 'model', None) self.model = getattr(options, 'model', None)
self.fields = getattr(options, 'fields', None) self.fields = getattr(options, 'fields', None)
self.exclude = getattr(options, 'exclude', None) self.exclude = getattr(options, 'exclude', None)
self.widgets = getattr(options, 'widgets', None)
class ModelFormMetaclass(type): class ModelFormMetaclass(type):
def __new__(cls, name, bases, attrs): def __new__(cls, name, bases, attrs):
formfield_callback = attrs.pop('formfield_callback', formfield_callback = attrs.pop('formfield_callback',
lambda f: f.formfield()) lambda f, **kwargs: f.formfield(**kwargs))
try: try:
parents = [b for b in bases if issubclass(b, ModelForm)] parents = [b for b in bases if issubclass(b, ModelForm)]
except NameError: except NameError:
@ -215,7 +220,7 @@ class ModelFormMetaclass(type):
if opts.model: if opts.model:
# If a model is defined, extract form fields from it. # If a model is defined, extract form fields from it.
fields = fields_for_model(opts.model, opts.fields, fields = fields_for_model(opts.model, opts.fields,
opts.exclude, formfield_callback) opts.exclude, opts.widgets, formfield_callback)
# Override default model fields with any custom declared ones # Override default model fields with any custom declared ones
# (plus, include all the other declared fields). # (plus, include all the other declared fields).
fields.update(declared_fields) fields.update(declared_fields)

View File

@ -146,7 +146,7 @@ In addition, each generated form field has attributes set as follows:
``default`` value will be initially selected instead). ``default`` value will be initially selected instead).
Finally, note that you can override the form field used for a given model Finally, note that you can override the form field used for a given model
field. See `Overriding the default field types`_ below. field. See `Overriding the default field types or widgets`_ below.
A full example A full example
-------------- --------------
@ -350,31 +350,53 @@ Since the Author model has only 3 fields, 'name', 'title', and
.. _section on saving forms: `The save() method`_ .. _section on saving forms: `The save() method`_
Overriding the default field types Overriding the default field types or widgets
---------------------------------- ---------------------------------------------
The default field types, as described in the `Field types`_ table above, are The default field types, as described in the `Field types`_ table above, are
sensible defaults. If you have a ``DateField`` in your model, chances are you'd sensible defaults. If you have a ``DateField`` in your model, chances are you'd
want that to be represented as a ``DateField`` in your form. But want that to be represented as a ``DateField`` in your form. But
``ModelForm`` gives you the flexibility of changing the form field type ``ModelForm`` gives you the flexibility of changing the form field type and
for a given model field. You do this by declaratively specifying fields like widget for a given model field.
you would in a regular ``Form``. Declared fields will override the default
ones generated by using the ``model`` attribute. To specify a custom widget for a field, use the ``widgets`` attribute of the
inner ``Meta`` class. This should be a dictionary mapping field names to widget
classes or instances.
For example, if you want the a ``CharField`` to be represented by a
``<textarea>`` instead of its default ``<input type="text">``, you can override
the field's widget::
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ['name', 'title', 'birth_date']
widgets = {
'name': Textarea(attrs={'cols': 80, 'rows': 20}),
}
The ``widgets`` dictionary accepts either widget instances (e.g.,
``Textarea(...)``) or classes (e.g., ``Textarea``).
If you want to further customize a field -- including its type, label, etc. --
you can do this by declaratively specifying fields like you would in a regular
``Form``. Declared fields will override the default ones generated by using the
``model`` attribute.
For example, if you wanted to use ``MyDateFormField`` for the ``pub_date`` For example, if you wanted to use ``MyDateFormField`` for the ``pub_date``
field, you could do the following:: field, you could do the following::
>>> class ArticleForm(ModelForm): class ArticleForm(ModelForm):
... pub_date = MyDateFormField() pub_date = MyDateFormField()
...
... class Meta:
... model = Article
If you want to override a field's default widget, then specify the ``widget`` class Meta:
model = Article
If you want to override a field's default label, then specify the ``label``
parameter when declaring the form field:: parameter when declaring the form field::
>>> class ArticleForm(ModelForm): >>> class ArticleForm(ModelForm):
... pub_date = DateField(widget=MyDateWidget()) ... pub_date = DateField(label='Publication date')
... ...
... class Meta: ... class Meta:
... model = Article ... model = Article

View File

@ -287,6 +287,27 @@ Using 'fields' *and* 'exclude'. Not sure why you'd want to do this, but uh,
>>> CategoryForm.base_fields.keys() >>> CategoryForm.base_fields.keys()
['name'] ['name']
Using 'widgets'
>>> class CategoryForm(ModelForm):
...
... class Meta:
... model = Category
... fields = ['name', 'url', 'slug']
... widgets = {
... 'name': forms.Textarea,
... 'url': forms.TextInput(attrs={'class': 'url'})
... }
>>> str(CategoryForm()['name'])
'<textarea id="id_name" rows="10" cols="40" name="name"></textarea>'
>>> str(CategoryForm()['url'])
'<input id="id_url" type="text" class="url" name="url" maxlength="40" />'
>>> str(CategoryForm()['slug'])
'<input id="id_slug" type="text" name="slug" maxlength="20" />'
Don't allow more than one 'model' definition in the inheritance hierarchy. Don't allow more than one 'model' definition in the inheritance hierarchy.
Technically, it would generate a valid form, but the fact that the resulting Technically, it would generate a valid form, but the fact that the resulting
save method won't deal with multiple objects is likely to trip up people not save method won't deal with multiple objects is likely to trip up people not