Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #19733 - deprecated ModelForms without 'fields' or 'exclude', a…

…nd 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.
  • Loading branch information...
commit f026a519aea8f3ea7ca339bfbbb007e1ee0068b0 1 parent 1e37cb3
Luke Plant authored February 21, 2013

Showing 34 changed files with 578 additions and 201 deletions. Show diff stats Hide diff stats

  1. 14  django/contrib/admin/options.py
  2. 1  django/contrib/auth/forms.py
  3. 9  django/contrib/contenttypes/generic.py
  4. 1  django/contrib/flatpages/forms.py
  5. 4  django/contrib/formtools/tests/wizard/test_forms.py
  6. 1  django/contrib/formtools/tests/wizard/wizardtests/tests.py
  7. 57  django/forms/models.py
  8. 11  django/views/generic/edit.py
  9. 2  docs/ref/class-based-views/generic-editing.txt
  10. 12  docs/ref/class-based-views/mixins-editing.txt
  11. 34  docs/ref/contrib/admin/index.txt
  12. 8  docs/ref/forms/models.txt
  13. 52  docs/releases/1.6.txt
  14. 1  docs/topics/auth/customizing.txt
  15. 37  docs/topics/class-based-views/generic-editing.txt
  16. 163  docs/topics/forms/modelforms.txt
  17. 2  tests/admin_validation/tests.py
  18. 1  tests/bug639/models.py
  19. 1  tests/foreign_object/tests.py
  20. 1  tests/forms_tests/tests/test_regressions.py
  21. 4  tests/forms_tests/tests/tests.py
  22. 1  tests/generic_relations/tests.py
  23. 44  tests/generic_views/test_edit.py
  24. 1  tests/generic_views/test_forms.py
  25. 9  tests/generic_views/views.py
  26. 1  tests/i18n/forms.py
  27. 10  tests/inline_formsets/tests.py
  28. 111  tests/model_forms/tests.py
  29. 57  tests/model_forms_regress/tests.py
  30. 89  tests/model_formsets/tests.py
  31. 28  tests/model_formsets_regress/tests.py
  32. 2  tests/model_inheritance_regress/tests.py
  33. 9  tests/modeladmin/tests.py
  34. 1  tests/timezones/forms.py
14  django/contrib/admin/options.py
@@ -5,7 +5,7 @@
5 5
 from django.conf import settings
6 6
 from django.forms.formsets import all_valid, DELETION_FIELD_NAME
7 7
 from django.forms.models import (modelform_factory, modelformset_factory,
8  
-    inlineformset_factory, BaseInlineFormSet)
  8
+    inlineformset_factory, BaseInlineFormSet, modelform_defines_fields)
9 9
 from django.contrib.contenttypes.models import ContentType
10 10
 from django.contrib.admin import widgets, helpers
11 11
 from django.contrib.admin.util import (unquote, flatten_fieldsets, get_deleted_objects,
@@ -488,6 +488,10 @@ def get_form(self, request, obj=None, **kwargs):
488 488
             "formfield_callback": partial(self.formfield_for_dbfield, request=request),
489 489
         }
490 490
         defaults.update(kwargs)
  491
+
  492
+        if defaults['fields'] is None and not modelform_defines_fields(defaults['form']):
  493
+            defaults['fields'] = forms.ALL_FIELDS
  494
+
491 495
         try:
492 496
             return modelform_factory(self.model, **defaults)
493 497
         except FieldError as e:
@@ -523,6 +527,10 @@ def get_changelist_form(self, request, **kwargs):
523 527
             "formfield_callback": partial(self.formfield_for_dbfield, request=request),
524 528
         }
525 529
         defaults.update(kwargs)
  530
+        if (defaults.get('fields') is None
  531
+            and not modelform_defines_fields(defaults.get('form'))):
  532
+            defaults['fields'] = forms.ALL_FIELDS
  533
+
526 534
         return modelform_factory(self.model, **defaults)
527 535
 
528 536
     def get_changelist_formset(self, request, **kwargs):
@@ -1527,6 +1535,10 @@ def is_valid(self):
1527 1535
                 return result
1528 1536
 
1529 1537
         defaults['form'] = DeleteProtectedModelForm
  1538
+
  1539
+        if defaults['fields'] is None and not modelform_defines_fields(defaults['form']):
  1540
+            defaults['fields'] = forms.ALL_FIELDS
  1541
+
1530 1542
         return inlineformset_factory(self.parent_model, self.model, **defaults)
1531 1543
 
1532 1544
     def get_fieldsets(self, request, obj=None):
1  django/contrib/auth/forms.py
@@ -130,6 +130,7 @@ class UserChangeForm(forms.ModelForm):
130 130
 
131 131
     class Meta:
132 132
         model = User
  133
+        fields = '__all__'
133 134
 
134 135
     def __init__(self, *args, **kwargs):
135 136
         super(UserChangeForm, self).__init__(*args, **kwargs)
9  django/contrib/contenttypes/generic.py
@@ -13,8 +13,9 @@
13 13
 from django.db.models.fields.related import ForeignObject, ForeignObjectRel
14 14
 from django.db.models.related import PathInfo
15 15
 from django.db.models.sql.where import Constraint
16  
-from django.forms import ModelForm
17  
-from django.forms.models import BaseModelFormSet, modelformset_factory, save_instance
  16
+from django.forms import ModelForm, ALL_FIELDS
  17
+from django.forms.models import (BaseModelFormSet, modelformset_factory, save_instance,
  18
+    modelform_defines_fields)
18 19
 from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets
19 20
 from django.contrib.contenttypes.models import ContentType
20 21
 from django.utils import six
@@ -480,6 +481,10 @@ def get_formset(self, request, obj=None, **kwargs):
480 481
             "exclude": exclude
481 482
         }
482 483
         defaults.update(kwargs)
  484
+
  485
+        if defaults['fields'] is None and not modelform_defines_fields(defaults['form']):
  486
+            defaults['fields'] = ALL_FIELDS
  487
+
483 488
         return generic_inlineformset_factory(self.model, **defaults)
484 489
 
