Fixed #6042 -- ModelForms implementation from Joseph Kocherhans. Still might

need a little tweaking as people start to use it, but this is mostly complete.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@6844 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Malcolm Tredinnick 2007-12-02 19:29:54 +00:00
parent 61947f0635
commit 51dc4ecf94
5 changed files with 1059 additions and 559 deletions

View File

@ -6,13 +6,15 @@ and database field objects.
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_unicode from django.utils.encoding import smart_unicode
from django.utils.datastructures import SortedDict from django.utils.datastructures import SortedDict
from django.core.exceptions import ImproperlyConfigured
from util import ValidationError from util import ValidationError, ErrorList
from forms import BaseForm from forms import BaseForm
from fields import Field, ChoiceField, EMPTY_VALUES from fields import Field, ChoiceField, EMPTY_VALUES
from widgets import Select, SelectMultiple, MultipleHiddenInput from widgets import Select, SelectMultiple, MultipleHiddenInput
__all__ = ( __all__ = (
'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model',
'save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields', 'save_instance', 'form_for_model', 'form_for_instance', 'form_for_fields',
'ModelChoiceField', 'ModelMultipleChoiceField' 'ModelChoiceField', 'ModelMultipleChoiceField'
) )
@ -132,6 +134,155 @@ def form_for_fields(field_list):
for f in field_list if f.editable]) for f in field_list if f.editable])
return type('FormForFields', (BaseForm,), {'base_fields': fields}) return type('FormForFields', (BaseForm,), {'base_fields': fields})
# ModelForms #################################################################
def model_to_dict(instance, fields=None, exclude=None):
"""
Returns a dict containing the data in ``instance`` suitable for passing as
a Form's ``initial`` keyword argument.
``fields`` is an optional list of field names. If provided, only the named
fields will be included in the returned dict.
``exclude`` is an optional list of field names. If provided, the named
fields will be excluded from the returned dict, even if they are listed in
the ``fields`` argument.
"""
# avoid a circular import
from django.db.models.fields.related import ManyToManyField
opts = instance._meta
data = {}
for f in opts.fields + opts.many_to_many:
if not f.editable:
continue
if fields and not f.name in fields:
continue
if exclude and f.name in exclude:
continue
if isinstance(f, ManyToManyField):
# If the object doesn't have a primry key yet, just use an empty
# list for its m2m fields. Calling f.value_from_object will raise
# an exception.
if instance.pk is None:
data[f.name] = []
else:
# MultipleChoiceWidget needs a list of pks, not object instances.
data[f.name] = [obj.pk for obj in f.value_from_object(instance)]
else:
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()):
"""
Returns a ``SortedDict`` containing form fields for the given model.
``fields`` is an optional list of field names. If provided, only the named
fields will be included in the returned fields.
``exclude`` is an optional list of field names. If provided, the named
fields will be excluded from the returned fields, even if they are listed
in the ``fields`` argument.
"""
# TODO: if fields is provided, it would be nice to return fields in that order
field_list = []
opts = model._meta
for f in opts.fields + opts.many_to_many:
if not f.editable:
continue
if fields and not f.name in fields:
continue
if exclude and f.name in exclude:
continue
formfield = formfield_callback(f)
if formfield:
field_list.append((f.name, formfield))
return SortedDict(field_list)
class ModelFormOptions(object):
def __init__(self, options=None):
self.model = getattr(options, 'model', None)
self.fields = getattr(options, 'fields', None)
self.exclude = getattr(options, 'exclude', None)
class ModelFormMetaclass(type):
def __new__(cls, name, bases, attrs):
# TODO: no way to specify formfield_callback yet, do we need one, or
# should it be a special case for the admin?
fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
# If this class is subclassing another Form, add that Form's fields.
# Note that we loop over the bases in *reverse*. This is necessary in
# order to preserve the correct order of fields.
for base in bases[::-1]:
if hasattr(base, 'base_fields'):
fields = base.base_fields.items() + fields
declared_fields = SortedDict(fields)
opts = ModelFormOptions(attrs.get('Meta', None))
attrs['_meta'] = opts
# Don't allow more than one Meta model defenition in bases. The fields
# would be generated correctly, but the save method won't deal with
# more than one object.
base_models = []
for base in bases:
base_opts = getattr(base, '_meta', None)
base_model = getattr(base_opts, 'model', None)
if base_model is not None:
base_models.append(base_model)
if len(base_models) > 1:
raise ImproperlyConfigured("%s's base classes define more than one model." % name)
# If a model is defined, extract form fields from it and add them to base_fields
if attrs['_meta'].model is not None:
# Don't allow a subclass to define a Meta model if a parent class has.
# Technically the right fields would be generated, but the save
# method will not deal with more than one model.
for base in bases:
base_opts = getattr(base, '_meta', None)
base_model = getattr(base_opts, 'model', None)
if base_model is not None:
raise ImproperlyConfigured('%s defines more than one model.' % name)
model_fields = fields_for_model(opts.model, opts.fields, opts.exclude)
# fields declared in base classes override fields from the model
model_fields.update(declared_fields)
attrs['base_fields'] = model_fields
else:
attrs['base_fields'] = declared_fields
return type.__new__(cls, name, bases, attrs)
class BaseModelForm(BaseForm):
def __init__(self, instance, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList, label_suffix=':'):
self.instance = instance
opts = self._meta
object_data = model_to_dict(instance, opts.fields, opts.exclude)
# if initial was provided, it should override the values from instance
if initial is not None:
object_data.update(initial)
BaseForm.__init__(self, data, files, auto_id, prefix, object_data, error_class, label_suffix)
def save(self, commit=True):
"""
Saves this ``form``'s cleaned_data into model instance ``self.instance``.
If commit=True, then the changes to ``instance`` will be saved to the
database. Returns ``instance``.
"""
if self.instance.pk is None:
fail_message = 'created'
else:
fail_message = 'changed'
return save_instance(self, self.instance, self._meta.fields, fail_message, commit)
class ModelForm(BaseModelForm):
__metaclass__ = ModelFormMetaclass
# Fields #####################################################################
class QuerySetIterator(object): class QuerySetIterator(object):
def __init__(self, queryset, empty_label, cache_choices): def __init__(self, queryset, empty_label, cache_choices):
self.queryset = queryset self.queryset = queryset
@ -142,7 +293,7 @@ class QuerySetIterator(object):
if self.empty_label is not None: if self.empty_label is not None:
yield (u"", self.empty_label) yield (u"", self.empty_label)
for obj in self.queryset: for obj in self.queryset:
yield (obj._get_pk_val(), smart_unicode(obj)) yield (obj.pk, smart_unicode(obj))
# Clear the QuerySet cache if required. # Clear the QuerySet cache if required.
if not self.cache_choices: if not self.cache_choices:
self.queryset._result_cache = None self.queryset._result_cache = None

418
docs/form_for_model.txt Normal file
View File

