Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

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
  • Loading branch information...
commit 9bb1fa725154e6d1d0317fa0c48d7c240ad5e2d4 1 parent 06645cb
@adrianholovaty adrianholovaty authored
View
13 django/forms/models.py
@@ -159,7 +159,7 @@ def model_to_dict(instance, fields=None, exclude=None):
data[f.name] = 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
continue
if exclude and f.name in exclude:
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:
field_list.append((f.name, 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))
try:
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).
fields.update(declared_fields)
View
50 docs/topics/forms/modelforms.txt
@@ -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
View
21 tests/modeltests/model_forms/models.py
@@ -287,6 +287,27 @@ def __unicode__(self):
>>> CategoryForm.base_fields.keys()
['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.
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
Please sign in to comment.
Something went wrong with that request. Please try again.