485 490
 class GenericStackedInline(GenericInlineModelAdmin):
1  django/contrib/flatpages/forms.py
@@ -12,6 +12,7 @@ class FlatpageForm(forms.ModelForm):
12 12
 
13 13
     class Meta:
14 14
         model = FlatPage
  15
+        fields = '__all__'
15 16
 
16 17
     def clean_url(self):
17 18
         url = self.cleaned_data['url']
4  django/contrib/formtools/tests/wizard/test_forms.py
@@ -60,9 +60,11 @@ class Meta:
60 60
 class TestModelForm(forms.ModelForm):
61 61
     class Meta:
62 62
         model = TestModel
  63
+        fields = '__all__'
63 64
 
64 65
 
65  
-TestModelFormSet = forms.models.modelformset_factory(TestModel, form=TestModelForm, extra=2)
  66
+TestModelFormSet = forms.models.modelformset_factory(TestModel, form=TestModelForm, extra=2,
  67
+                                                     fields='__all__')
66 68
 
67 69
 
68 70
 class TestWizard(WizardView):
1  django/contrib/formtools/tests/wizard/wizardtests/tests.py
@@ -15,6 +15,7 @@
15 15
 class UserForm(forms.ModelForm):
16 16
     class Meta:
17 17
         model = User
  18
+        fields = '__all__'
18 19
 
19 20
 
20 21
 UserFormSet = forms.models.modelformset_factory(User, form=UserForm, extra=2)
57  django/forms/models.py
@@ -5,6 +5,8 @@
5 5
 
6 6
 from __future__ import absolute_import, unicode_literals
7 7
 
  8
+import warnings
  9
+
8 10
 from django.core.exceptions import ValidationError, NON_FIELD_ERRORS, FieldError
9 11
 from django.forms.fields import Field, ChoiceField
10 12
 from django.forms.forms import BaseForm, get_declared_fields
@@ -22,8 +24,12 @@
22 24
 __all__ = (
23 25
     'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model',
24 26
     'save_instance', 'ModelChoiceField', 'ModelMultipleChoiceField',
  27
+    'ALL_FIELDS',
25 28
 )
26 29
 
  30
+ALL_FIELDS = '__all__'
  31
+
  32
+
27 33
 def construct_instance(form, instance, fields=None, exclude=None):
28 34
     """
29 35
     Constructs and returns a model instance from the bound ``form``'s
@@ -211,7 +217,7 @@ def __new__(cls, name, bases, attrs):
211 217
         # of ('foo',)
212 218
         for opt in ['fields', 'exclude']:
213 219
             value = getattr(opts, opt)
214  
-            if isinstance(value, six.string_types):
  220
+            if isinstance(value, six.string_types) and value != ALL_FIELDS:
215 221
                 msg = ("%(model)s.Meta.%(opt)s cannot be a string. "
216 222
                        "Did you mean to type: ('%(value)s',)?" % {
217 223
                            'model': new_class.__name__,
@@ -222,6 +228,20 @@ def __new__(cls, name, bases, attrs):
222 228
 
223 229
         if opts.model:
224 230
             # If a model is defined, extract form fields from it.
  231
+
  232
+            if opts.fields is None and opts.exclude is None:
  233
+                # This should be some kind of assertion error once deprecation
  234
+                # cycle is complete.
  235
+                warnings.warn("Creating a ModelForm without either the 'fields' attribute "
  236
+                              "or the 'exclude' attribute is deprecated - form %s "
  237
+                              "needs updating" % name,
  238
+                              PendingDeprecationWarning)
  239
+
  240
+            if opts.fields == ALL_FIELDS:
  241
+                # sentinel for fields_for_model to indicate "get the list of
  242
+                # fields from the model"
  243
+                opts.fields = None
  244
+
225 245
             fields = fields_for_model(opts.model, opts.fields,
226 246
                                       opts.exclude, opts.widgets, formfield_callback)
227 247
             # make sure opts.fields doesn't specify an invalid field
@@ -394,7 +414,8 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
394 414
     Returns a ModelForm containing form fields for the given model.
395 415
 
396 416
     ``fields`` is an optional list of field names. If provided, only the named
397  
-    fields will be included in the returned fields.
  417
+    fields will be included in the returned fields. If omitted or '__all__',
  418
+    all fields will be used.
398 419
 
399 420
     ``exclude`` is an optional list of field names. If provided, the named
400 421
     fields will be excluded from the returned fields, even if they are listed
@@ -434,6 +455,15 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
434 455
         'formfield_callback': formfield_callback
435 456
     }
436 457
 
  458
+    # The ModelFormMetaclass will trigger a similar warning/error, but this will
  459
+    # be difficult to debug for code that needs updating, so we produce the
  460
+    # warning here too.
  461
+    if (getattr(Meta, 'fields', None) is None and
  462
+        getattr(Meta, 'exclude', None) is None):
  463
+        warnings.warn("Calling modelform_factory without defining 'fields' or "
  464
+                      "'exclude' explicitly is deprecated",
  465
+                      PendingDeprecationWarning, stacklevel=2)
  466
+
437 467
     # Instatiate type(form) in order to use the same metaclass as form.
438 468
     return type(form)(class_name, (form,), form_class_attrs)
439 469
 
@@ -701,6 +731,21 @@ def modelformset_factory(model, form=ModelForm, formfield_callback=None,
701 731
     """
702 732
     Returns a FormSet class for the given Django model class.
