Browse files

Fixed #9223 -- Added support for declarative widgets to ModelForm. I …

…declare thanks to isagalaev.

git-svn-id: bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
1 parent 06645cb commit 9bb1fa725154e6d1d0317fa0c48d7c240ad5e2d4 @adrianholovaty adrianholovaty committed Jan 10, 2010
Showing with 66 additions and 18 deletions.
  1. +9 −4 django/forms/
  2. +36 −14 docs/topics/forms/modelforms.txt
  3. +21 −0 tests/modeltests/model_forms/
@@ -159,7 +159,7 @@ def model_to_dict(instance, fields=None, exclude=None):
data[] = f.value_from_object(instance)
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.
@@ -179,7 +179,11 @@ def fields_for_model(model, fields=None, exclude=None, formfield_callback=lambda
if exclude and in exclude:
- formfield = formfield_callback(f)
+ if widgets and in widgets:
+ kwargs = {'widget': widgets[]}
+ else:
+ kwargs = {}
+ formfield = formfield_callback(f, **kwargs)
if formfield:
field_list.append((, formfield))
field_dict = SortedDict(field_list)
@@ -192,12 +196,13 @@ def __init__(self, options=None):
self.model = getattr(options, 'model', None)
self.fields = getattr(options, 'fields', None)
self.exclude = getattr(options, 'exclude', None)
+ self.widgets = getattr(options, 'widgets', None)
class ModelFormMetaclass(type):
def __new__(cls, name, bases, attrs):
formfield_callback = attrs.pop('formfield_callback',
- lambda f: f.formfield())
+ lambda f, **kwargs: f.formfield(**kwargs))
parents = [b for b in bases if issubclass(b, ModelForm)]
except NameError:
@@ -215,7 +220,7 @@ def __new__(cls, name, bases, attrs):
if opts.model:
# If a model is defined, extract form fields from it.
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
# (plus, include all the other declared fields).
@@ -146,7 +146,7 @@ In addition, each generated form field has attributes set as follows:
``default`` value will be initially selected instead).
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
@@ -350,31 +350,53 @@ Since the Author model has only 3 fields, 'name', 'title', and
.. _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
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
-``ModelForm`` gives you the flexibility of changing the form field type
-for a given model field. You 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.
+``ModelForm`` gives you the flexibility of changing the form field type and
+widget for a given model field.
+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``
field, you could do the following::
- >>> class ArticleForm(ModelForm):
- ... pub_date = MyDateFormField()
- ...
- ... class Meta:
- ... model = Article
+ class ArticleForm(ModelForm):
+ pub_date = MyDateFormField()
+ class Meta:
+ model = Article
-If you want to override a field's default widget, then specify the ``widget``
+If you want to override a field's default label, then specify the ``label``
parameter when declaring the form field::
>>> class ArticleForm(ModelForm):
- ... pub_date = DateField(widget=MyDateWidget())
+ ... pub_date = DateField(label='Publication date')
... class Meta:
... model = Article
@@ -287,6 +287,27 @@ def __unicode__(self):
>>> CategoryForm.base_fields.keys()
+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.
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

0 comments on commit 9bb1fa7

Please sign in to comment.