@ -0,0 +1,418 @@
Generating forms for models
===========================
If you're building a database-driven app, chances are you'll have forms that
map closely to Django models. For instance, you might have a ``BlogComment``
model, and you want to create a form that lets people submit comments. In this
case, it would be redundant to define the field types in your form, because
you've already defined the fields in your model.
For this reason, Django provides a few helper functions that let you create a
``Form`` class from a Django model.
``form_for_model()``
--------------------
The method ``django.newforms.form_for_model()`` creates a form based on the
definition of a specific model. Pass it the model class, and it will return a
``Form`` class that contains a form field for each model field.
For example::
>>> from django.newforms import form_for_model
# Create the form class.
>>> ArticleForm = form_for_model(Article)
# Create an empty form instance.
>>> f = ArticleForm()
It bears repeating that ``form_for_model()`` takes the model *class*, not a
model instance, and it returns a ``Form`` *class*, not a ``Form`` instance.
Field types
~~~~~~~~~~~
The generated ``Form`` class will have a form field for every model field. Each
model field has a corresponding default form field. For example, a
``CharField`` on a model is represented as a ``CharField`` on a form. A
model ``ManyToManyField`` is represented as a ``MultipleChoiceField``. Here is
the full list of conversions:
=============================== ========================================
Model field Form field
=============================== ========================================
``AutoField`` Not represented in the form
``BooleanField`` ``BooleanField``
``CharField`` ``CharField`` with ``max_length`` set to
the model field's ``max_length``
``CommaSeparatedIntegerField`` ``CharField``
``DateField`` ``DateField``
``DateTimeField`` ``DateTimeField``
``DecimalField`` ``DecimalField``
``EmailField`` ``EmailField``
``FileField`` ``FileField``
``FilePathField`` ``CharField``
``FloatField`` ``FloatField``
``ForeignKey`` ``ModelChoiceField`` (see below)
``ImageField`` ``ImageField``
``IntegerField`` ``IntegerField``
``IPAddressField`` ``IPAddressField``
``ManyToManyField`` ``ModelMultipleChoiceField`` (see
below)
``NullBooleanField`` ``CharField``
``PhoneNumberField`` ``USPhoneNumberField``
(from ``django.contrib.localflavor.us``)
``PositiveIntegerField`` ``IntegerField``
``PositiveSmallIntegerField`` ``IntegerField``
``SlugField`` ``CharField``
``SmallIntegerField`` ``IntegerField``
``TextField`` ``CharField`` with ``widget=Textarea``
``TimeField`` ``TimeField``
``URLField`` ``URLField`` with ``verify_exists`` set
to the model field's ``verify_exists``
``USStateField`` ``CharField`` with
``widget=USStateSelect``
(``USStateSelect`` is from
``django.contrib.localflavor.us``)
``XMLField`` ``CharField`` with ``widget=Textarea``
=============================== ========================================
.. note::
The ``FloatField`` form field and ``DecimalField`` model and form fields
are new in the development version.
As you might expect, the ``ForeignKey`` and ``ManyToManyField`` model field
types are special cases:
* ``ForeignKey`` is represented by ``django.newforms.ModelChoiceField``,
which is a ``ChoiceField`` whose choices are a model ``QuerySet``.
* ``ManyToManyField`` is represented by
``django.newforms.ModelMultipleChoiceField``, which is a
``MultipleChoiceField`` whose choices are a model ``QuerySet``.
In addition, each generated form field has attributes set as follows:
* If the model field has ``blank=True``, then ``required`` is set to
``False`` on the form field. Otherwise, ``required=True``.
* The form field's ``label`` is set to the ``verbose_name`` of the model
field, with the first character capitalized.
* The form field's ``help_text`` is set to the ``help_text`` of the model
field.
* If the model field has ``choices`` set, then the form field's ``widget``
will be set to ``Select``, with choices coming from the model field's
``choices``. The choices will normally include the blank choice which is
selected by default. If the field is required, this forces the user to
make a selection. The blank choice will not be included if the model
field has ``blank=False`` and an explicit ``default`` value (the
``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.
A full example
~~~~~~~~~~~~~~
Consider this set of models::
from django.db import models
TITLE_CHOICES = (
('MR', 'Mr.'),
('MRS', 'Mrs.'),
('MS', 'Ms.'),
)
class Author(models.Model):
name = models.CharField(max_length=100)
title = models.CharField(max_length=3, choices=TITLE_CHOICES)
birth_date = models.DateField(blank=True, null=True)
def __unicode__(self):
return self.name
class Book(models.Model):
name = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
With these models, a call to ``form_for_model(Author)`` would return a ``Form``
class equivalent to this::
class AuthorForm(forms.Form):
name = forms.CharField(max_length=100)
title = forms.CharField(max_length=3,
widget=forms.Select(choices=TITLE_CHOICES))
birth_date = forms.DateField(required=False)
A call to ``form_for_model(Book)`` would return a ``Form`` class equivalent to
this::
class BookForm(forms.Form):
name = forms.CharField(max_length=100)
authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())
The ``save()`` method
~~~~~~~~~~~~~~~~~~~~~
Every form produced by ``form_for_model()`` also has a ``save()`` method. This
method creates and saves a database object from the data bound to the form. For
example::
# Create a form instance from POST data.
>>> f = ArticleForm(request.POST)
# Save a new Article object from the form's data.
>>> new_article = f.save()
Note that ``save()`` will raise a ``ValueError`` if the data in the form
doesn't validate -- i.e., ``if form.errors``.
This ``save()`` method accepts an optional ``commit`` keyword argument, which
accepts either ``True`` or ``False``. If you call ``save()`` with
``commit=False``, then it will return an object that hasn't yet been saved to
the database. In this case, it's up to you to call ``save()`` on the resulting
model instance. This is useful if you want to do custom processing on the
object before saving it. ``commit`` is ``True`` by default.
Another side effect of using ``commit=False`` is seen when your model has
a many-to-many relation with another model. If your model has a many-to-many
relation and you specify ``commit=False`` when you save a form, Django cannot
immediately save the form data for the many-to-many relation. This is because
it isn't possible to save many-to-many data for an instance until the instance
exists in the database.
To work around this problem, every time you save a form using ``commit=False``,
Django adds a ``save_m2m()`` method to the form created by ``form_for_model``.
After you've manually saved the instance produced by the form, you can invoke
``save_m2m()`` to save the many-to-many form data. For example::
# Create a form instance with POST data.
>>> f = AuthorForm(request.POST)
# Create, but don't save the new author instance.
>>> new_author = f.save(commit=False)
# Modify the author in some way.
>>> new_author.some_field = 'some_value'
# Save the new instance.
>>> new_author.save()
# Now, save the many-to-many data for the form.
>>> f.save_m2m()
Calling ``save_m2m()`` is only required if you use ``save(commit=False)``.
When you use a simple ``save()`` on a form, all data -- including
many-to-many data -- is saved without the need for any additional method calls.
For example::
# Create a form instance with POST data.
>>> f = AuthorForm(request.POST)
# Create and save the new author instance. There's no need to do anything else.
>>> new_author = f.save()
Using an alternate base class
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you want to add custom methods to the form generated by
``form_for_model()``, write a class that extends ``django.newforms.BaseForm``
and contains your custom methods. Then, use the ``form`` argument to
``form_for_model()`` to tell it to use your custom form as its base class.
For example::
# Create the new base class.
>>> class MyBase(BaseForm):
... def my_method(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.my_method()
Using 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 generated
form. There are two ways of telling ``form_for_model()`` to use only a subset
of the model fields:
1. Set ``editable=False`` on the model field. As a result, *any* form
created from the model via ``form_for_model()`` will not include that
field.
2. Use the ``fields`` argument to ``form_for_model()``. This argument, if
given, should be a list of field names to include in the form.
For example, if you want a form for the ``Author`` model (defined above)
that includes only the ``name`` and ``title`` fields, you would specify
``fields`` like this::
PartialArticleForm = form_for_model(Author, fields=('name', 'title'))
.. note::
If you specify ``fields`` when creating a form with ``form_for_model()``,
then the fields that are *not* specified will not be set by the form's
``save()`` method. Django will prevent any attempt to save an incomplete
model, so if the model does not allow the missing fields to be empty, and
does not provide a default value for the missing fields, any attempt to
``save()`` a ``form_for_model`` with missing fields will fail. To avoid
this failure, you must use ``save(commit=False)`` and manually set any
extra required fields::
instance = form.save(commit=False)
instance.required_field = 'new value'
instance.save()
See the `section on saving forms`_ for more details on using
``save(commit=False)``.
.. _section on saving forms: `The save() method`_
Overriding the default field types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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
``form_for_model()`` gives you the flexibility of changing the form field type
for a given model field. You do this by specifying a **formfield callback**.
A 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.
By default, ``form_for_model()`` calls the ``formfield()`` method on the model
field::
def default_callback(field, **kwargs):
return field.formfield(**kwargs)
The ``kwargs`` are any keyword arguments that might be passed to the form
field, such as ``required=True`` or ``label='Foo'``.
For example, if you wanted to use ``MyDateFormField`` for any ``DateField``
field on the model, you could define the callback::
>>> def my_callback(field, **kwargs):
... if isinstance(field, models.DateField):
... return MyDateFormField(**kwargs)
... else:
... return field.formfield(**kwargs)
>>> ArticleForm = form_for_model(Article, formfield_callback=my_callback)
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. That's why
this example has an ``else`` clause that implements the default behavior.
.. warning::
The field that is passed into the ``formfield_callback`` function in
``form_for_model()`` and ``form_for_instance`` is the field instance from
your model's class. You **must not** alter that object at all; treat it
as read-only!
If you make any alterations to that object, it will affect any future
users of the model class, because you will have changed the field object
used to construct the class. This is almost certainly what you don't want
to have happen.
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::
>>> ArticleForm = form_for_model(Article)
>>> ArticleForm._model
<class 'myapp.models.Article'>
``form_for_instance()``
-----------------------
``form_for_instance()`` is like ``form_for_model()``, but it takes a model
instance instead of a model class::
# Create an Author.
>>> a = Author(name='Joe Smith', title='MR', birth_date=None)
>>> a.save()
# Create a form for this particular Author.
>>> AuthorForm = form_for_instance(a)
# Instantiate the form.
>>> f = AuthorForm()
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.
Unlike ``form_for_model()``, a choice field in form created by
``form_for_instance()`` will not include the blank choice if the respective
model field has ``blank=False``. The initial choice is drawn from the instance.
When you call ``save()`` on a form created by ``form_for_instance()``,
the database instance will be updated. As in ``form_for_model()``, ``save()``
will raise ``ValueError`` if the data doesn't validate.
``form_for_instance()`` has ``form``, ``fields`` and ``formfield_callback``
arguments that behave the same way as they do for ``form_for_model()``.
Let's modify the earlier `contact form`_ view example a little bit. Suppose we
have a ``Message`` model that holds each contact submission. Something like::
class Message(models.Model):
subject = models.CharField(max_length=100)
message = models.TextField()
sender = models.EmailField()
cc_myself = models.BooleanField(required=False)
You could use this model to create a form (using ``form_for_model()``). You
could also use existing ``Message`` instances to create a form for editing
messages. The `simple example view`_ can be changed slightly to accept the ``id`` value
of an existing ``Message`` and present it for editing::
def contact_edit(request, msg_id):
# Create the form from the message id.
message = get_object_or_404(Message, id=msg_id)
ContactForm = form_for_instance(message)
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('/url/on_success/')
else:
form = ContactForm()
return render_to_response('contact.html', {'form': form})
Aside from how we create the ``ContactForm`` class here, the main point to
note is that the form display in the ``GET`` branch of the function
will use the values from the ``message`` instance as initial values for the
form field.
.. _contact form: ../newforms/#simple-view-example
.. _`simple example view`: ../newforms/#simple-view-example
When should you use ``form_for_model()`` and ``form_for_instance()``?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``form_for_model()`` and ``form_for_instance()`` functions are meant to be
shortcuts for the common case. If you want to create a form whose fields map to
more than one model, or a form that contains fields that *aren't* on a model,
you shouldn't use these shortcuts. Creating a ``Form`` class the "long" way
isn't that difficult, after all.

310
docs/modelforms.txt Normal file
View File

@ -0,0 +1,310 @@
==========================
Using newforms with models
==========================
``ModelForm``
=============
If you're building a database-driven app, chances are you'll have forms that
map closely to Django models. For instance, you might have a ``BlogComment``
model, and you want to create a form that lets people submit comments. In this
case, it would be redundant to define the field types in your form, because
you've already defined the fields in your model.
For this reason, Django provides a helper class that let you create a ``Form``
class from a Django model.
For example::
>>> from django.newforms import ModelForm
# Create the form class.
>>> class ArticleForm(ModelForm):
... class Meta:
... model = Article
# Creating a form to add an article.
>>> article\ = Article()
>>> form = ArticleForm(article)
# Creating a form to change an existing article.
>>> article = Article.objects.get(pk=1)
>>> form = ArticleForm(article)
Field types
-----------
The generated ``Form`` class will have a form field for every model field. Each
model field has a corresponding default form field. For example, a
``CharField`` on a model is represented as a ``CharField`` on a form. A
model ``ManyToManyField`` is represented as a ``MultipleChoiceField``. Here is
the full list of conversions:
=============================== ========================================
Model field Form field
=============================== ========================================
``AutoField`` Not represented in the form
``BooleanField`` ``BooleanField``
``CharField`` ``CharField`` with ``max_length`` set to
the model field's ``max_length``
``CommaSeparatedIntegerField`` ``CharField``
``DateField`` ``DateField``
``DateTimeField`` ``DateTimeField``
``DecimalField`` ``DecimalField``
``EmailField`` ``EmailField``
``FileField`` ``FileField``
``FilePathField`` ``CharField``
``FloatField`` ``FloatField``
``ForeignKey`` ``ModelChoiceField`` (see below)
``ImageField`` ``ImageField``
``IntegerField`` ``IntegerField``
``IPAddressField`` ``IPAddressField``
``ManyToManyField`` ``ModelMultipleChoiceField`` (see
below)
``NullBooleanField`` ``CharField``
``PhoneNumberField`` ``USPhoneNumberField``
(from ``django.contrib.localflavor.us``)
``PositiveIntegerField`` ``IntegerField``
``PositiveSmallIntegerField`` ``IntegerField``
``SlugField`` ``CharField``
``SmallIntegerField`` ``IntegerField``
``TextField`` ``CharField`` with ``widget=Textarea``
``TimeField`` ``TimeField``
``URLField`` ``URLField`` with ``verify_exists`` set
to the model field's ``verify_exists``
``USStateField`` ``CharField`` with
``widget=USStateSelect``
(``USStateSelect`` is from
``django.contrib.localflavor.us``)
``XMLField`` ``CharField`` with ``widget=Textarea``
=============================== ========================================
.. note::
The ``FloatField`` form field and ``DecimalField`` model and form fields
are new in the development version.
As you might expect, the ``ForeignKey`` and ``ManyToManyField`` model field
types are special cases:
* ``ForeignKey`` is represented by ``django.newforms.ModelChoiceField``,
which is a ``ChoiceField`` whose choices are a model ``QuerySet``.
* ``ManyToManyField`` is represented by
``django.newforms.ModelMultipleChoiceField``, which is a
``MultipleChoiceField`` whose choices are a model ``QuerySet``.
In addition, each generated form field has attributes set as follows:
* If the model field has ``blank=True``, then ``required`` is set to
``False`` on the form field. Otherwise, ``required=True``.
* The form field's ``label`` is set to the ``verbose_name`` of the model
field, with the first character capitalized.
* The form field's ``help_text`` is set to the ``help_text`` of the model
field.
* If the model field has ``choices`` set, then the form field's ``widget``
will be set to ``Select``, with choices coming from the model field's
``choices``. The choices will normally include the blank choice which is
selected by default. If the field is required, this forces the user to
make a selection. The blank choice will not be included if the model
field has ``blank=False`` and an explicit ``default`` value (the
``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.
A full example
--------------
Consider this set of models::
from django.db import models
TITLE_CHOICES = (
('MR', 'Mr.'),
('MRS', 'Mrs.'),
('MS', 'Ms.'),
)
class Author(models.Model):
name = models.CharField(max_length=100)
title = models.CharField(max_length=3, choices=TITLE_CHOICES)
birth_date = models.DateField(blank=True, null=True)
def __unicode__(self):
return self.name
class Book(models.Model):
name = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
class AuthorForm(ModelForm):
class Meta:
model = Author
class BookForm(ModelForm):
class Meta:
model = Book
With these models, the ``ModelForm`` subclasses above would be roughly
equivalent to this (the only difference being the ``save()`` method, which
we'll discuss in a moment.)::
class AuthorForm(forms.Form):
name = forms.CharField(max_length=100)
title = forms.CharField(max_length=3,
widget=forms.Select(choices=TITLE_CHOICES))
birth_date = forms.DateField(required=False)
class BookForm(forms.Form):
name = forms.CharField(max_length=100)
authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())
The ``save()`` method
---------------------
Every form produced by ``ModelForm`` also has a ``save()`` method. This
method creates and saves a database object from the data bound to the form.
A subclass of ``ModelForm`` also requires a model instance as the first
arument to its constructor. For example::
# Create a form instance from POST data.
>>> a = Article()
>>> f = ArticleForm(a, request.POST)
# Save a new Article object from the form's data.
>>> new_article = f.save()
Note that ``save()`` will raise a ``ValueError`` if the data in the form
doesn't validate -- i.e., ``if form.errors``.
This ``save()`` method accepts an optional ``commit`` keyword argument, which
accepts either ``True`` or ``False``. If you call ``save()`` with
``commit=False``, then it will return an object that hasn't yet been saved to
the database. In this case, it's up to you to call ``save()`` on the resulting
model instance. This is useful if you want to do custom processing on the
object before saving it. ``commit`` is ``True`` by default.
Another side effect of using ``commit=False`` is seen when your model has
a many-to-many relation with another model. If your model has a many-to-many
relation and you specify ``commit=False`` when you save a form, Django cannot
immediately save the form data for the many-to-many relation. This is because
it isn't possible to save many-to-many data for an instance until the instance
exists in the database.
To work around this problem, every time you save a form using ``commit=False``,
Django adds a ``save_m2m()`` method to your ``ModelForm`` subclass. After
you've manually saved the instance produced by the form, you can invoke
``save_m2m()`` to save the many-to-many form data. For example::
# Create a form instance with POST data.
>>> a = Author()
>>> f = AuthorForm(a, request.POST)
# Create, but don't save the new author instance.
>>> new_author = f.save(commit=False)
# Modify the author in some way.
>>> new_author.some_field = 'some_value'
# Save the new instance.
>>> new_author.save()
# Now, save the many-to-many data for the form.
>>> f.save_m2m()
Calling ``save_m2m()`` is only required if you use ``save(commit=False)``.
When you use a simple ``save()`` on a form, all data -- including
many-to-many data -- is saved without the need for any additional method calls.
For example::
# Create a form instance with POST data.
>>> a = Author()
>>> f = AuthorForm(a, request.POST)
# Create and save the new author instance. There's no need to do anything else.
>>> new_author = f.save()
Using a subset of fields on the form
------------------------------------
In some cases, you may not want all the model fields to appear on the generated
form. There are three ways of telling ``ModelForm`` to use only a subset of the
model fields:
1. Set ``editable=False`` on the model field. As a result, *any* form
created from the model via ``ModelForm`` will not include that
field.
2. Use the ``fields`` attribute of the ``ModelForm``'s inner ``Meta`` class.
This attribute, if given, should be a list of field names to include in
the form.
3. Use the ``exclude`` attribute of the ``ModelForm``'s inner ``Meta`` class.
This attribute, if given, should be a list of field names to exclude
the form.
For example, if you want a form for the ``Author`` model (defined above)
that includes only the ``name`` and ``title`` fields, you would specify
``fields`` or ``exclude`` like this::
class PartialAuthorForm(ModelForm):
class Meta:
model = Author
fields = ('name', 'title')
class PartialAuthorForm(ModelForm):
class Meta:
model = Author
exclude = ('birth_date',)
Since the Author model has only 3 fields, 'name', 'title', and
'birth_date', the forms above will contain exactly the same fields.
.. note::
If you specify ``fields`` or ``exclude`` when creating a form with
``ModelForm``, then the fields that are not in the resulting form will not
be set by the form's ``save()`` method. Django will prevent any attempt to
save an incomplete model, so if the model does not allow the missing fields
to be empty, and does not provide a default value for the missing fields,
any attempt to ``save()`` a ``ModelForm`` with missing fields will fail.
To avoid this failure, you must instantiate your model with initial values
for the missing, but required fields, or use ``save(commit=False)`` and
manually set anyextra required fields::
instance = Instance(requiured_field='value')
form = InstanceForm(instance, request.POST)
new_instance = form.save()
instance = form.save(commit=False)
instance.required_field = 'new value'
new_instance = instance.save()
See the `section on saving forms`_ for more details on using
``save(commit=False)``.
.. _section on saving forms: `The save() method`_
Overriding the default field types
----------------------------------
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.
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

View File

@ -1770,423 +1770,14 @@ You can then use this field whenever you have a form that requires a comment::
Generating forms for models Generating forms for models
=========================== ===========================
If you're building a database-driven app, chances are you'll have forms that The prefered way of generating forms that work with models is explained in the
map closely to Django models. For instance, you might have a ``BlogComment`` `ModelForms documentation`_.
model, and you want to create a form that lets people submit comments. In this
case, it would be redundant to define the field types in your form, because
you've already defined the fields in your model.
For this reason, Django provides a few helper functions that let you create a Looking for the ``form_for_model`` and ``form_for_instance`` documentation?
``Form`` class from a Django model. They've been deprecated, but you can still `view the documentation`_.
``form_for_model()`` .. _ModelForms documentation: ../modelforms/
-------------------- .. _view the documentation: ../form_for_model/
The method ``django.newforms.form_for_model()`` creates a form based on the
definition of a specific model. Pass it the model class, and it will return a
``Form`` class that contains a form field for each model field.
For example::
>>> from django.newforms import form_for_model
# Create the form class.
>>> ArticleForm = form_for_model(Article)
# Create an empty form instance.
>>> f = ArticleForm()
It bears repeating that ``form_for_model()`` takes the model *class*, not a
model instance, and it returns a ``Form`` *class*, not a ``Form`` instance.
Field types
~~~~~~~~~~~
The generated ``Form`` class will have a form field for every model field. Each
model field has a corresponding default form field. For example, a
``CharField`` on a model is represented as a ``CharField`` on a form. A
model ``ManyToManyField`` is represented as a ``MultipleChoiceField``. Here is
the full list of conversions:
=============================== ========================================
Model field Form field
=============================== ========================================
``AutoField`` Not represented in the form
``BooleanField`` ``BooleanField``
``CharField`` ``CharField`` with ``max_length`` set to
the model field's ``max_length``
``CommaSeparatedIntegerField`` ``CharField``
``DateField`` ``DateField``
``DateTimeField`` ``DateTimeField``
``DecimalField`` ``DecimalField``
``EmailField`` ``EmailField``
``FileField`` ``FileField``
``FilePathField`` ``CharField``
``FloatField`` ``FloatField``
``ForeignKey`` ``ModelChoiceField`` (see below)
``ImageField`` ``ImageField``
``IntegerField`` ``IntegerField``
``IPAddressField`` ``IPAddressField``
``ManyToManyField`` ``ModelMultipleChoiceField`` (see
below)
``NullBooleanField`` ``CharField``
``PhoneNumberField`` ``USPhoneNumberField``
(from ``django.contrib.localflavor.us``)
``PositiveIntegerField`` ``IntegerField``
``PositiveSmallIntegerField`` ``IntegerField``
``SlugField`` ``CharField``
``SmallIntegerField`` ``IntegerField``
``TextField`` ``CharField`` with ``widget=Textarea``
``TimeField`` ``TimeField``
``URLField`` ``URLField`` with ``verify_exists`` set
to the model field's ``verify_exists``
``USStateField`` ``CharField`` with
``widget=USStateSelect``
(``USStateSelect`` is from
``django.contrib.localflavor.us``)
``XMLField`` ``CharField`` with ``widget=Textarea``
=============================== ========================================
.. note::
The ``FloatField`` form field and ``DecimalField`` model and form fields
are new in the development version.
As you might expect, the ``ForeignKey`` and ``ManyToManyField`` model field
types are special cases:
* ``ForeignKey`` is represented by ``django.newforms.ModelChoiceField``,
which is a ``ChoiceField`` whose choices are a model ``QuerySet``.
* ``ManyToManyField`` is represented by
``django.newforms.ModelMultipleChoiceField``, which is a
``MultipleChoiceField`` whose choices are a model ``QuerySet``.
In addition, each generated form field has attributes set as follows:
* If the model field has ``blank=True``, then ``required`` is set to
``False`` on the form field. Otherwise, ``required=True``.
* The form field's ``label`` is set to the ``verbose_name`` of the model
field, with the first character capitalized.
* The form field's ``help_text`` is set to the ``help_text`` of the model
field.
* If the model field has ``choices`` set, then the form field's ``widget``
will be set to ``Select``, with choices coming from the model field's
``choices``.
The choices will include the "blank" choice, which is selected by
default. If the field is required, this forces the user to make a
selection. The blank choice will not be included if the model
field has ``blank=False`` and an explicit ``default`` value, in which
case the ``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.
A full example
~~~~~~~~~~~~~~
Consider this set of models::
from django.db import models
TITLE_CHOICES = (
('MR', 'Mr.'),
('MRS', 'Mrs.'),
('MS', 'Ms.'),
)
class Author(models.Model):
name = models.CharField(max_length=100)
title = models.CharField(max_length=3, choices=TITLE_CHOICES)
birth_date = models.DateField(blank=True, null=True)
def __unicode__(self):
return self.name
class Book(models.Model):
name = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
With these models, a call to ``form_for_model(Author)`` would return a ``Form``
class equivalent to this::
class AuthorForm(forms.Form):
name = forms.CharField(max_length=100)
title = forms.CharField(max_length=3,
widget=forms.Select(choices=TITLE_CHOICES))
birth_date = forms.DateField(required=False)
A call to ``form_for_model(Book)`` would return a ``Form`` class equivalent to
this::
class BookForm(forms.Form):
name = forms.CharField(max_length=100)
authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())
The ``save()`` method
~~~~~~~~~~~~~~~~~~~~~
Every form produced by ``form_for_model()`` also has a ``save()`` method. This
method creates and saves a database object from the data bound to the form. For
example::
# Create a form instance from POST data.
>>> f = ArticleForm(request.POST)
# Save a new Article object from the form's data.
>>> new_article = f.save()
Note that ``save()`` will raise a ``ValueError`` if the data in the form
doesn't validate -- i.e., ``if form.errors``.
This ``save()`` method accepts an optional ``commit`` keyword argument, which
accepts either ``True`` or ``False``. If you call ``save()`` with
``commit=False``, then it will return an object that hasn't yet been saved to
the database. In this case, it's up to you to call ``save()`` on the resulting
model instance. This is useful if you want to do custom processing on the
object before saving it. ``commit`` is ``True`` by default.
Another side effect of using ``commit=False`` is seen when your model has
a many-to-many relation with another model. If your model has a many-to-many
relation and you specify ``commit=False`` when you save a form, Django cannot
immediately save the form data for the many-to-many relation. This is because
it isn't possible to save many-to-many data for an instance until the instance
exists in the database.
To work around this problem, every time you save a form using ``commit=False``,
Django adds a ``save_m2m()`` method to the form created by ``form_for_model``.
After you've manually saved the instance produced by the form, you can invoke
``save_m2m()`` to save the many-to-many form data. For example::
# Create a form instance with POST data.
>>> f = AuthorForm(request.POST)
# Create, but don't save the new author instance.
>>> new_author = f.save(commit=False)
# Modify the author in some way.
>>> new_author.some_field = 'some_value'
# Save the new instance.
>>> new_author.save()
# Now, save the many-to-many data for the form.
>>> f.save_m2m()
Calling ``save_m2m()`` is only required if you use ``save(commit=False)``.
When you use a simple ``save()`` on a form, all data -- including
many-to-many data -- is saved without the need for any additional method calls.
For example::
# Create a form instance with POST data.
>>> f = AuthorForm(request.POST)
# Create and save the new author instance. There's no need to do anything else.
>>> new_author = f.save()
Using an alternate base class
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you want to add custom methods to the form generated by
``form_for_model()``, write a class that extends ``django.newforms.BaseForm``
and contains your custom methods. Then, use the ``form`` argument to
``form_for_model()`` to tell it to use your custom form as its base class.
For example::
# Create the new base class.
>>> class MyBase(BaseForm):
... def my_method(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.my_method()
Using 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 generated
form. There are two ways of telling ``form_for_model()`` to use only a subset
of the model fields:
1. Set ``editable=False`` on the model field. As a result, *any* form
created from the model via ``form_for_model()`` will not include that
field.
2. Use the ``fields`` argument to ``form_for_model()``. This argument, if
given, should be a list of field names to include in the form.
For example, if you want a form for the ``Author`` model (defined above)
that includes only the ``name`` and ``title`` fields, you would specify
``fields`` like this::
PartialArticleForm = form_for_model(Author, fields=('name', 'title'))
.. note::
If you specify ``fields`` when creating a form with ``form_for_model()``,
then the fields that are *not* specified will not be set by the form's
``save()`` method. Django will prevent any attempt to save an incomplete
model, so if the model does not allow the missing fields to be empty, and
does not provide a default value for the missing fields, any attempt to
``save()`` a ``form_for_model`` with missing fields will fail. To avoid
this failure, you must use ``save(commit=False)`` and manually set any
extra required fields::
instance = form.save(commit=False)
instance.required_field = 'new value'
instance.save()
See the `section on saving forms`_ for more details on using
``save(commit=False)``.
.. _section on saving forms: `The save() method`_
Overriding the default field types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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
``form_for_model()`` gives you the flexibility of changing the form field type
for a given model field. You do this by specifying a **formfield callback**.
A 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.
By default, ``form_for_model()`` calls the ``formfield()`` method on the model
field::
def default_callback(field, **kwargs):
return field.formfield(**kwargs)
The ``kwargs`` are any keyword arguments that might be passed to the form
field, such as ``required=True`` or ``label='Foo'``.
For example, if you wanted to use ``MyDateFormField`` for any ``DateField``
field on the model, you could define the callback::
>>> def my_callback(field, **kwargs):
... if isinstance(field, models.DateField):
... return MyDateFormField(**kwargs)
... else:
... return field.formfield(**kwargs)
>>> ArticleForm = form_for_model(Article, formfield_callback=my_callback)
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. That's why
this example has an ``else`` clause that implements the default behavior.
.. warning::
The field that is passed into the ``formfield_callback`` function in
``form_for_model()`` and ``form_for_instance`` is the field instance from
your model's class. You **must not** alter that object at all; treat it
as read-only!
If you make any alterations to that object, it will affect any future
users of the model class, because you will have changed the field object
used to construct the class. This is almost certainly what you don't want
to have happen.
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::
>>> ArticleForm = form_for_model(Article)
>>> ArticleForm._model
<class 'myapp.models.Article'>
``form_for_instance()``
-----------------------
``form_for_instance()`` is like ``form_for_model()``, but it takes a model
instance instead of a model class::
# Create an Author.
>>> a = Author(name='Joe Smith', title='MR', birth_date=None)
>>> a.save()
# Create a form for this particular Author.
>>> AuthorForm = form_for_instance(a)
# Instantiate the form.
>>> f = AuthorForm()
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.
Unlike ``form_for_model()``, a choice field in form created by
``form_for_instance()`` will not include the blank choice if the respective
model field has ``blank=False``. The initial choice is drawn from the instance.
When you call ``save()`` on a form created by ``form_for_instance()``,
the database instance will be updated. As in ``form_for_model()``, ``save()``
will raise ``ValueError`` if the data doesn't validate.
``form_for_instance()`` has ``form``, ``fields`` and ``formfield_callback``
arguments that behave the same way as they do for ``form_for_model()``.
Let's modify the earlier `contact form`_ view example a little bit. Suppose we
have a ``Message`` model that holds each contact submission. Something like::
class Message(models.Model):
subject = models.CharField(max_length=100)
message = models.TextField()
sender = models.EmailField()
cc_myself = models.BooleanField(required=False)
You could use this model to create a form (using ``form_for_model()``). You
could also use existing ``Message`` instances to create a form for editing
messages. The earlier_ view can be changed slightly to accept the ``id`` value
of an existing ``Message`` and present it for editing::
def contact_edit(request, msg_id):
# Create the form from the message id.
message = get_object_or_404(Message, id=msg_id)
ContactForm = form_for_instance(message)
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('/url/on_success/')
else:
form = ContactForm()
return render_to_response('contact.html', {'form': form})
Aside from how we create the ``ContactForm`` class here, the main point to
note is that the form display in the ``GET`` branch of the function
will use the values from the ``message`` instance as initial values for the
form field.
.. _contact form: `Simple view example`_
.. _earlier: `Simple view example`_
When should you use ``form_for_model()`` and ``form_for_instance()``?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``form_for_model()`` and ``form_for_instance()`` functions are meant to be
shortcuts for the common case. If you want to create a form whose fields map to
more than one model, or a form that contains fields that *aren't* on a model,
you shouldn't use these shortcuts. Creating a ``Form`` class the "long" way
isn't that difficult, after all.
More coming soon More coming soon
================ ================

View File

@ -1,25 +1,10 @@
""" """
36. Generating HTML forms from models XX. Generating HTML forms from models
Django provides shortcuts for creating Form objects from a model class and a This is mostly just a reworking of the form_for_model/form_for_instance tests
model instance. to use ModelForm. As such, the text may not make sense in all cases, and the
examples are probably a poor fit for the ModelForm syntax. In other words,
The function django.newforms.form_for_model() takes a model class and returns most of these tests should be rewritten.
a Form that is tied to the model. This Form works just like any other Form,
with one additional method: save(). The save() method creates an instance
of the model and returns that newly created instance. It saves the instance to
the database if save(commit=True), which is default. If you pass
commit=False, then you'll get the object without committing the changes to the
database.
The function django.newforms.form_for_instance() takes a model instance and
returns a Form that is tied to the instance. This form works just like any
other Form, with one additional method: save(). The save()
method updates the model instance. It also takes a commit=True parameter.
The function django.newforms.save_instance() takes a bound form instance and a
model instance and saves the form's cleaned_data into the instance. It also takes
a commit=True parameter.
""" """
from django.db import models from django.db import models
@ -30,23 +15,6 @@ ARTICLE_STATUS = (
(3, 'Live'), (3, 'Live'),
) )
STEERING_TYPE = (
('left', 'Left steering wheel'),
('right', 'Right steering wheel'),
)
FUEL_TYPE = (
('gas', 'Gasoline'),
('diesel', 'Diesel'),
('other', 'Other'),
)
TRANSMISSION_TYPE = (
('at', 'Automatic'),
('mt', 'Manual'),
('cvt', 'CVT'),
)
class Category(models.Model): class Category(models.Model):
name = models.CharField(max_length=20) name = models.CharField(max_length=20)
slug = models.SlugField(max_length=20) slug = models.SlugField(max_length=20)
@ -87,21 +55,119 @@ class PhoneNumber(models.Model):
def __unicode__(self): def __unicode__(self):
return self.phone return self.phone
class Car(models.Model):
name = models.CharField(max_length=50)
steering = models.CharField(max_length=5, choices=STEERING_TYPE, default='left')
fuel = models.CharField(max_length=10, choices=FUEL_TYPE)
transmission = models.CharField(max_length=3, choices=TRANSMISSION_TYPE, blank=True, help_text='Leave empty if not applicable.')
__test__ = {'API_TESTS': """ __test__ = {'API_TESTS': """
>>> from django.newforms import form_for_model, form_for_instance, save_instance, BaseForm, Form, CharField >>> from django import newforms as forms
>>> from django.newforms.models import ModelForm
The bare bones, absolutely nothing custom, basic case.
>>> class CategoryForm(ModelForm):
... class Meta:
... model = Category
>>> CategoryForm.base_fields.keys()
['name', 'slug', 'url']
Extra fields.
>>> class CategoryForm(ModelForm):
... some_extra_field = forms.BooleanField()
...
... class Meta:
... model = Category
>>> CategoryForm.base_fields.keys()
['name', 'slug', 'url', 'some_extra_field']
Replacing a field.
>>> class CategoryForm(ModelForm):
... url = forms.BooleanField()
...
... class Meta:
... model = Category
>>> CategoryForm.base_fields['url'].__class__
<class 'django.newforms.fields.BooleanField'>
Using 'fields'.
>>> class CategoryForm(ModelForm):
...
... class Meta:
... model = Category
... fields = ['url']
>>> CategoryForm.base_fields.keys()
['url']
Using 'exclude'
>>> class CategoryForm(ModelForm):
...
... class Meta:
... model = Category
... exclude = ['url']
>>> CategoryForm.base_fields.keys()
['name', 'slug']
Using 'fields' *and* 'exclude'. Not sure why you'd want to do this, but uh,
"be liberal in what you accept" and all.
>>> class CategoryForm(ModelForm):
...
... class Meta:
... model = Category
... fields = ['name', 'url']
... exclude = ['url']
>>> CategoryForm.base_fields.keys()
['name']
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
familiar with the mechanics.
>>> class CategoryForm(ModelForm):
... class Meta:
... model = Category
>>> class BadForm(CategoryForm):
... class Meta:
... model = Article
Traceback (most recent call last):
...
ImproperlyConfigured: BadForm defines more than one model.
>>> class ArticleForm(ModelForm):
... class Meta:
... model = Article
>>> class BadForm(ArticleForm, CategoryForm):
... pass
Traceback (most recent call last):
...
ImproperlyConfigured: BadForm's base classes define more than one model.
# Old form_for_x tests #######################################################
>>> from django.newforms import ModelForm, CharField
>>> import datetime >>> import datetime
>>> Category.objects.all() >>> Category.objects.all()
[] []
>>> CategoryForm = form_for_model(Category) >>> class CategoryForm(ModelForm):
>>> f = CategoryForm() ... class Meta:
... model = Category
>>> f = CategoryForm(Category())
>>> print f >>> print f
<tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" maxlength="20" /></td></tr> <tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" maxlength="20" /></td></tr>
<tr><th><label for="id_slug">Slug:</label></th><td><input id="id_slug" type="text" name="slug" maxlength="20" /></td></tr> <tr><th><label for="id_slug">Slug:</label></th><td><input id="id_slug" type="text" name="slug" maxlength="20" /></td></tr>
@ -113,13 +179,13 @@ __test__ = {'API_TESTS': """
>>> print f['name'] >>> print f['name']
<input id="id_name" type="text" name="name" maxlength="20" /> <input id="id_name" type="text" name="name" maxlength="20" />
>>> f = CategoryForm(auto_id=False) >>> f = CategoryForm(Category(), auto_id=False)
>>> print f.as_ul() >>> print f.as_ul()
<li>Name: <input type="text" name="name" maxlength="20" /></li> <li>Name: <input type="text" name="name" maxlength="20" /></li>
<li>Slug: <input type="text" name="slug" maxlength="20" /></li> <li>Slug: <input type="text" name="slug" maxlength="20" /></li>
<li>The URL: <input type="text" name="url" maxlength="40" /></li> <li>The URL: <input type="text" name="url" maxlength="40" /></li>
>>> f = CategoryForm({'name': 'Entertainment', 'slug': 'entertainment', 'url': 'entertainment'}) >>> f = CategoryForm(Category(), {'name': 'Entertainment', 'slug': 'entertainment', 'url': 'entertainment'})
>>> f.is_valid() >>> f.is_valid()
True True
>>> f.cleaned_data >>> f.cleaned_data
@ -130,7 +196,7 @@ True
>>> Category.objects.all() >>> Category.objects.all()
[<Category: Entertainment>] [<Category: Entertainment>]
>>> f = CategoryForm({'name': "It's a test", 'slug': 'its-test', 'url': 'test'}) >>> f = CategoryForm(Category(), {'name': "It's a test", 'slug': 'its-test', 'url': 'test'})
>>> f.is_valid() >>> f.is_valid()
True True
>>> f.cleaned_data >>> f.cleaned_data
@ -144,7 +210,7 @@ True
If you call save() with commit=False, then it will return an object that If you call save() with commit=False, then it will return an object that
hasn't yet been saved to the database. In this case, it's up to you to call hasn't yet been saved to the database. In this case, it's up to you to call
save() on the resulting model instance. save() on the resulting model instance.
>>> f = CategoryForm({'name': 'Third test', 'slug': 'third-test', 'url': 'third'}) >>> f = CategoryForm(Category(), {'name': 'Third test', 'slug': 'third-test', 'url': 'third'})
>>> f.is_valid() >>> f.is_valid()
True True
>>> f.cleaned_data >>> f.cleaned_data
@ -159,7 +225,7 @@ True
[<Category: Entertainment>, <Category: It's a test>, <Category: Third test>] [<Category: Entertainment>, <Category: It's a test>, <Category: Third test>]
If you call save() with invalid data, you'll get a ValueError. If you call save() with invalid data, you'll get a ValueError.
>>> f = CategoryForm({'name': '', 'slug': '', 'url': 'foo'}) >>> f = CategoryForm(Category(), {'name': '', 'slug': '', 'url': 'foo'})
>>> f.errors >>> f.errors
{'name': [u'This field is required.'], 'slug': [u'This field is required.']} {'name': [u'This field is required.'], 'slug': [u'This field is required.']}
>>> f.cleaned_data >>> f.cleaned_data
@ -170,7 +236,7 @@ AttributeError: 'CategoryForm' object has no attribute 'cleaned_data'
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValueError: The Category could not be created because the data didn't validate. ValueError: The Category could not be created because the data didn't validate.
>>> f = CategoryForm({'name': '', 'slug': '', 'url': 'foo'}) >>> f = CategoryForm(Category(), {'name': '', 'slug': '', 'url': 'foo'})
>>> f.save() >>> f.save()
Traceback (most recent call last): Traceback (most recent call last):
... ...
@ -184,8 +250,10 @@ Create a couple of Writers.
ManyToManyFields are represented by a MultipleChoiceField, ForeignKeys and any ManyToManyFields are represented by a MultipleChoiceField, ForeignKeys and any
fields with the 'choices' attribute are represented by a ChoiceField. fields with the 'choices' attribute are represented by a ChoiceField.
>>> ArticleForm = form_for_model(Article) >>> class ArticleForm(ModelForm):
>>> f = ArticleForm(auto_id=False) ... class Meta:
... model = Article
>>> f = ArticleForm(Article(), auto_id=False)
>>> print f >>> print f
<tr><th>Headline:</th><td><input type="text" name="headline" maxlength="50" /></td></tr> <tr><th>Headline:</th><td><input type="text" name="headline" maxlength="50" /></td></tr>
<tr><th>Slug:</th><td><input type="text" name="slug" maxlength="50" /></td></tr> <tr><th>Slug:</th><td><input type="text" name="slug" maxlength="50" /></td></tr>
@ -214,28 +282,23 @@ 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 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 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! from the form can't provide a value for that field!
>>> PartialArticleForm = form_for_model(Article, fields=('headline','pub_date')) >>> class PartialArticleForm(ModelForm):
>>> f = PartialArticleForm(auto_id=False) ... class Meta:
... model = Article
... fields = ('headline','pub_date')
>>> f = PartialArticleForm(Article(), auto_id=False)
>>> print f >>> print f
<tr><th>Headline:</th><td><input type="text" name="headline" maxlength="50" /></td></tr> <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> <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):
... def say_hello(self):
... print 'hello'
>>> CategoryForm = form_for_model(Category, form=CustomForm)
>>> f = CategoryForm()
>>> f.say_hello()
hello
Use form_for_instance to create a Form from a model instance. The difference Use form_for_instance to create a Form from a model instance. The difference
between this Form and one created via form_for_model is that the object's between this Form and one created via form_for_model is that the object's
current values are inserted as 'initial' data in each Field. current values are inserted as 'initial' data in each Field.
>>> w = Writer.objects.get(name='Mike Royko') >>> w = Writer.objects.get(name='Mike Royko')
>>> RoykoForm = form_for_instance(w) >>> class RoykoForm(ModelForm):
>>> f = RoykoForm(auto_id=False) ... class Meta:
... model = Writer
>>> f = RoykoForm(w, auto_id=False)
>>> print f >>> print f
<tr><th>Name:</th><td><input type="text" name="name" value="Mike Royko" maxlength="50" /><br />Use both first and last names.</td></tr> <tr><th>Name:</th><td><input type="text" name="name" value="Mike Royko" maxlength="50" /><br />Use both first and last names.</td></tr>
@ -243,8 +306,10 @@ current values are inserted as 'initial' data in each Field.
>>> art.save() >>> art.save()
>>> art.id >>> art.id
1 1
>>> TestArticleForm = form_for_instance(art) >>> class TestArticleForm(ModelForm):
>>> f = TestArticleForm(auto_id=False) ... class Meta:
... model = Article
>>> f = TestArticleForm(art, auto_id=False)
>>> print f.as_ul() >>> print f.as_ul()
<li>Headline: <input type="text" name="headline" value="Test article" maxlength="50" /></li> <li>Headline: <input type="text" name="headline" value="Test article" maxlength="50" /></li>
<li>Slug: <input type="text" name="slug" value="test-article" maxlength="50" /></li> <li>Slug: <input type="text" name="slug" value="test-article" maxlength="50" /></li>
@ -266,7 +331,7 @@ current values are inserted as 'initial' data in each Field.
<option value="2">It&#39;s a test</option> <option value="2">It&#39;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'Test headline', 'slug': 'test-headline', 'pub_date': u'1984-02-06', 'writer': u'1', 'article': 'Hello.'}) >>> f = TestArticleForm(art, {'headline': u'Test headline', 'slug': 'test-headline', 'pub_date': u'1984-02-06', 'writer': u'1', 'article': 'Hello.'})
>>> f.is_valid() >>> f.is_valid()
True True
>>> test_art = f.save() >>> test_art = f.save()
@ -278,8 +343,11 @@ u'Test headline'
You can create a form over a subset of the available fields You can create a form over a subset of the available fields
by specifying a 'fields' argument to form_for_instance. by specifying a 'fields' argument to form_for_instance.
>>> PartialArticleForm = form_for_instance(art, fields=('headline', 'slug', 'pub_date')) >>> class PartialArticleForm(ModelForm):
>>> f = PartialArticleForm({'headline': u'New headline', 'slug': 'new-headline', 'pub_date': u'1988-01-04'}, auto_id=False) ... class Meta:
... model = Article
... fields=('headline', 'slug', 'pub_date')
>>> f = PartialArticleForm(art, {'headline': u'New headline', 'slug': 'new-headline', 'pub_date': u'1988-01-04'}, auto_id=False)
>>> print f.as_ul() >>> print f.as_ul()
<li>Headline: <input type="text" name="headline" value="New headline" maxlength="50" /></li> <li>Headline: <input type="text" name="headline" value="New headline" maxlength="50" /></li>
<li>Slug: <input type="text" name="slug" value="new-headline" maxlength="50" /></li> <li>Slug: <input type="text" name="slug" value="new-headline" maxlength="50" /></li>
@ -299,8 +367,10 @@ Add some categories and test the many-to-many form output.
>>> new_art.categories.add(Category.objects.get(name='Entertainment')) >>> new_art.categories.add(Category.objects.get(name='Entertainment'))
>>> new_art.categories.all() >>> new_art.categories.all()
[<Category: Entertainment>] [<Category: Entertainment>]
>>> TestArticleForm = form_for_instance(new_art) >>> class TestArticleForm(ModelForm):
>>> f = TestArticleForm(auto_id=False) ... class Meta:
... model = Article
>>> f = TestArticleForm(new_art, auto_id=False)
>>> print f.as_ul() >>> print f.as_ul()
<li>Headline: <input type="text" name="headline" value="New headline" maxlength="50" /></li> <li>Headline: <input type="text" name="headline" value="New headline" maxlength="50" /></li>
<li>Slug: <input type="text" name="slug" value="new-headline" maxlength="50" /></li> <li>Slug: <input type="text" name="slug" value="new-headline" maxlength="50" /></li>
@ -323,7 +393,7 @@ Add some categories and test the many-to-many form output.
<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', 'slug': u'new-headline', 'pub_date': u'1988-01-04', >>> f = TestArticleForm(new_art, {'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04',
... 'writer': u'1', 'article': u'Hello.', 'categories': [u'1', u'2']}) ... 'writer': u'1', 'article': u'Hello.', 'categories': [u'1', u'2']})
>>> new_art = f.save() >>> new_art = f.save()
>>> new_art.id >>> new_art.id
@ -333,7 +403,7 @@ Add some categories and test the many-to-many form output.
[<Category: Entertainment>, <Category: It's a test>] [<Category: Entertainment>, <Category: It's a test>]
Now, submit form data with no categories. This deletes the existing categories. Now, submit form data with no categories. This deletes the existing categories.
>>> f = TestArticleForm({'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04', >>> f = TestArticleForm(new_art, {'headline': u'New headline', 'slug': u'new-headline', 'pub_date': u'1988-01-04',
... 'writer': u'1', 'article': u'Hello.'}) ... 'writer': u'1', 'article': u'Hello.'})
>>> new_art = f.save() >>> new_art = f.save()
>>> new_art.id >>> new_art.id
@ -343,8 +413,10 @@ Now, submit form data with no categories. This deletes the existing categories.
[] []
Create a new article, with categories, via the form. Create a new article, with categories, via the form.
>>> ArticleForm = form_for_model(Article) >>> class ArticleForm(ModelForm):
>>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01', ... class Meta:
... model = Article
>>> f = ArticleForm(Article(), {'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01',
... 'writer': u'1', 'article': u'Test.', 'categories': [u'1', u'2']}) ... 'writer': u'1', 'article': u'Test.', 'categories': [u'1', u'2']})
>>> new_art = f.save() >>> new_art = f.save()
>>> new_art.id >>> new_art.id
@ -354,8 +426,10 @@ Create a new article, with categories, via the form.
[<Category: Entertainment>, <Category: It's a test>] [<Category: Entertainment>, <Category: It's a test>]
Create a new article, with no categories, via the form. Create a new article, with no categories, via the form.
>>> ArticleForm = form_for_model(Article) >>> class ArticleForm(ModelForm):
>>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01', ... class Meta:
... model = Article
>>> f = ArticleForm(Article(), {'headline': u'The walrus was Paul', 'slug': u'walrus-was-paul', 'pub_date': u'1967-11-01',
... 'writer': u'1', 'article': u'Test.'}) ... 'writer': u'1', 'article': u'Test.'})
>>> new_art = f.save() >>> new_art = f.save()
>>> new_art.id >>> new_art.id
@ -366,8 +440,10 @@ Create a new article, with no categories, via the form.
Create a new article, with categories, via the form, but use commit=False. Create a new article, with categories, via the form, but use commit=False.
The m2m data won't be saved until save_m2m() is invoked on the form. The m2m data won't be saved until save_m2m() is invoked on the form.
>>> ArticleForm = form_for_model(Article) >>> class ArticleForm(ModelForm):
>>> f = ArticleForm({'headline': u'The walrus was Paul', 'slug': 'walrus-was-paul', 'pub_date': u'1967-11-01', ... class Meta:
... model = Article
>>> f = ArticleForm(Article(), {'headline': u'The walrus was Paul', 'slug': 'walrus-was-paul', 'pub_date': u'1967-11-01',
... 'writer': u'1', 'article': u'Test.', 'categories': [u'1', u'2']}) ... 'writer': u'1', 'article': u'Test.', 'categories': [u'1', u'2']})
>>> new_art = f.save(commit=False) >>> new_art = f.save(commit=False)
@ -386,10 +462,10 @@ The m2m data won't be saved until save_m2m() is invoked on the form.
>>> new_art.categories.order_by('name') >>> new_art.categories.order_by('name')
[<Category: Entertainment>, <Category: It's a test>] [<Category: Entertainment>, <Category: It's a test>]
Here, we define a custom Form. Because it happens to have the same fields as Here, we define a custom ModelForm. Because it happens to have the same fields as
the Category model, we can use save_instance() to apply its changes to an the Category model, we can just call the form's save() to apply its changes to an
existing Category instance. existing Category instance.
>>> class ShortCategory(Form): >>> class ShortCategory(ModelForm):
... name = CharField(max_length=5) ... name = CharField(max_length=5)
... slug = CharField(max_length=5) ... slug = CharField(max_length=5)
... url = CharField(max_length=3) ... url = CharField(max_length=3)
@ -398,8 +474,8 @@ existing Category instance.
<Category: Third test> <Category: Third test>
>>> cat.id >>> cat.id
3 3
>>> sc = ShortCategory({'name': 'Third', 'slug': 'third', 'url': '3rd'}) >>> form = ShortCategory(cat, {'name': 'Third', 'slug': 'third', 'url': '3rd'})
>>> save_instance(sc, cat) >>> form.save()
<Category: Third> <Category: Third>
>>> Category.objects.get(id=3) >>> Category.objects.get(id=3)
<Category: Third> <Category: Third>
@ -407,8 +483,10 @@ existing Category instance.
Here, we demonstrate that choices for a ForeignKey ChoiceField are determined Here, we demonstrate that choices for a ForeignKey ChoiceField are determined
at runtime, based on the data in the database when the form is displayed, not at runtime, based on the data in the database when the form is displayed, not
the data in the database when the form is instantiated. the data in the database when the form is instantiated.
>>> ArticleForm = form_for_model(Article) >>> class ArticleForm(ModelForm):
>>> f = ArticleForm(auto_id=False) ... class Meta:
... model = Article
>>> f = ArticleForm(Article(), auto_id=False)
>>> print f.as_ul() >>> print f.as_ul()
<li>Headline: <input type="text" name="headline" maxlength="50" /></li> <li>Headline: <input type="text" name="headline" maxlength="50" /></li>
<li>Slug: <input type="text" name="slug" maxlength="50" /></li> <li>Slug: <input type="text" name="slug" maxlength="50" /></li>
@ -609,60 +687,12 @@ ValidationError: [u'Select a valid choice. 4 is not one of the available choices
# PhoneNumberField ############################################################ # PhoneNumberField ############################################################
>>> PhoneNumberForm = form_for_model(PhoneNumber) >>> class PhoneNumberForm(ModelForm):
>>> f = PhoneNumberForm({'phone': '(312) 555-1212', 'description': 'Assistance'}) ... class Meta:
... model = PhoneNumber
>>> f = PhoneNumberForm(PhoneNumber(), {'phone': '(312) 555-1212', 'description': 'Assistance'})
>>> f.is_valid() >>> f.is_valid()
True True
>>> f.cleaned_data >>> f.cleaned_data
{'phone': u'312-555-1212', 'description': u'Assistance'} {'phone': u'312-555-1212', 'description': u'Assistance'}
# form_for_* blank choices ####################################################
Show the form for a new Car. Note that steering field doesn't include the blank choice,
because the field is obligatory and has an explicit default.
>>> CarForm = form_for_model(Car)
>>> f = CarForm(auto_id=False)
>>> print f
<tr><th>Name:</th><td><input type="text" name="name" maxlength="50" /></td></tr>
<tr><th>Steering:</th><td><select name="steering">
<option value="left" selected="selected">Left steering wheel</option>
<option value="right">Right steering wheel</option>
</select></td></tr>
<tr><th>Fuel:</th><td><select name="fuel">
<option value="" selected="selected">---------</option>
<option value="gas">Gasoline</option>
<option value="diesel">Diesel</option>
<option value="other">Other</option>
</select></td></tr>
<tr><th>Transmission:</th><td><select name="transmission">
<option value="" selected="selected">---------</option>
<option value="at">Automatic</option>
<option value="mt">Manual</option>
<option value="cvt">CVT</option>
</select><br />Leave empty if not applicable.</td></tr>
Create a Car, and display the form for modifying it. Note that now the fuel
selector doesn't include the blank choice as well, since the field is
obligatory and can not be changed to be blank.
>>> honda = Car(name='Honda Accord Wagon', steering='right', fuel='gas', transmission='at')
>>> honda.save()
>>> HondaForm = form_for_instance(honda)
>>> f = HondaForm(auto_id=False)
>>> print f
<tr><th>Name:</th><td><input type="text" name="name" value="Honda Accord Wagon" maxlength="50" /></td></tr>
<tr><th>Steering:</th><td><select name="steering">
<option value="left">Left steering wheel</option>
<option value="right" selected="selected">Right steering wheel</option>
</select></td></tr>
<tr><th>Fuel:</th><td><select name="fuel">
<option value="gas" selected="selected">Gasoline</option>
<option value="diesel">Diesel</option>
<option value="other">Other</option>
</select></td></tr>
<tr><th>Transmission:</th><td><select name="transmission">
<option value="">---------</option>
<option value="at" selected="selected">Automatic</option>
<option value="mt">Manual</option>
<option value="cvt">CVT</option>
</select><br />Leave empty if not applicable.</td></tr>
"""} """}