703 733
     """
  734
+    # modelform_factory will produce the same warning/error, but that will be
  735
+    # difficult to debug for code that needs upgrading, so we produce the
  736
+    # warning here too. This logic is reproducing logic inside
  737
+    # modelform_factory, but it can be removed once the deprecation cycle is
  738
+    # complete, since the validation exception will produce a helpful
  739
+    # stacktrace.
  740
+    meta = getattr(form, 'Meta', None)
  741
+    if meta is None:
  742
+        meta = type(str('Meta'), (object,), {})
  743
+    if (getattr(meta, 'fields', fields) is None and
  744
+        getattr(meta, 'exclude', exclude) is None):
  745
+        warnings.warn("Calling modelformset_factory without defining 'fields' or "
  746
+                      "'exclude' explicitly is deprecated",
  747
+                      PendingDeprecationWarning, stacklevel=2)
  748
+
704 749
     form = modelform_factory(model, form=form, fields=fields, exclude=exclude,
705 750
                              formfield_callback=formfield_callback,
706 751
                              widgets=widgets)
@@ -1091,3 +1136,11 @@ def _has_changed(self, initial, data):
1091 1136
         initial_set = set([force_text(value) for value in self.prepare_value(initial)])
1092 1137
         data_set = set([force_text(value) for value in data])
1093 1138
         return data_set != initial_set
  1139
+
  1140
+
  1141
+def modelform_defines_fields(form_class):
  1142
+    return (form_class is not None and (
  1143
+            hasattr(form_class, '_meta') and
  1144
+            (form_class._meta.fields is not None or
  1145
+             form_class._meta.exclude is not None)
  1146
+            ))
11  django/views/generic/edit.py
... ...
@@ -1,3 +1,5 @@
  1
+import warnings
  2
+
1 3
 from django.forms import models as model_forms
2 4
 from django.core.exceptions import ImproperlyConfigured
3 5
 from django.http import HttpResponseRedirect
@@ -95,7 +97,14 @@ def get_form_class(self):
95 97
                 # Try to get a queryset and extract the model class
96 98
                 # from that
97 99
                 model = self.get_queryset().model
98  
-            return model_forms.modelform_factory(model)
  100
+
  101
+            fields = getattr(self, 'fields', None)
  102
+            if fields is None:
  103
+                warnings.warn("Using ModelFormMixin (base class of %s) without "
  104
+                              "the 'fields' attribute is deprecated." % self.__class__.__name__,
  105
+                              PendingDeprecationWarning)
  106
+
  107
+            return model_forms.modelform_factory(model, fields=fields)
99 108
 
100 109
     def get_form_kwargs(self):
101 110
         """
2  docs/ref/class-based-views/generic-editing.txt
@@ -110,6 +110,7 @@ CreateView
110 110
 
111 111
         class AuthorCreate(CreateView):
112 112
             model = Author
  113
+            fields = ['name']
113 114
 
114 115
 UpdateView
115 116
 ----------
@@ -152,6 +153,7 @@ UpdateView
152 153
 
153 154
         class AuthorUpdate(UpdateView):
154 155
             model = Author
  156
+            fields = ['name']
155 157
 
156 158
 DeleteView
157 159
 ----------
12  docs/ref/class-based-views/mixins-editing.txt
@@ -116,6 +116,18 @@ ModelFormMixin
116 116
         by examining ``self.object`` or
117 117
         :attr:`~django.views.generic.detail.SingleObjectMixin.queryset`.
118 118
 
  119
+    .. attribute:: fields
  120
+
  121
+        .. versionadded:: 1.6
  122
+
  123
+        A list of names of fields. This is interpreted the same way as the
  124
+        ``Meta.fields`` attribute of :class:`~django.forms.ModelForm`.
  125
+
  126
+        This is a required attribute if you are generating the form class
  127
+        automatically (e.g. using ``model``). Omitting this attribute will
  128
+        result in all fields being used, but this behaviour is deprecated
  129
+        and will be removed in Django 1.8.
  130
+
119 131
     .. attribute:: success_url
120 132
 
121 133
         The URL to redirect to when the form is successfully processed.
34  docs/ref/contrib/admin/index.txt
@@ -337,6 +337,22 @@ subclass::
337 337
 
338 338
     .. admonition:: Note
339 339
 
  340
+        .. versionchanged:: 1.6
  341
+
  342
+        If you define the ``Meta.model`` attribute on a
  343
+        :class:`~django.forms.ModelForm`, you must also define the
  344
+        ``Meta.fields`` attribute (or the ``Meta.exclude`` attribute). However,
  345
+        since the admin has its own way of defining fields, the ``Meta.fields``
  346
+        attribute will be ignored.
  347
+
  348
+        If the ``ModelForm`` is only going to be used for the admin, the easiest
  349
+        solution is to omit the ``Meta.model`` attribute, since ``ModelAdmin``
  350
+        will provide the correct model to use. Alternatively, you can set
  351
+        ``fields = []`` in the ``Meta`` class to satisfy the validation on the
  352
+        ``ModelForm``.
  353
+
  354
+    .. admonition:: Note
  355
+
340 356
         If your ``ModelForm`` and ``ModelAdmin`` both define an ``exclude``
341 357
         option then ``ModelAdmin`` takes precedence::
342 358
 
@@ -1283,13 +1299,24 @@ templates used by the :class:`ModelAdmin` views:
1283 1299
     on the changelist page. To use a custom form, for example::
1284 1300
 
1285 1301
         class MyForm(forms.ModelForm):
1286  
-            class Meta:
1287  
-                model = MyModel
  1302
+            pass
1288 1303
 
1289 1304
         class MyModelAdmin(admin.ModelAdmin):
1290 1305
             def get_changelist_form(self, request, **kwargs):
1291 1306
                 return MyForm
1292 1307
 
  1308
+    .. admonition:: Note
  1309
+
  1310
+        .. versionchanged:: 1.6
  1311
+
  1312
+        If you define the ``Meta.model`` attribute on a
  1313
+        :class:`~django.forms.ModelForm`, you must also define the
  1314
+        ``Meta.fields`` attribute (or the ``Meta.exclude`` attribute). However,
  1315
+        ``ModelAdmin`` ignores this value, overriding it with the
  1316
+        :attr:`ModelAdmin.list_editable` attribute. The easiest solution is to
  1317
+        omit the ``Meta.model`` attribute, since ``ModelAdmin`` will provide the
  1318
+        correct model to use.
  1319
+
1293 1320
 .. method::  ModelAdmin.get_changelist_formset(self, request, **kwargs)
1294 1321
 
1295 1322
     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
1490 1517
 any field::
1491 1518
 
1492 1519
     class MyArticleAdminForm(forms.ModelForm):
1493  
-        class Meta:
1494  
-            model = Article
1495  
-
1496 1520
         def clean_name(self):
