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:
parent
ca5e12b4ee
commit
6aa5091d58
|
@ -12,17 +12,7 @@ from widgets import Select, SelectMultiple, MultipleHiddenInput
|
||||||
__all__ = ('save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields',
|
__all__ = ('save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields',
|
||||||
'ModelChoiceField', 'ModelMultipleChoiceField')
|
'ModelChoiceField', 'ModelMultipleChoiceField')
|
||||||
|
|
||||||
def model_save(self, commit=True):
|
def save_instance(form, instance, fields=None, fail_message='saved', 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):
|
|
||||||
"""
|
"""
|
||||||
Saves bound Form ``form``'s clean_data into model instance ``instance``.
|
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
|
from django.db import models
|
||||||
opts = instance.__class__._meta
|
opts = instance.__class__._meta
|
||||||
if form.errors:
|
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
|
clean_data = form.clean_data
|
||||||
for f in opts.fields:
|
for f in opts.fields:
|
||||||
if not f.editable or isinstance(f, models.AutoField) or not f.name in clean_data:
|
if not f.editable or isinstance(f, models.AutoField) or not f.name in clean_data:
|
||||||
continue
|
continue
|
||||||
|
if fields and f.name not in fields:
|
||||||
|
continue
|
||||||
setattr(instance, f.name, clean_data[f.name])
|
setattr(instance, f.name, clean_data[f.name])
|
||||||
if commit:
|
if commit:
|
||||||
instance.save()
|
instance.save()
|
||||||
for f in opts.many_to_many:
|
for f in opts.many_to_many:
|
||||||
|
if fields and f.name not in fields:
|
||||||
|
continue
|
||||||
if f.name in clean_data:
|
if f.name in clean_data:
|
||||||
setattr(instance, f.attname, clean_data[f.name])
|
setattr(instance, f.attname, clean_data[f.name])
|
||||||
# GOTCHA: If many-to-many data is given and commit=False, the many-to-many
|
# 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.
|
# exception in that case.
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def make_instance_save(instance):
|
def make_model_save(model, fields, fail_message):
|
||||||
"Returns the save() method for a form_for_instance Form."
|
"Returns the save() method for a Form."
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
return save_instance(self, instance, commit)
|
return save_instance(self, model(), fields, fail_message, commit)
|
||||||
return save
|
return save
|
||||||
|
|
||||||
def form_for_model(model, form=BaseForm, formfield_callback=lambda f: f.formfield()):
|
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, fields=None, formfield_callback=lambda f: f.formfield()):
|
||||||
"""
|
"""
|
||||||
Returns a Form class for the given Django model class.
|
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:
|
for f in opts.fields + opts.many_to_many:
|
||||||
if not f.editable:
|
if not f.editable:
|
||||||
continue
|
continue
|
||||||
|
if fields and not f.name in fields:
|
||||||
|
continue
|
||||||
formfield = formfield_callback(f)
|
formfield = formfield_callback(f)
|
||||||
if formfield:
|
if formfield:
|
||||||
field_list.append((f.name, formfield))
|
field_list.append((f.name, formfield))
|
||||||
fields = SortedDictFromList(field_list)
|
base_fields = SortedDictFromList(field_list)
|
||||||
return type(opts.object_name + 'Form', (form,), {'base_fields': fields, '_model': model, 'save': model_save})
|
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.
|
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:
|
for f in opts.fields + opts.many_to_many:
|
||||||
if not f.editable:
|
if not f.editable:
|
||||||
continue
|
continue
|
||||||
|
if fields and not f.name in fields:
|
||||||
|
continue
|
||||||
current_value = f.value_from_object(instance)
|
current_value = f.value_from_object(instance)
|
||||||
formfield = formfield_callback(f, initial=current_value)
|
formfield = formfield_callback(f, initial=current_value)
|
||||||
if formfield:
|
if formfield:
|
||||||
field_list.append((f.name, formfield))
|
field_list.append((f.name, formfield))
|
||||||
fields = SortedDictFromList(field_list)
|
base_fields = SortedDictFromList(field_list)
|
||||||
return type(opts.object_name + 'InstanceForm', (form,),
|
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):
|
def form_for_fields(field_list):
|
||||||
"Returns a Form class for the given list of Django database field instances."
|
"Returns a Form class for the given list of Django database field instances."
|
||||||
|
|
|
@ -870,6 +870,161 @@ custom ``Field`` classes. To do this, just create a subclass of
|
||||||
mentioned above (``required``, ``label``, ``initial``, ``widget``,
|
mentioned above (``required``, ``label``, ``initial``, ``widget``,
|
||||||
``help_text``).
|
``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
|
More coming soon
|
||||||
================
|
================
|
||||||
|
|
||||||
|
|
|
@ -179,6 +179,18 @@ fields with the 'choices' attribute are represented by a ChoiceField.
|
||||||
<option value="3">Third test</option>
|
<option value="3">Third test</option>
|
||||||
</select><br /> Hold down "Control", or "Command" on a Mac, to select more than one.</td></tr>
|
</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
|
You can pass a custom Form class to form_for_model. Make sure it's a
|
||||||
subclass of BaseForm, not Form.
|
subclass of BaseForm, not Form.
|
||||||
>>> class CustomForm(BaseForm):
|
>>> class CustomForm(BaseForm):
|
||||||
|
@ -224,7 +236,23 @@ current values are inserted as 'initial' data in each Field.
|
||||||
<option value="2">It's a test</option>
|
<option value="2">It's a test</option>
|
||||||
<option value="3">Third test</option>
|
<option value="3">Third test</option>
|
||||||
</select> Hold down "Control", or "Command" on a Mac, to select more than one.</li>
|
</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()
|
>>> f.is_valid()
|
||||||
True
|
True
|
||||||
>>> new_art = f.save()
|
>>> new_art = f.save()
|
||||||
|
|
Loading…
Reference in New Issue