diff options
author | Luke Plant <L.Plant.98@cantab.net> | 2013-02-21 21:56:55 +0000 |
---|---|---|
committer | Luke Plant <L.Plant.98@cantab.net> | 2013-05-09 16:44:36 +0100 |
commit | f026a519aea8f3ea7ca339bfbbb007e1ee0068b0 (patch) | |
tree | 882e594178a78d507ede36c2c9b0b8d5a9305561 /docs | |
parent | 1e37cb37cec330a6b78925e2ef5589817428d09a (diff) | |
download | django-f026a519aea8f3ea7ca339bfbbb007e1ee0068b0.tar.gz |
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.
Diffstat (limited to 'docs')
-rw-r--r-- | docs/ref/class-based-views/generic-editing.txt | 2 | ||||
-rw-r--r-- | docs/ref/class-based-views/mixins-editing.txt | 12 | ||||
-rw-r--r-- | docs/ref/contrib/admin/index.txt | 34 | ||||
-rw-r--r-- | docs/ref/forms/models.txt | 8 | ||||
-rw-r--r-- | docs/releases/1.6.txt | 52 | ||||
-rw-r--r-- | docs/topics/auth/customizing.txt | 1 | ||||
-rw-r--r-- | docs/topics/class-based-views/generic-editing.txt | 37 | ||||
-rw-r--r-- | docs/topics/forms/modelforms.txt | 163 |
8 files changed, 204 insertions, 105 deletions
diff --git a/docs/ref/class-based-views/generic-editing.txt b/docs/ref/class-based-views/generic-editing.txt index 1dbb427036..555ba40cfb 100644 --- a/docs/ref/class-based-views/generic-editing.txt +++ b/docs/ref/class-based-views/generic-editing.txt @@ -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 ---------- diff --git a/docs/ref/class-based-views/mixins-editing.txt b/docs/ref/class-based-views/mixins-editing.txt index 3f32269742..51d8628818 100644 --- a/docs/ref/class-based-views/mixins-editing.txt +++ b/docs/ref/class-based-views/mixins-editing.txt @@ -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. diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 43f0398566..67e498ee91 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -337,6 +337,22 @@ subclass:: .. 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`` option then ``ModelAdmin`` takes precedence:: @@ -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"] diff --git a/docs/ref/forms/models.txt b/docs/ref/forms/models.txt index 7e3a1470b6..9b3480758a 100644 --- a/docs/ref/forms/models.txt +++ b/docs/ref/forms/models.txt @@ -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. diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt index 611b661415..9cce36aac3 100644 --- a/docs/releases/1.6.txt +++ b/docs/releases/1.6.txt @@ -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. diff --git a/docs/topics/auth/customizing.txt b/docs/topics/auth/customizing.txt index a5d7d3f9a1..b53bbe8211 100644 --- a/docs/topics/auth/customizing.txt +++ b/docs/topics/auth/customizing.txt @@ -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. diff --git a/docs/topics/class-based-views/generic-editing.txt b/docs/topics/class-based-views/generic-editing.txt index 66ba36fd87..86c5280159 100644 --- a/docs/topics/class-based-views/generic-editing.txt +++ b/docs/topics/class-based-views/generic-editing.txt @@ -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 diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index eaf2bbbaf2..e58dade736 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -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:: |