1497 1521
             # do something that validates your data
1498 1522
             return self.cleaned_data["name"]
8  docs/ref/forms/models.txt
@@ -25,6 +25,14 @@ Model Form Functions
25 25
 
26 26
     See :ref:`modelforms-factory` for example usage.
27 27
 
  28
+    .. versionchanged:: 1.6
  29
+
  30
+    You must provide the list of fields explicitly, either via keyword arguments
  31
+    ``fields`` or ``exclude``, or the corresponding attributes on the form's
  32
+    inner ``Meta`` class. See :ref:`modelforms-selecting-fields` for more
  33
+    information. Omitting any definition of the fields to use will result in all
  34
+    fields being used, but this behaviour is deprecated.
  35
+
28 36
 .. 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)
29 37
 
30 38
     Returns a ``FormSet`` class for the given ``model`` class.
52  docs/releases/1.6.txt
@@ -532,3 +532,55 @@ including it in an URLconf, simply replace::
532 532
 with::
533 533
 
534 534
     (r'^prefix/(?P<content_type_id>\d+)/(?P<object_id>.*)/$', 'django.contrib.contenttypes.views.shortcut'),
  535
+
  536
+``ModelForm`` without ``fields`` or ``exclude``
  537
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  538
+
  539
+Previously, if you wanted a :class:`~django.forms.ModelForm` to use all fields on
  540
+the model, you could simply omit the ``Meta.fields`` attribute, and all fields
  541
+would be used.
  542
+
  543
+This can lead to security problems where fields are added to the model and,
  544
+unintentionally, automatically become editable by end users. In some cases,
  545
+particular with boolean fields, it is possible for this problem to be completely
  546
+invisible. This is a form of `Mass assignment vulnerability
  547
+<http://en.wikipedia.org/wiki/Mass_assignment_vulnerability>`_.
  548
+
  549
+For this reason, this behaviour is deprecated, and using the ``Meta.exclude``
  550
+option is strongly discouraged. Instead, all fields that are intended for
  551
+inclusion in the form should be listed explicitly in the ``fields`` attribute.
  552
+
  553
+If this security concern really does not apply in your case, there is a shortcut
  554
+to explicitly indicate that all fields should be used - use the special value
  555
+``"__all__"`` for the fields attribute::
  556
+
  557
+    class MyModelForm(ModelForm):
  558
+        class Meta:
  559
+            fields = "__all__"
  560
+            model = MyModel
  561
+
  562
+If you have custom ``ModelForms`` that only need to be used in the admin, there
  563
+is another option. The admin has its own methods for defining fields
  564
+(``fieldsets`` etc.), and so adding a list of fields to the ``ModelForm`` is
  565
+redundant. Instead, simply omit the ``Meta`` inner class of the ``ModelForm``,
  566
+or omit the ``Meta.model`` attribute. Since the ``ModelAdmin`` subclass knows
  567
+which model it is for, it can add the necessary attributes to derive a
  568
+functioning ``ModelForm``. This behaviour also works for earlier Django
  569
+versions.
  570
+
  571
+``UpdateView`` and ``CreateView`` without explicit fields
  572
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  573
+
  574
+The generic views :class:`~django.views.generic.edit.CreateView` and
  575
+:class:`~django.views.generic.edit.UpdateView`, and anything else derived from
  576
+:class:`~django.views.generic.edit.ModelFormMixin`, are vulnerable to the
  577
+security problem described in the section above, because they can automatically
  578
+create a ``ModelForm`` that uses all fields for a model.
  579
+
  580
+For this reason, if you use these views for editing models, you must also supply
  581
+the ``fields`` attribute, which is a list of model fields and works in the same
  582
+way as the :class:`~django.forms.ModelForm` ``Meta.fields`` attribute. Alternatively,
  583
+you can set set the ``form_class`` attribute to a ``ModelForm`` that explicitly
  584
+defines the fields to be used. Defining an ``UpdateView`` or ``CreateView``
  585
+subclass to be used with a model but without an explicit list of fields is
  586
+deprecated.
1  docs/topics/auth/customizing.txt
@@ -1051,6 +1051,7 @@ code would be required in the app's ``admin.py`` file::
1051 1051
 
1052 1052
         class Meta:
1053 1053
             model = MyUser
  1054
+            fields = ['email', 'password', 'date_of_birth', 'is_active', 'is_admin']
1054 1055
 
1055 1056
         def clean_password(self):
1056 1057
             # Regardless of what the user provides, return the initial value.
37  docs/topics/class-based-views/generic-editing.txt
@@ -114,9 +114,11 @@ here; we don't have to write any logic ourselves::
114 114
 
115 115
     class AuthorCreate(CreateView):
116 116
         model = Author
  117
+        fields = ['name']
117 118
 
118 119
     class AuthorUpdate(UpdateView):
119 120
         model = Author
  121
+        fields = ['name']
120 122
 
121 123
     class AuthorDelete(DeleteView):
122 124
         model = Author
@@ -126,6 +128,17 @@ here; we don't have to write any logic ourselves::
126 128
     We have to use :func:`~django.core.urlresolvers.reverse_lazy` here, not
127 129
     just ``reverse`` as the urls are not loaded when the file is imported.
128 130
 
  131
+.. versionchanged:: 1.6
  132
+
  133
+In Django 1.6, the ``fields`` attribute was added, which works the same way as
  134
+the ``fields`` attribute on the inner ``Meta`` class on
  135
+:class:`~django.forms.ModelForm`.
  136
+
  137
+Omitting the fields attribute will work as previously, but is deprecated and
  138
+this attribute will be required from 1.8 (unless you define the form class in
  139
+another way).
  140
+
  141
+
129 142
 Finally, we hook these new views into the URLconf::
130 143
 
131 144
     # urls.py
@@ -177,33 +190,17 @@ the foreign key relation to the model::
177 190
 
178 191
         # ...
179 192
 
