mirror of
https://github.com/django/django.git
synced 2025-08-03 10:34:04 +00:00
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:
parent
1e37cb37ce
commit
f026a519ae
34 changed files with 578 additions and 201 deletions
|
@ -110,6 +110,7 @@ CreateView
|
|||
|
||||
class AuthorCreate(CreateView):
|
||||
model = Author
|
||||
fields = ['name']
|
||||
|
||||
UpdateView
|
||||
----------
|
||||
|
@ -152,6 +153,7 @@ UpdateView
|
|||
|
||||
class AuthorUpdate(UpdateView):
|
||||
model = Author
|
||||
fields = ['name']
|
||||
|
||||
DeleteView
|
||||
----------
|
||||
|
|
|
@ -116,6 +116,18 @@ ModelFormMixin
|
|||
by examining ``self.object`` or
|
||||
:attr:`~django.views.generic.detail.SingleObjectMixin.queryset`.
|
||||
|
||||
.. attribute:: fields
|
||||
|
||||
.. versionadded:: 1.6
|
||||
|
||||
A list of names of fields. This is interpreted the same way as the
|
||||
``Meta.fields`` attribute of :class:`~django.forms.ModelForm`.
|
||||
|
||||
This is a required attribute if you are generating the form class
|
||||
automatically (e.g. using ``model``). Omitting this attribute will
|
||||
result in all fields being used, but this behaviour is deprecated
|
||||
and will be removed in Django 1.8.
|
||||
|
||||
.. attribute:: success_url
|
||||
|
||||
The URL to redirect to when the form is successfully processed.
|
||||
|
|
|
@ -335,6 +335,22 @@ subclass::
|
|||
|
||||
For an example see the section `Adding custom validation to the admin`_.
|
||||
|
||||
.. admonition:: Note
|
||||
|
||||
.. versionchanged:: 1.6
|
||||
|
||||
If you define the ``Meta.model`` attribute on a
|
||||
:class:`~django.forms.ModelForm`, you must also define the
|
||||
``Meta.fields`` attribute (or the ``Meta.exclude`` attribute). However,
|
||||
since the admin has its own way of defining fields, the ``Meta.fields``
|
||||
attribute will be ignored.
|
||||
|
||||
If the ``ModelForm`` is only going to be used for the admin, the easiest
|
||||
solution is to omit the ``Meta.model`` attribute, since ``ModelAdmin``
|
||||
will provide the correct model to use. Alternatively, you can set
|
||||
``fields = []`` in the ``Meta`` class to satisfy the validation on the
|
||||
``ModelForm``.
|
||||
|
||||
.. admonition:: Note
|
||||
|
||||
If your ``ModelForm`` and ``ModelAdmin`` both define an ``exclude``
|
||||
|
@ -1283,13 +1299,24 @@ templates used by the :class:`ModelAdmin` views:
|
|||
on the changelist page. To use a custom form, for example::
|
||||
|
||||
class MyForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = MyModel
|
||||
pass
|
||||
|
||||
class MyModelAdmin(admin.ModelAdmin):
|
||||
def get_changelist_form(self, request, **kwargs):
|
||||
return MyForm
|
||||
|
||||
.. admonition:: Note
|
||||
|
||||
.. versionchanged:: 1.6
|
||||
|
||||
If you define the ``Meta.model`` attribute on a
|
||||
:class:`~django.forms.ModelForm`, you must also define the
|
||||
``Meta.fields`` attribute (or the ``Meta.exclude`` attribute). However,
|
||||
``ModelAdmin`` ignores this value, overriding it with the
|
||||
:attr:`ModelAdmin.list_editable` attribute. The easiest solution is to
|
||||
omit the ``Meta.model`` attribute, since ``ModelAdmin`` will provide the
|
||||
correct model to use.
|
||||
|
||||
.. method:: ModelAdmin.get_changelist_formset(self, request, **kwargs)
|
||||
|
||||
Returns a :ref:`ModelFormSet <model-formsets>` class for use on the
|
||||
|
@ -1490,9 +1517,6 @@ needed. Now within your form you can add your own custom validation for
|
|||
any field::
|
||||
|
||||
class MyArticleAdminForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Article
|
||||
|
||||
def clean_name(self):
|
||||
# do something that validates your data
|
||||
return self.cleaned_data["name"]
|
||||
|
|
|
@ -25,6 +25,14 @@ Model Form Functions
|
|||
|
||||
See :ref:`modelforms-factory` for example usage.
|
||||
|
||||
.. versionchanged:: 1.6
|
||||
|
||||
You must provide the list of fields explicitly, either via keyword arguments
|
||||
``fields`` or ``exclude``, or the corresponding attributes on the form's
|
||||
inner ``Meta`` class. See :ref:`modelforms-selecting-fields` for more
|
||||
information. Omitting any definition of the fields to use will result in all
|
||||
fields being used, but this behaviour is deprecated.
|
||||
|
||||
.. function:: modelformset_factory(model, form=ModelForm, formfield_callback=None, formset=BaseModelFormSet, extra=1, can_delete=False, can_order=False, max_num=None, fields=None, exclude=None, widgets=None, validate_max=False)
|
||||
|
||||
Returns a ``FormSet`` class for the given ``model`` class.
|
||||
|
|
|
@ -532,3 +532,55 @@ including it in an URLconf, simply replace::
|
|||
with::
|
||||
|
||||
(r'^prefix/(?P<content_type_id>\d+)/(?P<object_id>.*)/$', 'django.contrib.contenttypes.views.shortcut'),
|
||||
|
||||
``ModelForm`` without ``fields`` or ``exclude``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Previously, if you wanted a :class:`~django.forms.ModelForm` to use all fields on
|
||||
the model, you could simply omit the ``Meta.fields`` attribute, and all fields
|
||||
would be used.
|
||||
|
||||
This can lead to security problems where fields are added to the model and,
|
||||
unintentionally, automatically become editable by end users. In some cases,
|
||||
particular with boolean fields, it is possible for this problem to be completely
|
||||
invisible. This is a form of `Mass assignment vulnerability
|
||||
<http://en.wikipedia.org/wiki/Mass_assignment_vulnerability>`_.
|
||||
|
||||
For this reason, this behaviour is deprecated, and using the ``Meta.exclude``
|
||||
option is strongly discouraged. Instead, all fields that are intended for
|
||||
inclusion in the form should be listed explicitly in the ``fields`` attribute.
|
||||
|
||||
If this security concern really does not apply in your case, there is a shortcut
|
||||
to explicitly indicate that all fields should be used - use the special value
|
||||
``"__all__"`` for the fields attribute::
|
||||
|
||||
class MyModelForm(ModelForm):
|
||||
class Meta:
|
||||
fields = "__all__"
|
||||
model = MyModel
|
||||
|
||||
If you have custom ``ModelForms`` that only need to be used in the admin, there
|
||||
is another option. The admin has its own methods for defining fields
|
||||
(``fieldsets`` etc.), and so adding a list of fields to the ``ModelForm`` is
|
||||
redundant. Instead, simply omit the ``Meta`` inner class of the ``ModelForm``,
|
||||
or omit the ``Meta.model`` attribute. Since the ``ModelAdmin`` subclass knows
|
||||
which model it is for, it can add the necessary attributes to derive a
|
||||
functioning ``ModelForm``. This behaviour also works for earlier Django
|
||||
versions.
|
||||
|
||||
``UpdateView`` and ``CreateView`` without explicit fields
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The generic views :class:`~django.views.generic.edit.CreateView` and
|
||||
:class:`~django.views.generic.edit.UpdateView`, and anything else derived from
|
||||
:class:`~django.views.generic.edit.ModelFormMixin`, are vulnerable to the
|
||||
security problem described in the section above, because they can automatically
|
||||
create a ``ModelForm`` that uses all fields for a model.
|
||||
|
||||
For this reason, if you use these views for editing models, you must also supply
|
||||
the ``fields`` attribute, which is a list of model fields and works in the same
|
||||
way as the :class:`~django.forms.ModelForm` ``Meta.fields`` attribute. Alternatively,
|
||||
you can set set the ``form_class`` attribute to a ``ModelForm`` that explicitly
|
||||
defines the fields to be used. Defining an ``UpdateView`` or ``CreateView``
|
||||
subclass to be used with a model but without an explicit list of fields is
|
||||
deprecated.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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::
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue