Added docs for form_for_model and form_for_instance, and added a fields argument so it is easy to create forms from a subset of model fields.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@5202 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Russell Keith-Magee 2007-05-12 14:42:46 +00:00
parent ca5e12b4ee
commit 6aa5091d58
3 changed files with 210 additions and 22 deletions

View File

@ -12,17 +12,7 @@ from widgets import Select, SelectMultiple, MultipleHiddenInput
__all__ = ('save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields',
'ModelChoiceField', 'ModelMultipleChoiceField')
def model_save(self, commit=True):
"""
Creates and returns model instance according to self.clean_data.
This method is created for any form_for_model Form.
"""
if self.errors:
raise ValueError("The %s could not be created because the data didn't validate." % self._model._meta.object_name)
return save_instance(self, self._model(), commit)
def save_instance(form, instance, commit=True):
def save_instance(form, instance, fields=None, fail_message='saved', commit=True):
"""
Saves bound Form ``form``'s clean_data into model instance ``instance``.
@ -33,15 +23,19 @@ def save_instance(form, instance, commit=True):
from django.db import models
opts = instance.__class__._meta
if form.errors:
raise ValueError("The %s could not be changed because the data didn't validate." % opts.object_name)
raise ValueError("The %s could not be %s because the data didn't validate." % (opts.object_name, fail_message))
clean_data = form.clean_data
for f in opts.fields:
if not f.editable or isinstance(f, models.AutoField) or not f.name in clean_data:
continue
if fields and f.name not in fields:
continue
setattr(instance, f.name, clean_data[f.name])
if commit:
instance.save()
for f in opts.many_to_many:
if fields and f.name not in fields:
continue
if f.name in clean_data:
setattr(instance, f.attname, clean_data[f.name])
# GOTCHA: If many-to-many data is given and commit=False, the many-to-many
@ -50,13 +44,19 @@ def save_instance(form, instance, commit=True):
# exception in that case.
return instance
def make_instance_save(instance):
"Returns the save() method for a form_for_instance Form."
def make_model_save(model, fields, fail_message):
"Returns the save() method for a Form."
def save(self, commit=True):
return save_instance(self, instance, commit)
return save_instance(self, model(), fields, fail_message, commit)
return save
def make_instance_save(instance, fields, fail_message):
"Returns the save() method for a Form."
def save(self, commit=True):
return save_instance(self, instance, fields, fail_message, commit)
return save
def form_for_model(model, form=BaseForm, formfield_callback=lambda f: f.formfield()):
def form_for_model(model, form=BaseForm, fields=None, formfield_callback=lambda f: f.formfield()):
"""
Returns a Form class for the given Django model class.
@ -71,13 +71,16 @@ def form_for_model(model, form=BaseForm, formfield_callback=lambda f: f.formfiel
for f in opts.fields + opts.many_to_many:
if not f.editable:
continue
if fields and not f.name in fields:
continue
formfield = formfield_callback(f)
if formfield:
field_list.append((f.name, formfield))
fields = SortedDictFromList(field_list)
return type(opts.object_name + 'Form', (form,), {'base_fields': fields, '_model': model, 'save': model_save})
base_fields = SortedDictFromList(field_list)
return type(opts.object_name + 'Form', (form,),
{'base_fields': base_fields, '_model': model, 'save': make_model_save(model, fields, 'created')})
def form_for_instance(instance, form=BaseForm, formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)):
def form_for_instance(instance, form=BaseForm, fields=None, formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)):
"""
Returns a Form class for the given Django model instance.
@ -94,13 +97,15 @@ def form_for_instance(instance, form=BaseForm, formfield_callback=lambda f, **kw
for f in opts.fields + opts.many_to_many:
if not f.editable:
continue
if fields and not f.name in fields:
continue
current_value = f.value_from_object(instance)
formfield = formfield_callback(f, initial=current_value)
if formfield:
field_list.append((f.name, formfield))
fields = SortedDictFromList(field_list)
base_fields = SortedDictFromList(field_list)
return type(opts.object_name + 'InstanceForm', (form,),
{'base_fields': fields, '_model': model, 'save': make_instance_save(instance)})
{'base_fields': base_fields, '_model': model, 'save': make_instance_save(instance, fields, 'changed')})
def form_for_fields(field_list):
"Returns a Form class for the given list of Django database field instances."

View File

@ -870,6 +870,161 @@ custom ``Field`` classes. To do this, just create a subclass of
mentioned above (``required``, ``label``, ``initial``, ``widget``,
``help_text``).
Generating forms for models
===========================
Although you can build customized forms by specifying the fields manually,
in many cases you won't need to. Django provides helper methods to simplify the
common cases of form creation.
``form_for_model()``
--------------------
This method creates a form based upon the definition for a specific model.
``form_for_model()`` examines the model definition, and creates a new form
class that contains a form field for each model field that is defined.
The type of fields produced on the generated form is determined by the type
of the model fields. For example, a ``CharField`` on a model will be
represented with a ``CharField`` on the form. Each ``ManyToManyField``
on the model will be represented with a ``MultipleChoiceField`` on the
form. Each ``ForeignKey`` will be represented with a ``ChoiceField``.
A ``ChoiceField`` is also used for any model field that has a ``choices``
attribute specified.
``form_for_model()`` returns a generated class. This class must then be
instantiated::
# Create the form class
>>> ArticleForm = form_for_model(Article)
# Create an empty form instance
>>> f = ArticleForm()
The form produced by ``form_for_model`` also has a ``save()`` method. Once the
form contains valid data, the ``save()`` method can be used to create a model
instance with the attribute values described on the form::
# Create a form instance populated with POST data
>>> f = ArticleForm(request.POST)
# Save the new instance
>>> new_article = f.save()
Using an alternate base class
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you want to add other methods to the generated form, you can put those
methods onto a base class, and instruct ``form_for_model()`` to use that
base class.
By default, every form produced by ``form_for_model()`` extends
``django.newforms.forms.BaseForm``. However, if you provide a ``forms``
argument to ``form_for_model()``, Django will use that class as the base
for the form it generates::
# Create the new base class:
>>> class MyBase(BaseForm):
... def fiddle(self):
... # Do whatever the method does
# Create the form class with a different base class
>>> ArticleForm = form_for_model(Article, form=MyBase)
# Instantiate the form
>>> f = ArticleForm()
# Use the base class method
>>> f.fiddle()
Putting a subset of fields on the form
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**New in Django development version**
In some cases, you may not want all the model fields to appear on the form.
One option is to set ``editable=False`` on the model field. ``form_for_model()``
will not include any non-editable fields on a generated form instance.
However, if you just want to exclude a field from one specific form, you
can use the ``fields`` argument. If you provide a fields argument to
``form_for_model()``, only the fields named will be included on the form.
For example, if you only want the 'title' and 'pub_date' attributes to be
included on the Article form, you would call::
>>> PartialArticleForm = form_for_model(Article, fields=('title', 'pub_date'))
.. note::
If you specify ``fields`` when creating a form with ``form_for_model()``
make sure that the fields that are *not* specified can provide default
values, or are allowed to have a value of ``None``. If a field isn't
specified on a form, the object created from the form can't provide
a value for that attribute, which will prevent the new instance from
being saved.
Overriding the default field types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Although the form field types generated by ``form_for_model()`` are suitable
for most general purposes, you may have need to override the default field
types on a specific form. In order to do this, ``form_for_model()`` provides
access to the *formfield callback*.
The formfield callback is a function that, when provided with a model field,
returns a form field instance. When constructing a form, ``form_for_model()``
asks the formfield callback to provide form field types. The default
implementation asks the model field for an appropriate field type; however,
any other strategy may be employed. If you need to use an alternate strategy,
you can define your own callback, and provide it to ``form_for_model()`` using
the ``formfield_callback`` argument.
For example, if you wanted to use ``MyDateFormField`` for any ``DateField``
fields on the model, you could define the callback::
>>> def my_fields(field, **kwargs):
... if isinstance(field, models.DateField):
... return MyDateFormField(**kwargs)
... else:
... return field.formfield(**kwargs)
>>> ArticleForm = form_for_model(formfield_callback=my_fields)
Note that your callback needs to handle *all* possible model field types, not
just the ones that you want to behave differently to the default.
Finding the model associated with a form
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The model class that was used to construct the form is available
using the ``_model`` property of the generated form.
``form_for_instance()``
-----------------------
``form_for_instance()`` is very similar to ``form_for_model()``. However,
rather than using a model class to generate a form, it uses an instance of a
model::
# Create an article
>>> art = Article(... some data ...)
>>> art.save()
# Create a form
>>> ArticleForm = form_for_instance(art)
# Instantiate the form
>>> f = ArticleForm()
When a form created by ``form_for_instance()`` is created, the initial
data values for the form fields are drawn from the instance. However,
this data is not bound to the form. You will need to bind data to the
form before the form can be saved.
When you call ``save()`` on a form created by ``form_for_instance()``,
the database instance will be updated.
``form_for_instance()`` has ``form``, ``fields`` and ``formfield_callback``
arguments that behave the same way as they do for ``form_for_model()``.
More coming soon
================

View File

@ -179,6 +179,18 @@ fields with the 'choices' attribute are represented by a ChoiceField.
<option value="3">Third test</option>
</select><br /> Hold down "Control", or "Command" on a Mac, to select more than one.</td></tr>
You can restrict a form to a subset of the complete list of fields
by providing a 'fields' argument. If you try to save a
model created with such a form, you need to ensure that the fields
that are _not_ on the form have default values, or are allowed to have
a value of None. If a field isn't specified on a form, the object created
from the form can't provide a value for that field!
>>> PartialArticleForm = form_for_model(Article, fields=('headline','pub_date'))
>>> f = PartialArticleForm(auto_id=False)
>>> print f
<tr><th>Headline:</th><td><input type="text" name="headline" maxlength="50" /></td></tr>
<tr><th>Pub date:</th><td><input type="text" name="pub_date" /></td></tr>
You can pass a custom Form class to form_for_model. Make sure it's a
subclass of BaseForm, not Form.
>>> class CustomForm(BaseForm):
@ -224,7 +236,23 @@ current values are inserted as 'initial' data in each Field.
<option value="2">It&#39;s a test</option>
<option value="3">Third test</option>
</select> Hold down "Control", or "Command" on a Mac, to select more than one.</li>
>>> f = TestArticleForm({'headline': u'New headline', 'pub_date': u'1988-01-04', 'writer': u'1', 'article': 'Hello.'})
>>> f = TestArticleForm({'headline': u'Test headline', 'pub_date': u'1984-02-06', 'writer': u'1', 'article': 'Hello.'})
>>> f.is_valid()
True
>>> test_art = f.save()
>>> test_art.id
1
>>> test_art = Article.objects.get(id=1)
>>> test_art.headline
'Test headline'
You can create a form over a subset of the available fields
by specifying a 'fields' argument to form_for_instance.
>>> PartialArticleForm = form_for_instance(art, fields=('headline','pub_date'))
>>> f = PartialArticleForm({'headline': u'New headline', 'pub_date': u'1988-01-04'}, auto_id=False)
>>> print f.as_ul()
<li>Headline: <input type="text" name="headline" value="New headline" maxlength="50" /></li>
<li>Pub date: <input type="text" name="pub_date" value="1988-01-04" /></li>
>>> f.is_valid()
True
>>> new_art = f.save()