180  
-Create a custom :class:`~django.forms.ModelForm` in order to exclude the
181  
-``created_by`` field and prevent the user from editing it:
182  
-
183  
-.. code-block:: python
184  
-
185  
-    # forms.py
186  
-    from django import forms
187  
-    from myapp.models import Author
188  
-
189  
-    class AuthorForm(forms.ModelForm):
190  
-        class Meta:
191  
-            model = Author
192  
-            exclude = ('created_by',)
193  
-
194  
-In the view, use the custom
195  
-:attr:`~django.views.generic.edit.FormMixin.form_class` and override
196  
-:meth:`~django.views.generic.edit.ModelFormMixin.form_valid()` to add the
197  
-user::
  193
+In the view, ensure that you exclude ``created_by`` in the list of fields to
  194
+edit, and override
  195
+:meth:`~django.views.generic.edit.ModelFormMixin.form_valid()` to add the user::
198 196
 
199 197
     # views.py
200 198
     from django.views.generic.edit import CreateView
201 199
     from myapp.models import Author
202  
-    from myapp.forms import AuthorForm
203 200
 
204 201
     class AuthorCreate(CreateView):
205  
-        form_class = AuthorForm
206 202
         model = Author
  203
+        fields = ['name']
207 204
 
208 205
         def form_valid(self, form):
209 206
             form.instance.created_by = self.request.user
163  docs/topics/forms/modelforms.txt
@@ -28,6 +28,7 @@ For example::
28 28
     >>> class ArticleForm(ModelForm):
29 29
     ...     class Meta:
30 30
     ...         model = Article
  31
+    ...         fields = ['pub_date', 'headline', 'content', 'reporter']
31 32
 
32 33
     # Creating a form to add an article.
33 34
     >>> form = ArticleForm()
@@ -39,11 +40,13 @@ For example::
39 40
 Field types
40 41
 -----------
41 42
 
42  
-The generated ``Form`` class will have a form field for every model field. Each
43  
-model field has a corresponding default form field. For example, a
44  
-``CharField`` on a model is represented as a ``CharField`` on a form. A
45  
-model ``ManyToManyField`` is represented as a ``MultipleChoiceField``. Here is
46  
-the full list of conversions:
  43
+The generated ``Form`` class will have a form field for every model field
  44
+specified, in the order specified in the ``fields`` attribute.
  45
+
  46
+Each model field has a corresponding default form field. For example, a
  47
+``CharField`` on a model is represented as a ``CharField`` on a form. A model
  48
+``ManyToManyField`` is represented as a ``MultipleChoiceField``. Here is the
  49
+full list of conversions:
47 50
 
48 51
 ===============================  ========================================
49 52
 Model field                      Form field
@@ -168,10 +171,13 @@ Consider this set of models::
168 171
     class AuthorForm(ModelForm):
169 172
         class Meta:
170 173
             model = Author
  174
+            fields = ['name', 'title', 'birth_date']
171 175
 
172 176
     class BookForm(ModelForm):
173 177
         class Meta:
174 178
             model = Book
  179
+            fields = ['name', 'authors']
  180
+
175 181
 
176 182
 With these models, the ``ModelForm`` subclasses above would be roughly
177 183
 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
288 294
 hence whether ``request.FILES`` must be passed to the form), etc. See
289 295
 :ref:`binding-uploaded-files` for more information.
290 296
 
291  
-Using a subset of fields on the form
292  
-------------------------------------
  297
+.. _modelforms-selecting-fields:
293 298
 
294  
-In some cases, you may not want all the model fields to appear on the generated
295  
-form. There are three ways of telling ``ModelForm`` to use only a subset of the
296  
-model fields:
  299
+Selecting the fields to use
  300
+---------------------------
297 301
 
298  
-1. Set ``editable=False`` on the model field. As a result, *any* form
299  
-   created from the model via ``ModelForm`` will not include that
300  
-   field.
  302
+It is strongly recommended that you explicitly set all fields that should be
  303
+edited in the form using the ``fields`` attribute. Failure to do so can easily
  304
+lead to security problems when a form unexpectedly allows a user to set certain
  305
+fields, especially when new fields are added to a model. Depending on how the
  306
+form is rendered, the problem may not even be visible on the web page.
301 307
 
302  
-2. Use the ``fields`` attribute of the ``ModelForm``'s inner ``Meta``
303  
-   class.  This attribute, if given, should be a list of field names
304  
-   to include in the form. The order in which the fields names are specified
305  
-   in that list is respected when the form renders them.
  308
+The alternative approach would be to include all fields automatically, or
  309
+blacklist only some. This fundamental approach is known to be much less secure
  310
+and has led to serious exploits on major websites (e.g. `GitHub
  311
+<https://github.com/blog/1068-public-key-security-vulnerability-and-mitigation>`_).
306 312
 
307  
-3. Use the ``exclude`` attribute of the ``ModelForm``'s inner ``Meta``
308  
-   class.  This attribute, if given, should be a list of field names
309  
-   to exclude from the form.
  313
+There are, however, two shortcuts available for cases where you can guarantee
  314
+these security concerns do not apply to you:
310 315
 
311  
-For example, if you want a form for the ``Author`` model (defined
312  
-above) that includes only the ``name`` and ``birth_date`` fields, you would
313  
-specify ``fields`` or ``exclude`` like this::
  316
+1. Set the ``fields`` attribute to the special value ``'__all__'`` to indicate
  317
+   that all fields in the model should be used. For example::
314 318
 
315  
-    class PartialAuthorForm(ModelForm):
316  
-        class Meta:
317  
-            model = Author
318  
-            fields = ('name', 'birth_date')
  319
+       class AuthorForm(ModelForm):
  320
+           class Meta:
  321
+               model = Author
  322
+               fields = '__all__'
319 323
 
320  
-    class PartialAuthorForm(ModelForm):
321  
-        class Meta:
322  
-            model = Author
323  
-            exclude = ('title',)
  324
+2. Set the ``exclude`` attribute of the ``ModelForm``'s inner ``Meta`` class to
  325
+   a list of fields to be excluded from the form.
  326
+
  327
+   For example::
  328
+
  329
+       class PartialAuthorForm(ModelForm):
  330
+           class Meta:
  331
+               model = Author
  332
