Fixed #19733 - deprecated ModelForms without 'fields' or 'exclude', and added '__all__' shortcut

This also updates all dependent functionality, including modelform_factory
 and modelformset_factory, and the generic views `ModelFormMixin`,
 `CreateView` and `UpdateView` which gain a new `fields` attribute.
This commit is contained in:
Luke Plant 2013-02-21 21:56:55 +00:00
parent 1e37cb37ce
commit f026a519ae
34 changed files with 578 additions and 201 deletions

View file

@ -1051,6 +1051,7 @@ code would be required in the app's ``admin.py`` file::
class Meta:
model = MyUser
fields = ['email', 'password', 'date_of_birth', 'is_active', 'is_admin']
def clean_password(self):
# Regardless of what the user provides, return the initial value.

View file

@ -114,9 +114,11 @@ here; we don't have to write any logic ourselves::
class AuthorCreate(CreateView):
model = Author
fields = ['name']
class AuthorUpdate(UpdateView):
model = Author
fields = ['name']
class AuthorDelete(DeleteView):
model = Author
@ -126,6 +128,17 @@ here; we don't have to write any logic ourselves::
We have to use :func:`~django.core.urlresolvers.reverse_lazy` here, not
just ``reverse`` as the urls are not loaded when the file is imported.
.. versionchanged:: 1.6
In Django 1.6, the ``fields`` attribute was added, which works the same way as
the ``fields`` attribute on the inner ``Meta`` class on
:class:`~django.forms.ModelForm`.
Omitting the fields attribute will work as previously, but is deprecated and
this attribute will be required from 1.8 (unless you define the form class in
another way).
Finally, we hook these new views into the URLconf::
# urls.py
@ -177,33 +190,17 @@ the foreign key relation to the model::
# ...
Create a custom :class:`~django.forms.ModelForm` in order to exclude the
``created_by`` field and prevent the user from editing it:
.. code-block:: python
# forms.py
from django import forms
from myapp.models import Author
class AuthorForm(forms.ModelForm):
class Meta:
model = Author
exclude = ('created_by',)
In the view, use the custom
:attr:`~django.views.generic.edit.FormMixin.form_class` and override
:meth:`~django.views.generic.edit.ModelFormMixin.form_valid()` to add the
user::
In the view, ensure that you exclude ``created_by`` in the list of fields to
edit, and override
:meth:`~django.views.generic.edit.ModelFormMixin.form_valid()` to add the user::
# views.py
from django.views.generic.edit import CreateView
from myapp.models import Author
from myapp.forms import AuthorForm
class AuthorCreate(CreateView):
form_class = AuthorForm
model = Author
fields = ['name']
def form_valid(self, form):
form.instance.created_by = self.request.user

View file