+               exclude = ['title']
  333
+
  334
+   Since the ``Author`` model has the 3 fields ``name``, ``title`` and
  335
+   ``birth_date``, this will result in the fields ``name`` and ``birth_date``
  336
+   being present on the form.
  337
+
  338
+If either of these are used, the order the fields appear in the form will be the
  339
+order the fields are defined in the model, with ``ManyToManyField`` instances
  340
+appearing last.
  341
+
  342
+In addition, Django applies the following rule: if you set ``editable=False`` on
  343
+the model field, *any* form created from the model via ``ModelForm`` will not
  344
+include that field.
  345
+
  346
+.. versionchanged:: 1.6
  347
+
  348
+    Before version 1.6, the ``'__all__'`` shortcut did not exist, but omitting
  349
+    the ``fields`` attribute had the same effect. Omitting both ``fields`` and
  350
+    ``exclude`` is now deprecated, but will continue to work as before until
  351
+    version 1.8
324 352
 
325  
-Since the Author model has only 3 fields, 'name', 'title', and
326  
-'birth_date', the forms above will contain exactly the same fields.
327 353
 
328 354
 .. note::
329 355
 
330  
-    If you specify ``fields`` or ``exclude`` when creating a form with
331  
-    ``ModelForm``, then the fields that are not in the resulting form
  356
+    Any fields not included in a form by the above logic
332 357
     will not be set by the form's ``save()`` method. Also, if you
333 358
     manually add the excluded fields back to the form, they will not
334 359
     be initialized from the model instance.
@@ -401,15 +426,19 @@ field, you could do the following::
401 426
 
402 427
         class Meta:
403 428
             model = Article
  429
+            fields = ['pub_date', 'headline', 'content', 'reporter']
  430
+
404 431
 
405 432
 If you want to override a field's default label, then specify the ``label``
406 433
 parameter when declaring the form field::
407 434
 
408  
-   >>> class ArticleForm(ModelForm):
409  
-   ...     pub_date = DateField(label='Publication date')
410  
-   ...
411  
-   ...     class Meta:
412  
-   ...         model = Article
  435
+    class ArticleForm(ModelForm):
  436
+        pub_date = DateField(label='Publication date')
  437
+
  438
+        class Meta:
  439
+            model = Article
  440
+            fields = ['pub_date', 'headline', 'content', 'reporter']
  441
+
413 442
 
414 443
 .. note::
415 444
 
@@ -436,6 +465,7 @@ parameter when declaring the form field::
436 465
 
437 466
             class Meta:
438 467
                 model = Article
  468
+                fields = ['headline', 'content']
439 469
 
440 470
     You must ensure that the type of the form field can be used to set the
441 471
     contents of the corresponding model field. When they are not compatible,
@@ -444,30 +474,6 @@ parameter when declaring the form field::
444 474
     See the :doc:`form field documentation </ref/forms/fields>` for more information
445 475
     on fields and their arguments.
446 476
 
447  
-Changing the order of fields
448  
-----------------------------
449  
-
450  
-By default, a ``ModelForm`` will render fields in the same order that they are
451  
-defined on the model, with ``ManyToManyField`` instances appearing last. If
452  
-you want to change the order in which fields are rendered, you can use the
453  
-``fields`` attribute on the ``Meta`` class.
454  
-
455  
-The ``fields`` attribute defines the subset of model fields that will be
456  
-rendered, and the order in which they will be rendered. For example given this
457  
-model::
458  
-
459  
-    class Book(models.Model):
460  
-        author = models.ForeignKey(Author)
461  
-        title = models.CharField(max_length=100)
462  
-
463  
-the ``author`` field would be rendered first. If we wanted the title field
464  
-to be rendered first, we could specify the following ``ModelForm``::
465  
-
466  
-    >>> class BookForm(ModelForm):
467  
-    ...     class Meta:
468  
-    ...         model = Book
469  
-    ...         fields = ('title', 'author')
470  
-
471 477
 .. _overriding-modelform-clean-method:
472 478
 
473 479
 Overriding the clean() method
@@ -550,21 +556,19 @@ definition. This may be more convenient if you do not have many customizations
550 556
 to make::
551 557
 
552 558
     >>> from django.forms.models import modelform_factory
553  
-    >>> BookForm = modelform_factory(Book)
  559
+    >>> BookForm = modelform_factory(Book, fields=("author", "title"))
554 560
 
555 561
 This can also be used to make simple modifications to existing forms, for
556  
-example by specifying which fields should be displayed::
557  
-
558  
-    >>> Form = modelform_factory(Book, form=BookForm, fields=("author",))
559  
-
560  
-... or which fields should be excluded::
561  
-
562  
-    >>> Form = modelform_factory(Book, form=BookForm, exclude=("title",))
563  
-
564  
-You can also specify the widgets to be used for a given field::
  562
+example by specifying the widgets to be used for a given field::
565 563
 
566 564
     >>> from django.forms import Textarea
567  
-    >>> Form = modelform_factory(Book, form=BookForm, widgets={"title": Textarea()})
  565
+    >>> Form = modelform_factory(Book, form=BookForm,
  566
+                                 widgets={"title": Textarea()})
  567
+
  568
+The fields to include can be specified using the ``fields`` and ``exclude``
  569
+keyword arguments, or the corresponding attributes on the ``ModelForm`` inner
  570
+``Meta`` class. Please see the ``ModelForm`` :ref:`modelforms-selecting-fields`
  571
+documentation.
568 572
 
569 573
 .. _model-formsets:
570 574
 
@@ -688,11 +692,10 @@ database. If a given instance's data didn't change in the bound data, the
688 692
 instance won't be saved to the database and won't be included in the return
689 693
 value (``instances``, in the above example).
690 694
 
691  
-When fields are missing from the form (for example because they have
692  
-been excluded), these fields will not be set by the ``save()``
693  
-method. You can find more information about this restriction, which
694  
-also holds for regular ``ModelForms``, in `Using a subset of fields on
695  
-the form`_.
  695
+When fields are missing from the form (for example because they have been
  696
+excluded), these fields will not be set by the ``save()`` method. You can find
  697
+more information about this restriction, which also holds for regular
  698
+``ModelForms``, in `Selecting the fields to use`_.
696 699
 
697 700
 Pass ``commit=False`` to return the unsaved model instances::
698 701
 
2  tests/admin_validation/tests.py
@@ -285,6 +285,8 @@ class SongForm(forms.ModelForm):
285 285
             extra_data = forms.CharField()
286 286
             class Meta:
287 287
                 model = Song
  288
+                fields = '__all__'
  289
+
288 290
 
289 291
         class FieldsOnFormOnlyAdmin(admin.ModelAdmin):
290 292
             form = SongForm
1  tests/bug639/models.py
@@ -25,3 +25,4 @@ def save(self, force_insert=False, force_update=False):
25 25
 class PhotoForm(ModelForm):
26 26
     class Meta:
27 27
         model = Photo
  28
+        fields = '__all__'
1  tests/foreign_object/tests.py
@@ -322,6 +322,7 @@ class FormsTests(TestCase):
322 322
     class ArticleForm(forms.ModelForm):
323 323
         class Meta:
324 324
             model = Article
  325
+            fields = '__all__'
325 326
 
326 327
     def test_foreign_object_form(self):
327 328
         # A very crude test checking that the non-concrete fields do not get form fields.
1  tests/forms_tests/tests/test_regressions.py
@@ -139,6 +139,7 @@ def test_regression_14234(self):
139 139
         class CheeseForm(ModelForm):
140 140
             class Meta:
141 141
                 model = Cheese
  142
+                fields = '__all__'
142 143
 
143 144
         form = CheeseForm({
144 145
             'name': 'Brie',
4  tests/forms_tests/tests/tests.py
@@ -17,11 +17,13 @@
17 17
 class ChoiceFieldForm(ModelForm):
18 18
     class Meta:
19 19
         model = ChoiceFieldModel
  20
+        fields = '__all__'
20 21
 
21 22
 
22 23
 class OptionalMultiChoiceModelForm(ModelForm):
23 24
     class Meta:
24 25
         model = OptionalMultiChoiceModel
  26
+        fields = '__all__'
25 27
 
26 28
 
27 29
 class FileForm(Form):
@@ -139,6 +141,7 @@ def test_boundary_conditions(self):
139 141
         class BoundaryForm(ModelForm):
140 142
             class Meta:
141 143
                 model = BoundaryModel
  144
+                fields = '__all__'
142 145
 
143 146
         f = BoundaryForm({'positive_integer': 100})
144 147
         self.assertTrue(f.is_valid())
@@ -154,6 +157,7 @@ def test_formfield_initial(self):
154 157
         class DefaultsForm(ModelForm):
155 158
             class Meta:
156 159
                 model = Defaults
  160
+                fields = '__all__'
157 161
 
158 162
         self.assertEqual(DefaultsForm().fields['name'].initial, 'class default value')
159 163
         self.assertEqual(DefaultsForm().fields['def_date'].initial, datetime.date(1980, 1, 1))
1  tests/generic_relations/tests.py
@@ -247,6 +247,7 @@ class CustomWidget(forms.TextInput):
247 247
 class TaggedItemForm(forms.ModelForm):
248 248
     class Meta:
249 249
         model = TaggedItem
  250
+        fields = '__all__'
250 251
         widgets = {'tag': CustomWidget}
251 252
 
252 253
 class GenericInlineFormsetTest(TestCase):
44  tests/generic_views/test_edit.py
... ...
@@ -1,12 +1,14 @@
1 1
 from __future__ import absolute_import
2 2
 
  3
+import warnings
  4
+
3 5
 from django.core.exceptions import ImproperlyConfigured
4 6
 from django.core.urlresolvers import reverse
5 7
 from django import forms
6 8
 from django.test import TestCase
7 9
 from django.utils.unittest import expectedFailure
8 10
 from django.views.generic.base import View
9  
-from django.views.generic.edit import FormMixin
  11
+from django.views.generic.edit import FormMixin, CreateView, UpdateView
10 12
 
11 13
 from . import views
12 14
 from .models import Artist, Author
@@ -34,6 +36,7 @@ def test_get_form(self):
34 36
         form_class = views.AuthorGetQuerySetFormView().get_form_class()
35 37
         self.assertEqual(form_class._meta.model, Author)
36 38
 
  39
+
37 40
 class CreateViewTests(TestCase):
38 41
     urls = 'generic_views.urls'
39 42
 
@@ -112,6 +115,45 @@ def test_create_restricted(self):
112 115
         self.assertEqual(res.status_code, 302)
113 116
         self.assertRedirects(res, 'http://testserver/accounts/login/?next=/edit/authors/create/restricted/')
114 117
 
  118
+    def test_create_view_with_restricted_fields(self):
  119
+
  120
+        class MyCreateView(CreateView):
  121
+            model = Author
  122
+            fields = ['name']
  123
+
  124
+        self.assertEqual(list(MyCreateView().get_form_class().base_fields),
  125
+                         ['name'])
  126
+
  127
+    def test_create_view_all_fields(self):
  128
+
  129
+        with warnings.catch_warnings(record=True) as w:
  130
+            warnings.simplefilter("always", PendingDeprecationWarning)
  131
+
  132
+            class MyCreateView(CreateView):
  133
+                model = Author
  134
+                fields = '__all__'
  135
+
  136
+            self.assertEqual(list(MyCreateView().get_form_class().base_fields),
  137
+                             ['name', 'slug'])
  138
+        self.assertEqual(len(w), 0)
  139
+
  140
+
  141
+    def test_create_view_without_explicit_fields(self):
  142
+
  143
+        with warnings.catch_warnings(record=True) as w:
  144
+            warnings.simplefilter("always", PendingDeprecationWarning)
  145
+
  146
+            class MyCreateView(CreateView):
  147
+                model = Author
  148
+
  149
+            # Until end of the deprecation cycle, should still create the form
  150
+            # as before:
  151
+            self.assertEqual(list(MyCreateView().get_form_class().base_fields),
  152
+                             ['name', 'slug'])
  153
+
  154
+        # but with a warning:
  155
+        self.assertEqual(w[0].category, PendingDeprecationWarning)
  156
+
115 157
 
116 158
 class UpdateViewTests(TestCase):
117 159
     urls = 'generic_views.urls'
1  tests/generic_views/test_forms.py
@@ -11,6 +11,7 @@ class AuthorForm(forms.ModelForm):
11 11
 
12 12
     class Meta:
13 13
         model = Author
  14
+        fields = ['name', 'slug']
14 15
 
15 16
 
16 17
 class ContactForm(forms.Form):
9  tests/generic_views/views.py
@@ -85,15 +85,18 @@ class ContactView(generic.FormView):
85 85
 
86 86
 class ArtistCreate(generic.CreateView):
87 87
     model = Artist
  88
+    fields = '__all__'
88 89
 
89 90
 
90 91
 class NaiveAuthorCreate(generic.CreateView):
91 92
     queryset = Author.objects.all()
  93
+    fields = '__all__'
92 94
 
93 95
 
94 96
 class AuthorCreate(generic.CreateView):
95 97
     model = Author
96 98
     success_url = '/list/authors/'
  99
+    fields = '__all__'
97 100
 
98 101
 
99 102
 class SpecializedAuthorCreate(generic.CreateView):
@@ -112,19 +115,23 @@ class AuthorCreateRestricted(AuthorCreate):
112 115
 
113 116
 class ArtistUpdate(generic.UpdateView):
114 117
     model = Artist
  118
+    fields = '__all__'
115 119
 
116 120
 
117 121
 class NaiveAuthorUpdate(generic.UpdateView):
118 122
     queryset = Author.objects.all()
  123
+    fields = '__all__'
119 124
 
120 125
 
121 126
 class AuthorUpdate(generic.UpdateView):
122 127
     model = Author
123 128
     success_url = '/list/authors/'
  129
+    fields = '__all__'
124 130
 
125 131
 
126 132
 class OneAuthorUpdate(generic.UpdateView):
127 133
     success_url = '/list/authors/'
  134
+    fields = '__all__'
128 135
 
129 136
     def get_object(self):
130 137
         return Author.objects.get(pk=1)
@@ -184,6 +191,8 @@ class BookDetail(BookConfig, generic.DateDetailView):
184 191
     pass
185 192
 
186 193
 class AuthorGetQuerySetFormView(generic.edit.ModelFormMixin):
  194
+    fields = '__all__'
  195
+
187 196
     def get_queryset(self):
188 197
         return Author.objects.all()
189 198
 
1  tests/i18n/forms.py
@@ -24,3 +24,4 @@ class CompanyForm(forms.ModelForm):
24 24
 
25 25
     class Meta:
26 26
         model = Company
  27
+        fields = '__all__'
10  tests/inline_formsets/tests.py
@@ -10,7 +10,7 @@
10 10
 class DeletionTests(TestCase):
11 11
 
12 12
     def test_deletion(self):
13  
-        PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True)
  13
+        PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True, fields="__all__")
14 14
         poet = Poet.objects.create(name='test')