@ -28,6 +28,7 @@ For example::
>>> class ArticleForm(ModelForm):
... class Meta:
... model = Article
... fields = ['pub_date', 'headline', 'content', 'reporter']
# Creating a form to add an article.
>>> form = ArticleForm()
@ -39,11 +40,13 @@ For example::
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:
The generated ``Form`` class will have a form field for every model field
specified, in the order specified in the ``fields`` attribute.
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
@ -168,10 +171,13 @@ Consider this set of models::
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ['name', 'title', 'birth_date']
class BookForm(ModelForm):
class Meta:
model = Book
fields = ['name', 'authors']
With these models, the ``ModelForm`` subclasses above would be roughly
equivalent to this (the only difference being the ``save()`` method, which
@ -288,47 +294,66 @@ method is used to determine whether a form requires multipart file upload (and
hence whether ``request.FILES`` must be passed to the form), etc. See
:ref:`binding-uploaded-files` for more information.
Using a subset of fields on the form
------------------------------------
.. _modelforms-selecting-fields:
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:
Selecting the fields to use
---------------------------
1. Set ``editable=False`` on the model field. As a result, *any* form
created from the model via ``ModelForm`` will not include that
field.
It is strongly recommended that you explicitly set all fields that should be
edited in the form using the ``fields`` attribute. Failure to do so can easily
lead to security problems when a form unexpectedly allows a user to set certain
fields, especially when new fields are added to a model. Depending on how the
form is rendered, the problem may not even be visible on the web page.
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. The order in which the fields names are specified
in that list is respected when the form renders them.
The alternative approach would be to include all fields automatically, or
blacklist only some. This fundamental approach is known to be much less secure
and has led to serious exploits on major websites (e.g. `GitHub
<https://github.com/blog/1068-public-key-security-vulnerability-and-mitigation>`_).
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 from the form.
There are, however, two shortcuts available for cases where you can guarantee
these security concerns do not apply to you:
For example, if you want a form for the ``Author`` model (defined
above) that includes only the ``name`` and ``birth_date`` fields, you would
specify ``fields`` or ``exclude`` like this::
1. Set the ``fields`` attribute to the special value ``'__all__'`` to indicate
that all fields in the model should be used. For example::
class PartialAuthorForm(ModelForm):
class Meta:
model = Author
fields = ('name', 'birth_date')
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = '__all__'
class PartialAuthorForm(ModelForm):
class Meta:
model = Author
exclude = ('title',)
2. Set the ``exclude`` attribute of the ``ModelForm``'s inner ``Meta`` class to
a list of fields to be excluded from the form.
For example::
class PartialAuthorForm(ModelForm):
class Meta:
model = Author
exclude = ['title']
Since the ``Author`` model has the 3 fields ``name``, ``title`` and
``birth_date``, this will result in the fields ``name`` and ``birth_date``
being present on the form.
If either of these are used, the order the fields appear in the form will be the
order the fields are defined in the model, with ``ManyToManyField`` instances
appearing last.
In addition, Django applies the following rule: if you set ``editable=False`` on
the model field, *any* form created from the model via ``ModelForm`` will not
include that field.
.. versionchanged:: 1.6
Before version 1.6, the ``'__all__'`` shortcut did not exist, but omitting
the ``fields`` attribute had the same effect. Omitting both ``fields`` and
``exclude`` is now deprecated, but will continue to work as before until
version 1.8
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
Any fields not included in a form by the above logic
will not be set by the form's ``save()`` method. Also, if you
manually add the excluded fields back to the form, they will not
be initialized from the model instance.
@ -401,15 +426,19 @@ field, you could do the following::
class Meta:
model = Article
fields = ['pub_date', 'headline', 'content', 'reporter']
If you want to override a field's default label, then specify the ``label``
parameter when declaring the form field::
>>> class ArticleForm(ModelForm):
... pub_date = DateField(label='Publication date')
...
... class Meta:
... model = Article
class ArticleForm(ModelForm):
pub_date = DateField(label='Publication date')
class Meta:
model = Article
fields = ['pub_date', 'headline', 'content', 'reporter']
.. note::
@ -436,6 +465,7 @@ parameter when declaring the form field::
class Meta:
model = Article
fields = ['headline', 'content']
You must ensure that the type of the form field can be used to set the
contents of the corresponding model field. When they are not compatible,
@ -444,30 +474,6 @@ parameter when declaring the form field::
See the :doc:`form field documentation </ref/forms/fields>` for more information
on fields and their arguments.
Changing the order of fields
----------------------------
By default, a ``ModelForm`` will render fields in the same order that they are
defined on the model, with ``ManyToManyField`` instances appearing last. If
you want to change the order in which fields are rendered, you can use the
``fields`` attribute on the ``Meta`` class.
The ``fields`` attribute defines the subset of model fields that will be
rendered, and the order in which they will be rendered. For example given this
model::
class Book(models.Model):
author = models.ForeignKey(Author)
title = models.CharField(max_length=100)
the ``author`` field would be rendered first. If we wanted the title field
to be rendered first, we could specify the following ``ModelForm``::
>>> class BookForm(ModelForm):
... class Meta:
... model = Book
... fields = ('title', 'author')
.. _overriding-modelform-clean-method:
Overriding the clean() method
@ -550,21 +556,19 @@ definition. This may be more convenient if you do not have many customizations
to make::
>>> from django.forms.models import modelform_factory
>>> BookForm = modelform_factory(Book)
>>> BookForm = modelform_factory(Book, fields=("author", "title"))
This can also be used to make simple modifications to existing forms, for
example by specifying which fields should be displayed::
>>> Form = modelform_factory(Book, form=BookForm, fields=("author",))
... or which fields should be excluded::
>>> Form = modelform_factory(Book, form=BookForm, exclude=("title",))
You can also specify the widgets to be used for a given field::
example by specifying the widgets to be used for a given field::
>>> from django.forms import Textarea
>>> Form = modelform_factory(Book, form=BookForm, widgets={"title": Textarea()})
>>> Form = modelform_factory(Book, form=BookForm,
widgets={"title": Textarea()})
The fields to include can be specified using the ``fields`` and ``exclude``
keyword arguments, or the corresponding attributes on the ``ModelForm`` inner
``Meta`` class. Please see the ``ModelForm`` :ref:`modelforms-selecting-fields`
documentation.
.. _model-formsets:
@ -688,11 +692,10 @@ database. If a given instance's data didn't change in the bound data, the
instance won't be saved to the database and won't be included in the return
value (``instances``, in the above example).
When fields are missing from the form (for example because they have
been excluded), these fields will not be set by the ``save()``
method. You can find more information about this restriction, which
also holds for regular ``ModelForms``, in `Using a subset of fields on
the form`_.
When fields are missing from the form (for example because they have been
excluded), these fields will not be set by the ``save()`` method. You can find
more information about this restriction, which also holds for regular
``ModelForms``, in `Selecting the fields to use`_.
Pass ``commit=False`` to return the unsaved model instances::