15 15
         poem = poet.poem_set.create(name='test poem')
16 16
         data = {
@@ -32,7 +32,7 @@ def test_add_form_deletion_when_invalid(self):
32 32
         Make sure that an add form that is filled out, but marked for deletion
33 33
         doesn't cause validation errors.
34 34
         """
35  
-        PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True)
  35
+        PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True, fields="__all__")
36 36
         poet = Poet.objects.create(name='test')
37 37
         data = {
38 38
             'poem_set-TOTAL_FORMS': '1',
@@ -60,7 +60,7 @@ def test_change_form_deletion_when_invalid(self):
60 60
         Make sure that a change form that is filled out, but marked for deletion
61 61
         doesn't cause validation errors.
62 62
         """
63  
-        PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True)
  63
+        PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True, fields="__all__")
64 64
         poet = Poet.objects.create(name='test')
65 65
         poem = poet.poem_set.create(name='test poem')
66 66
         data = {
@@ -115,8 +115,8 @@ def test_inline_formset_factory(self):
115 115
         """
116 116
         These should both work without a problem.
117 117
         """
118  
-        inlineformset_factory(Parent, Child, fk_name='mother')
119  
-        inlineformset_factory(Parent, Child, fk_name='father')
  118
+        inlineformset_factory(Parent, Child, fk_name='mother', fields="__all__")
  119
+        inlineformset_factory(Parent, Child, fk_name='father', fields="__all__")
120 120
 
121 121
     def test_exception_on_unspecified_foreign_key(self):
122 122
         """
111  tests/model_forms/tests.py
@@ -3,6 +3,7 @@
3 3
 import datetime
4 4
 import os
5 5
 from decimal import Decimal
  6
+import warnings
6 7
 
7 8
 from django import forms
8 9
 from django.core.exceptions import FieldError
@@ -30,19 +31,25 @@
30 31
     class ImageFileForm(forms.ModelForm):
31 32
         class Meta:
32 33
             model = ImageFile
  34
+            fields = '__all__'
  35
+
33 36
 
34 37
     class OptionalImageFileForm(forms.ModelForm):
35 38
         class Meta:
36 39
             model = OptionalImageFile
  40
+            fields = '__all__'
  41
+
37 42
 
38 43
 class ProductForm(forms.ModelForm):
39 44
     class Meta:
40 45
         model = Product
  46
+        fields = '__all__'
41 47
 
42 48
 
43 49
 class PriceForm(forms.ModelForm):
44 50
     class Meta:
45 51
         model = Price
  52
+        fields = '__all__'
46 53
 
47 54
 
48 55
 class BookForm(forms.ModelForm):
@@ -66,11 +73,13 @@ class Meta:
66 73
 class PostForm(forms.ModelForm):
67 74
     class Meta:
68 75
         model = Post
  76
+        fields = '__all__'
69 77
 
70 78
 
71 79
 class DerivedPostForm(forms.ModelForm):
72 80
     class Meta:
73 81
         model = DerivedPost
  82
+        fields = '__all__'
74 83
 
75 84
 
76