Browse Source

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.
Luke Plant 12 years ago
parent
commit
f026a519ae

+ 13 - 1
django/contrib/admin/options.py

@@ -5,7 +5,7 @@ from django import forms
 from django.conf import settings
 from django.forms.formsets import all_valid, DELETION_FIELD_NAME
 from django.forms.models import (modelform_factory, modelformset_factory,
-    inlineformset_factory, BaseInlineFormSet)
+    inlineformset_factory, BaseInlineFormSet, modelform_defines_fields)
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.admin import widgets, helpers
 from django.contrib.admin.util import (unquote, flatten_fieldsets, get_deleted_objects,
@@ -488,6 +488,10 @@ class ModelAdmin(BaseModelAdmin):
             "formfield_callback": partial(self.formfield_for_dbfield, request=request),
         }
         defaults.update(kwargs)
+
+        if defaults['fields'] is None and not modelform_defines_fields(defaults['form']):
+            defaults['fields'] = forms.ALL_FIELDS
+
         try:
             return modelform_factory(self.model, **defaults)
         except FieldError as e:
@@ -523,6 +527,10 @@ class ModelAdmin(BaseModelAdmin):
             "formfield_callback": partial(self.formfield_for_dbfield, request=request),
         }
         defaults.update(kwargs)
+        if (defaults.get('fields') is None
+            and not modelform_defines_fields(defaults.get('form'))):
+            defaults['fields'] = forms.ALL_FIELDS
+
         return modelform_factory(self.model, **defaults)
 
     def get_changelist_formset(self, request, **kwargs):
@@ -1527,6 +1535,10 @@ class InlineModelAdmin(BaseModelAdmin):
                 return result
 
         defaults['form'] = DeleteProtectedModelForm
+
+        if defaults['fields'] is None and not modelform_defines_fields(defaults['form']):
+            defaults['fields'] = forms.ALL_FIELDS
+
         return inlineformset_factory(self.parent_model, self.model, **defaults)
 
     def get_fieldsets(self, request, obj=None):

+ 1 - 0
django/contrib/auth/forms.py

@@ -130,6 +130,7 @@ class UserChangeForm(forms.ModelForm):
 
     class Meta:
         model = User
+        fields = '__all__'
 
     def __init__(self, *args, **kwargs):
         super(UserChangeForm, self).__init__(*args, **kwargs)

+ 7 - 2
django/contrib/contenttypes/generic.py

@@ -13,8 +13,9 @@ from django.db.models import signals
 from django.db.models.fields.related import ForeignObject, ForeignObjectRel
 from django.db.models.related import PathInfo
 from django.db.models.sql.where import Constraint
-from django.forms import ModelForm
-from django.forms.models import BaseModelFormSet, modelformset_factory, save_instance
+from django.forms import ModelForm, ALL_FIELDS
+from django.forms.models import (BaseModelFormSet, modelformset_factory, save_instance,
+    modelform_defines_fields)
 from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets
 from django.contrib.contenttypes.models import ContentType
 from django.utils import six
@@ -480,6 +481,10 @@ class GenericInlineModelAdmin(InlineModelAdmin):
             "exclude": exclude
         }
         defaults.update(kwargs)
+
+        if defaults['fields'] is None and not modelform_defines_fields(defaults['form']):
+            defaults['fields'] = ALL_FIELDS
+
         return generic_inlineformset_factory(self.model, **defaults)
 
 class GenericStackedInline(GenericInlineModelAdmin):

+ 1 - 0
django/contrib/flatpages/forms.py

@@ -12,6 +12,7 @@ class FlatpageForm(forms.ModelForm):
 
     class Meta:
         model = FlatPage
+        fields = '__all__'
 
     def clean_url(self):
         url = self.cleaned_data['url']

+ 3 - 1
django/contrib/formtools/tests/wizard/test_forms.py

@@ -60,9 +60,11 @@ class TestModel(models.Model):
 class TestModelForm(forms.ModelForm):
     class Meta:
         model = TestModel
+        fields = '__all__'
 
 
-TestModelFormSet = forms.models.modelformset_factory(TestModel, form=TestModelForm, extra=2)
+TestModelFormSet = forms.models.modelformset_factory(TestModel, form=TestModelForm, extra=2,
+                                                     fields='__all__')
 
 
 class TestWizard(WizardView):

+ 1 - 0
django/contrib/formtools/tests/wizard/wizardtests/tests.py

@@ -15,6 +15,7 @@ from django.utils._os import upath
 class UserForm(forms.ModelForm):
     class Meta:
         model = User
+        fields = '__all__'
 
 
 UserFormSet = forms.models.modelformset_factory(User, form=UserForm, extra=2)

+ 55 - 2
django/forms/models.py

@@ -5,6 +5,8 @@ and database field objects.
 
 from __future__ import absolute_import, unicode_literals
 
+import warnings
+
 from django.core.exceptions import ValidationError, NON_FIELD_ERRORS, FieldError
 from django.forms.fields import Field, ChoiceField
 from django.forms.forms import BaseForm, get_declared_fields
@@ -22,8 +24,12 @@ from django.utils.translation import ugettext_lazy as _, ugettext
 __all__ = (
     'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model',
     'save_instance', 'ModelChoiceField', 'ModelMultipleChoiceField',
+    'ALL_FIELDS',
 )
 
+ALL_FIELDS = '__all__'
+
+
 def construct_instance(form, instance, fields=None, exclude=None):
     """
     Constructs and returns a model instance from the bound ``form``'s
@@ -211,7 +217,7 @@ class ModelFormMetaclass(type):
         # of ('foo',)
         for opt in ['fields', 'exclude']:
             value = getattr(opts, opt)
-            if isinstance(value, six.string_types):
+            if isinstance(value, six.string_types) and value != ALL_FIELDS:
                 msg = ("%(model)s.Meta.%(opt)s cannot be a string. "
                        "Did you mean to type: ('%(value)s',)?" % {
                            'model': new_class.__name__,
@@ -222,6 +228,20 @@ class ModelFormMetaclass(type):
 
         if opts.model:
             # If a model is defined, extract form fields from it.
+
+            if opts.fields is None and opts.exclude is None:
+                # This should be some kind of assertion error once deprecation
+                # cycle is complete.
+                warnings.warn("Creating a ModelForm without either the 'fields' attribute "
+                              "or the 'exclude' attribute is deprecated - form %s "
+                              "needs updating" % name,
+                              PendingDeprecationWarning)
+
+            if opts.fields == ALL_FIELDS:
+                # sentinel for fields_for_model to indicate "get the list of
+                # fields from the model"
+                opts.fields = None
+
             fields = fields_for_model(opts.model, opts.fields,
                                       opts.exclude, opts.widgets, formfield_callback)
             # make sure opts.fields doesn't specify an invalid field
@@ -394,7 +414,8 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
     Returns a ModelForm containing form fields for the given model.
 
     ``fields`` is an optional list of field names. If provided, only the named
-    fields will be included in the returned fields.
+    fields will be included in the returned fields. If omitted or '__all__',
+    all fields will be used.
 
     ``exclude`` is an optional list of field names. If provided, the named
     fields will be excluded from the returned fields, even if they are listed
@@ -434,6 +455,15 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
         'formfield_callback': formfield_callback
     }
 
+    # The ModelFormMetaclass will trigger a similar warning/error, but this will
+    # be difficult to debug for code that needs updating, so we produce the
+    # warning here too.
+    if (getattr(Meta, 'fields', None) is None and
+        getattr(Meta, 'exclude', None) is None):
+        warnings.warn("Calling modelform_factory without defining 'fields' or "
+                      "'exclude' explicitly is deprecated",
+                      PendingDeprecationWarning, stacklevel=2)
+
     # Instatiate type(form) in order to use the same metaclass as form.
     return type(form)(class_name, (form,), form_class_attrs)
 
@@ -701,6 +731,21 @@ def modelformset_factory(model, form=ModelForm, formfield_callback=None,
     """
     Returns a FormSet class for the given Django model class.
     """
+    # modelform_factory will produce the same warning/error, but that will be
+    # difficult to debug for code that needs upgrading, so we produce the
+    # warning here too. This logic is reproducing logic inside
+    # modelform_factory, but it can be removed once the deprecation cycle is
+    # complete, since the validation exception will produce a helpful
+    # stacktrace.
+    meta = getattr(form, 'Meta', None)
+    if meta is None:
+        meta = type(str('Meta'), (object,), {})
+    if (getattr(meta, 'fields', fields) is None and
+        getattr(meta, 'exclude', exclude) is None):
+        warnings.warn("Calling modelformset_factory without defining 'fields' or "
+                      "'exclude' explicitly is deprecated",
+                      PendingDeprecationWarning, stacklevel=2)
+
     form = modelform_factory(model, form=form, fields=fields, exclude=exclude,
                              formfield_callback=formfield_callback,
                              widgets=widgets)
@@ -1091,3 +1136,11 @@ class ModelMultipleChoiceField(ModelChoiceField):
         initial_set = set([force_text(value) for value in self.prepare_value(initial)])
         data_set = set([force_text(value) for value in data])
         return data_set != initial_set
+
+
+def modelform_defines_fields(form_class):
+    return (form_class is not None and (
+            hasattr(form_class, '_meta') and
+            (form_class._meta.fields is not None or
+             form_class._meta.exclude is not None)
+            ))

+ 10 - 1
django/views/generic/edit.py

@@ -1,3 +1,5 @@
+import warnings
+
 from django.forms import models as model_forms
 from django.core.exceptions import ImproperlyConfigured
 from django.http import HttpResponseRedirect
@@ -95,7 +97,14 @@ class ModelFormMixin(FormMixin, SingleObjectMixin):
                 # Try to get a queryset and extract the model class
                 # from that
                 model = self.get_queryset().model
-            return model_forms.modelform_factory(model)
+
+            fields = getattr(self, 'fields', None)
+            if fields is None:
+                warnings.warn("Using ModelFormMixin (base class of %s) without "
+                              "the 'fields' attribute is deprecated." % self.__class__.__name__,
+                              PendingDeprecationWarning)
+
+            return model_forms.modelform_factory(model, fields=fields)
 
     def get_form_kwargs(self):
         """

+ 2 - 0
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
 ----------

+ 12 - 0
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.

+ 29 - 5
docs/ref/contrib/admin/index.txt

@@ -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"]

+ 8 - 0
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.

+ 52 - 0
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.

+ 1 - 0
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.

+ 17 - 20
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

+ 83 - 80
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::
 

+ 2 - 0
tests/admin_validation/tests.py

@@ -285,6 +285,8 @@ class ValidationTestCase(TestCase):
             extra_data = forms.CharField()
             class Meta:
                 model = Song
+                fields = '__all__'
+
 
         class FieldsOnFormOnlyAdmin(admin.ModelAdmin):
             form = SongForm

+ 1 - 0
tests/bug639/models.py

@@ -25,3 +25,4 @@ class Photo(models.Model):
 class PhotoForm(ModelForm):
     class Meta:
         model = Photo
+        fields = '__all__'

+ 1 - 0
tests/foreign_object/tests.py

@@ -322,6 +322,7 @@ class FormsTests(TestCase):
     class ArticleForm(forms.ModelForm):
         class Meta:
             model = Article
+            fields = '__all__'
 
     def test_foreign_object_form(self):
         # A very crude test checking that the non-concrete fields do not get form fields.

+ 1 - 0
tests/forms_tests/tests/test_regressions.py

@@ -139,6 +139,7 @@ class FormsRegressionsTestCase(TestCase):
         class CheeseForm(ModelForm):
             class Meta:
                 model = Cheese
+                fields = '__all__'
 
         form = CheeseForm({
             'name': 'Brie',

+ 4 - 0
tests/forms_tests/tests/tests.py

@@ -17,11 +17,13 @@ from ..models import (ChoiceOptionModel, ChoiceFieldModel, FileModel, Group,
 class ChoiceFieldForm(ModelForm):
     class Meta:
         model = ChoiceFieldModel
+        fields = '__all__'
 
 
 class OptionalMultiChoiceModelForm(ModelForm):
     class Meta:
         model = OptionalMultiChoiceModel
+        fields = '__all__'
 
 
 class FileForm(Form):
@@ -139,6 +141,7 @@ class FormsModelTestCase(TestCase):
         class BoundaryForm(ModelForm):
             class Meta:
                 model = BoundaryModel
+                fields = '__all__'
 
         f = BoundaryForm({'positive_integer': 100})
         self.assertTrue(f.is_valid())
@@ -154,6 +157,7 @@ class FormsModelTestCase(TestCase):
         class DefaultsForm(ModelForm):
             class Meta:
                 model = Defaults
+                fields = '__all__'
 
         self.assertEqual(DefaultsForm().fields['name'].initial, 'class default value')
         self.assertEqual(DefaultsForm().fields['def_date'].initial, datetime.date(1980, 1, 1))

+ 1 - 0
tests/generic_relations/tests.py

@@ -247,6 +247,7 @@ class CustomWidget(forms.TextInput):
 class TaggedItemForm(forms.ModelForm):
     class Meta:
         model = TaggedItem
+        fields = '__all__'
         widgets = {'tag': CustomWidget}
 
 class GenericInlineFormsetTest(TestCase):

+ 43 - 1
tests/generic_views/test_edit.py

@@ -1,12 +1,14 @@
 from __future__ import absolute_import
 
+import warnings
+
 from django.core.exceptions import ImproperlyConfigured
 from django.core.urlresolvers import reverse
 from django import forms
 from django.test import TestCase
 from django.utils.unittest import expectedFailure
 from django.views.generic.base import View
-from django.views.generic.edit import FormMixin
+from django.views.generic.edit import FormMixin, CreateView, UpdateView
 
 from . import views
 from .models import Artist, Author
@@ -34,6 +36,7 @@ class ModelFormMixinTests(TestCase):
         form_class = views.AuthorGetQuerySetFormView().get_form_class()
         self.assertEqual(form_class._meta.model, Author)
 
+
 class CreateViewTests(TestCase):
     urls = 'generic_views.urls'
 
@@ -112,6 +115,45 @@ class CreateViewTests(TestCase):
         self.assertEqual(res.status_code, 302)
         self.assertRedirects(res, 'http://testserver/accounts/login/?next=/edit/authors/create/restricted/')
 
+    def test_create_view_with_restricted_fields(self):
+
+        class MyCreateView(CreateView):
+            model = Author
+            fields = ['name']
+
+        self.assertEqual(list(MyCreateView().get_form_class().base_fields),
+                         ['name'])
+
+    def test_create_view_all_fields(self):
+
+        with warnings.catch_warnings(record=True) as w:
+            warnings.simplefilter("always", PendingDeprecationWarning)
+
+            class MyCreateView(CreateView):
+                model = Author
+                fields = '__all__'
+
+            self.assertEqual(list(MyCreateView().get_form_class().base_fields),
+                             ['name', 'slug'])
+        self.assertEqual(len(w), 0)
+
+
+    def test_create_view_without_explicit_fields(self):
+
+        with warnings.catch_warnings(record=True) as w:
+            warnings.simplefilter("always", PendingDeprecationWarning)
+
+            class MyCreateView(CreateView):
+                model = Author
+
+            # Until end of the deprecation cycle, should still create the form
+            # as before:
+            self.assertEqual(list(MyCreateView().get_form_class().base_fields),
+                             ['name', 'slug'])
+
+        # but with a warning:
+        self.assertEqual(w[0].category, PendingDeprecationWarning)
+
 
 class UpdateViewTests(TestCase):
     urls = 'generic_views.urls'

+ 1 - 0
tests/generic_views/test_forms.py

@@ -11,6 +11,7 @@ class AuthorForm(forms.ModelForm):
 
     class Meta:
         model = Author
+        fields = ['name', 'slug']
 
 
 class ContactForm(forms.Form):

+ 9 - 0
tests/generic_views/views.py

@@ -85,15 +85,18 @@ class ContactView(generic.FormView):
 
 class ArtistCreate(generic.CreateView):
     model = Artist
+    fields = '__all__'
 
 
 class NaiveAuthorCreate(generic.CreateView):
     queryset = Author.objects.all()
+    fields = '__all__'
 
 
 class AuthorCreate(generic.CreateView):
     model = Author
     success_url = '/list/authors/'
+    fields = '__all__'
 
 
 class SpecializedAuthorCreate(generic.CreateView):
@@ -112,19 +115,23 @@ class AuthorCreateRestricted(AuthorCreate):
 
 class ArtistUpdate(generic.UpdateView):
     model = Artist
+    fields = '__all__'
 
 
 class NaiveAuthorUpdate(generic.UpdateView):
     queryset = Author.objects.all()
+    fields = '__all__'
 
 
 class AuthorUpdate(generic.UpdateView):
     model = Author
     success_url = '/list/authors/'
+    fields = '__all__'
 
 
 class OneAuthorUpdate(generic.UpdateView):
     success_url = '/list/authors/'
+    fields = '__all__'
 
     def get_object(self):
         return Author.objects.get(pk=1)
@@ -184,6 +191,8 @@ class BookDetail(BookConfig, generic.DateDetailView):
     pass
 
 class AuthorGetQuerySetFormView(generic.edit.ModelFormMixin):
+    fields = '__all__'
+
     def get_queryset(self):
         return Author.objects.all()
 

+ 1 - 0
tests/i18n/forms.py

@@ -24,3 +24,4 @@ class CompanyForm(forms.ModelForm):
 
     class Meta:
         model = Company
+        fields = '__all__'

+ 5 - 5
tests/inline_formsets/tests.py

@@ -10,7 +10,7 @@ from .models import Poet, Poem, School, Parent, Child
 class DeletionTests(TestCase):
 
     def test_deletion(self):
-        PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True)
+        PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True, fields="__all__")
         poet = Poet.objects.create(name='test')
         poem = poet.poem_set.create(name='test poem')
         data = {
@@ -32,7 +32,7 @@ class DeletionTests(TestCase):
         Make sure that an add form that is filled out, but marked for deletion
         doesn't cause validation errors.
         """
-        PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True)
+        PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True, fields="__all__")
         poet = Poet.objects.create(name='test')
         data = {
             'poem_set-TOTAL_FORMS': '1',
@@ -60,7 +60,7 @@ class DeletionTests(TestCase):
         Make sure that a change form that is filled out, but marked for deletion
         doesn't cause validation errors.
         """
-        PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True)
+        PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True, fields="__all__")
         poet = Poet.objects.create(name='test')
         poem = poet.poem_set.create(name='test poem')
         data = {
@@ -115,8 +115,8 @@ class InlineFormsetFactoryTest(TestCase):
         """
         These should both work without a problem.
         """
-        inlineformset_factory(Parent, Child, fk_name='mother')
-        inlineformset_factory(Parent, Child, fk_name='father')
+        inlineformset_factory(Parent, Child, fk_name='mother', fields="__all__")
+        inlineformset_factory(Parent, Child, fk_name='father', fields="__all__")
 
     def test_exception_on_unspecified_foreign_key(self):
         """

+ 99 - 12
tests/model_forms/tests.py

@@ -3,6 +3,7 @@ from __future__ import absolute_import, unicode_literals
 import datetime
 import os
 from decimal import Decimal
+import warnings
 
 from django import forms
 from django.core.exceptions import FieldError
@@ -30,19 +31,25 @@ if test_images:
     class ImageFileForm(forms.ModelForm):
         class Meta:
             model = ImageFile
+            fields = '__all__'
+
 
     class OptionalImageFileForm(forms.ModelForm):
         class Meta:
             model = OptionalImageFile
+            fields = '__all__'
+
 
 class ProductForm(forms.ModelForm):
     class Meta:
         model = Product
+        fields = '__all__'
 
 
 class PriceForm(forms.ModelForm):
     class Meta:
         model = Price
+        fields = '__all__'
 
 
 class BookForm(forms.ModelForm):
@@ -66,11 +73,13 @@ class ExplicitPKForm(forms.ModelForm):
 class PostForm(forms.ModelForm):
     class Meta:
         model = Post
+        fields = '__all__'
 
 
 class DerivedPostForm(forms.ModelForm):
     class Meta:
         model = DerivedPost
+        fields = '__all__'
 
 
 class CustomAuthorForm(forms.ModelForm):
@@ -78,61 +87,79 @@ class CustomAuthorForm(forms.ModelForm):
 
    class Meta:
        model = Author
+       fields = '__all__'
 
 
 class FlexDatePostForm(forms.ModelForm):
     class Meta:
         model = FlexibleDatePost
+        fields = '__all__'
 
 
 class BaseCategoryForm(forms.ModelForm):
     class Meta:
         model = Category
+        fields = '__all__'
 
 
 class ArticleForm(forms.ModelForm):
     class Meta:
         model = Article
+        fields = '__all__'
 
 
 class ArticleForm(forms.ModelForm):
     class Meta:
         model = Article
+        fields = '__all__'
+
 
 class PartialArticleForm(forms.ModelForm):
     class Meta:
         model = Article
         fields = ('headline','pub_date')
 
+
 class RoykoForm(forms.ModelForm):
     class Meta:
         model = Author
+        fields = '__all__'
+
 
 class TestArticleForm(forms.ModelForm):
     class Meta:
         model = Article
+        fields = '__all__'
+
 
 class PartialArticleFormWithSlug(forms.ModelForm):
     class Meta:
         model = Article
-        fields=('headline', 'slug', 'pub_date')
+        fields = ('headline', 'slug', 'pub_date')
+
 
 class ArticleStatusForm(forms.ModelForm):
     class Meta:
         model = ArticleStatus
+        fields = '__all__'
+
 
 class InventoryForm(forms.ModelForm):
     class Meta:
         model = Inventory
+        fields = '__all__'
+
 
 class SelectInventoryForm(forms.Form):
     items = forms.ModelMultipleChoiceField(Inventory.objects.all(), to_field_name='barcode')
 
+
 class CustomFieldForExclusionForm(forms.ModelForm):
     class Meta:
         model = CustomFieldForExclusionModel
         fields = ['name', 'markup']
 
+
 class ShortCategory(forms.ModelForm):
     name = forms.CharField(max_length=5)
     slug = forms.CharField(max_length=5)
@@ -140,30 +167,44 @@ class ShortCategory(forms.ModelForm):
 
     class Meta:
         model = Category
+        fields = '__all__'
+
 
 class ImprovedArticleForm(forms.ModelForm):
     class Meta:
         model = ImprovedArticle
+        fields = '__all__'
+
 
 class ImprovedArticleWithParentLinkForm(forms.ModelForm):
     class Meta:
         model = ImprovedArticleWithParentLink
+        fields = '__all__'
+
 
 class BetterAuthorForm(forms.ModelForm):
     class Meta:
         model = BetterAuthor
+        fields = '__all__'
+
 
 class AuthorProfileForm(forms.ModelForm):
     class Meta:
         model = AuthorProfile
+        fields = '__all__'
+
 
 class TextFileForm(forms.ModelForm):
     class Meta:
         model = TextFile
+        fields = '__all__'
+
 
 class BigIntForm(forms.ModelForm):
     class Meta:
         model = BigInt
+        fields = '__all__'
+
 
 class ModelFormWithMedia(forms.ModelForm):
     class Media:
@@ -173,19 +214,25 @@ class ModelFormWithMedia(forms.ModelForm):
         }
     class Meta:
         model = TextFile
+        fields = '__all__'
+
 
 class CommaSeparatedIntegerForm(forms.ModelForm):
-   class Meta:
-       model = CommaSeparatedInteger
+    class Meta:
+        model = CommaSeparatedInteger
+        fields = '__all__'
+
 
 class PriceFormWithoutQuantity(forms.ModelForm):
     class Meta:
         model = Price
         exclude = ('quantity',)
 
+
 class ColourfulItemForm(forms.ModelForm):
     class Meta:
         model = ColourfulItem
+        fields = '__all__'
 
 
 class ModelFormBaseTest(TestCase):
@@ -193,6 +240,25 @@ class ModelFormBaseTest(TestCase):
         self.assertEqual(list(BaseCategoryForm.base_fields),
                          ['name', 'slug', 'url'])
 
+    def test_missing_fields_attribute(self):
+        with warnings.catch_warnings(record=True) as w:
+            warnings.simplefilter("always", PendingDeprecationWarning)
+
+            class MissingFieldsForm(forms.ModelForm):
+                class Meta:
+                    model = Category
+
+        # There is some internal state in warnings module which means that
+        # if a warning has been seen already, the catch_warnings won't
+        # have recorded it. The following line therefore will not work reliably:
+
+        # self.assertEqual(w[0].category, PendingDeprecationWarning)
+
+        # Until end of the deprecation cycle, should still create the
+        # form as before:
+        self.assertEqual(list(MissingFieldsForm.base_fields),
+                         ['name', 'slug', 'url'])
+
     def test_extra_fields(self):
         class ExtraFields(BaseCategoryForm):
             some_extra_field = forms.BooleanField()
@@ -206,6 +272,33 @@ class ModelFormBaseTest(TestCase):
 
             class Meta:
                 model = Category
+                fields = '__all__'
+
+        self.assertTrue(isinstance(ReplaceField.base_fields['url'],
+                                     forms.fields.BooleanField))
+
+    def test_replace_field_variant_2(self):
+        # Should have the same result as before,
+        # but 'fields' attribute specified differently
+        class ReplaceField(forms.ModelForm):
+            url = forms.BooleanField()
+
+            class Meta:
+                model = Category
+                fields = ['url']
+
+        self.assertTrue(isinstance(ReplaceField.base_fields['url'],
+                                     forms.fields.BooleanField))
+
+    def test_replace_field_variant_3(self):
+        # Should have the same result as before,
+        # but 'fields' attribute specified differently
+        class ReplaceField(forms.ModelForm):
+            url = forms.BooleanField()
+
+            class Meta:
+                model = Category
+                fields = [] # url will still appear, since it is explicit above
 
         self.assertTrue(isinstance(ReplaceField.base_fields['url'],
                                      forms.fields.BooleanField))
@@ -216,19 +309,11 @@ class ModelFormBaseTest(TestCase):
 
             class Meta:
                 model = Author
+                fields = '__all__'
 
         wf = AuthorForm({'name': 'Richard Lockridge'})
         self.assertTrue(wf.is_valid())
 
-    def test_limit_fields(self):
-        class LimitFields(forms.ModelForm):
-            class Meta:
-                model = Category
-                fields = ['url']
-
-        self.assertEqual(list(LimitFields.base_fields),
-                         ['url'])
-
     def test_limit_nonexistent_field(self):
         expected_msg = 'Unknown field(s) (nonexistent) specified for Category'
         with self.assertRaisesMessage(FieldError, expected_msg):
@@ -294,6 +379,7 @@ class ModelFormBaseTest(TestCase):
             """
             class Meta:
                 model = Article
+                fields = '__all__'
             # MixModelForm is now an Article-related thing, because MixModelForm.Meta
             # overrides BaseCategoryForm.Meta.
 
@@ -348,6 +434,7 @@ class ModelFormBaseTest(TestCase):
 
              class Meta:
                  model = Category
+                 fields = '__all__'
 
         class SubclassMeta(SomeCategoryForm):
             """ We can also subclass the Meta inner class to change the fields

+ 51 - 6
tests/model_forms_regress/tests.py

@@ -1,6 +1,7 @@
 from __future__ import absolute_import, unicode_literals
 
 from datetime import date
+import warnings
 
 from django import forms
 from django.core.exceptions import FieldError, ValidationError
@@ -43,9 +44,12 @@ class ModelMultipleChoiceFieldTests(TestCase):
         f.clean([p.pk for p in Person.objects.all()[8:9]])
         self.assertTrue(self._validator_run)
 
+
 class TripleForm(forms.ModelForm):
     class Meta:
         model = Triple
+        fields = '__all__'
+
 
 class UniqueTogetherTests(TestCase):
     def test_multiple_field_unique_together(self):
@@ -63,15 +67,18 @@ class UniqueTogetherTests(TestCase):
         form = TripleForm({'left': '1', 'middle': '3', 'right': '1'})
         self.assertTrue(form.is_valid())
 
+
 class TripleFormWithCleanOverride(forms.ModelForm):
     class Meta:
         model = Triple
+        fields = '__all__'
 
     def clean(self):
         if not self.cleaned_data['left'] == self.cleaned_data['right']:
             raise forms.ValidationError('Left and right should be equal')
         return self.cleaned_data
 
+
 class OverrideCleanTests(TestCase):
     def test_override_clean(self):
         """
@@ -84,6 +91,7 @@ class OverrideCleanTests(TestCase):
         # by form.full_clean().
         self.assertEqual(form.instance.left, 1)
 
+
 # Regression test for #12960.
 # Make sure the cleaned_data returned from ModelForm.clean() is applied to the
 # model instance.
@@ -95,6 +103,8 @@ class PublicationForm(forms.ModelForm):
 
     class Meta:
         model = Publication
+        fields = '__all__'
+
 
 class ModelFormCleanTest(TestCase):
     def test_model_form_clean_applies_to_model(self):
@@ -103,9 +113,12 @@ class ModelFormCleanTest(TestCase):
         publication = form.save()
         self.assertEqual(publication.title, 'TEST')
 
+
 class FPForm(forms.ModelForm):
     class Meta:
         model = FilePathModel
+        fields = '__all__'
+
 
 class FilePathFieldTests(TestCase):
     def test_file_path_field_blank(self):
@@ -133,7 +146,8 @@ class ManyToManyCallableInitialTests(TestCase):
         book3 = Publication.objects.create(title="Third Book", date_published=date(2009,1,1))
 
         # Create a ModelForm, instantiate it, and check that the output is as expected
-        ModelForm = modelform_factory(Article, formfield_callback=formfield_for_dbfield)
+        ModelForm = modelform_factory(Article, fields="__all__",
+                                      formfield_callback=formfield_for_dbfield)
         form = ModelForm()
         self.assertHTMLEqual(form.as_ul(), """<li><label for="id_headline">Headline:</label> <input id="id_headline" type="text" name="headline" maxlength="100" /></li>
 <li><label for="id_publications">Publications:</label> <select multiple="multiple" name="publications" id="id_publications">
@@ -143,9 +157,12 @@ class ManyToManyCallableInitialTests(TestCase):
 </select> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></li>"""
             % (book1.pk, book2.pk, book3.pk))
 
+
 class CFFForm(forms.ModelForm):
     class Meta:
         model = CustomFF
+        fields = '__all__'
+
 
 class CustomFieldSaveTests(TestCase):
     def test_save(self):
@@ -168,9 +185,12 @@ class ModelChoiceIteratorTests(TestCase):
         f = Form()
         self.assertEqual(len(f.fields["publications"].choices), 1)
 
+
 class RealPersonForm(forms.ModelForm):
     class Meta:
         model = RealPerson
+        fields = '__all__'
+
 
 class CustomModelFormSaveMethod(TestCase):
     def test_string_message(self):
@@ -230,9 +250,12 @@ class TestTicket11183(TestCase):
         self.assertTrue(field1 is not ModelChoiceForm.base_fields['person'])
         self.assertTrue(field1.widget.choices.field is field1)
 
+
 class HomepageForm(forms.ModelForm):
     class Meta:
         model = Homepage
+        fields = '__all__'
+
 
 class URLFieldTests(TestCase):
     def test_url_on_modelform(self):
@@ -274,6 +297,7 @@ class FormFieldCallbackTests(TestCase):
             class Meta:
                 model = Person
                 widgets = {'name': widget}
+                fields = "__all__"
 
         Form = modelform_factory(Person, form=BaseForm)
         self.assertTrue(Form.base_fields['name'].widget is widget)
@@ -285,11 +309,11 @@ class FormFieldCallbackTests(TestCase):
         widget = forms.Textarea()
 
         # Without a widget should not set the widget to textarea
-        Form = modelform_factory(Person)
+        Form = modelform_factory(Person, fields="__all__")
         self.assertNotEqual(Form.base_fields['name'].widget.__class__, forms.Textarea)
 
         # With a widget should not set the widget to textarea
-        Form = modelform_factory(Person, widgets={'name':widget})
+        Form = modelform_factory(Person, fields="__all__", widgets={'name':widget})
         self.assertEqual(Form.base_fields['name'].widget.__class__, forms.Textarea)
 
     def test_custom_callback(self):
@@ -307,6 +331,7 @@ class FormFieldCallbackTests(TestCase):
             class Meta:
                 model = Person
                 widgets = {'name': widget}
+                fields = "__all__"
 
         _ = modelform_factory(Person, form=BaseForm,
                               formfield_callback=callback)
@@ -317,7 +342,7 @@ class FormFieldCallbackTests(TestCase):
 
     def test_bad_callback(self):
         # A bad callback provided by user still gives an error
-        self.assertRaises(TypeError, modelform_factory, Person,
+        self.assertRaises(TypeError, modelform_factory, Person, fields="__all__",
                           formfield_callback='not a function or callable')
 
 
@@ -362,6 +387,8 @@ class InvalidFieldAndFactory(TestCase):
 class DocumentForm(forms.ModelForm):
     class Meta:
         model = Document
+        fields = '__all__'
+
 
 class FileFieldTests(unittest.TestCase):
     def test_clean_false(self):
@@ -425,6 +452,7 @@ class FileFieldTests(unittest.TestCase):
         self.assertTrue('something.txt' in rendered)
         self.assertTrue('myfile-clear' in rendered)
 
+
 class EditionForm(forms.ModelForm):
     author = forms.ModelChoiceField(queryset=Person.objects.all())
     publication = forms.ModelChoiceField(queryset=Publication.objects.all())
@@ -433,6 +461,8 @@ class EditionForm(forms.ModelForm):
 
     class Meta:
         model = Edition
+        fields = '__all__'
+
 
 class UniqueErrorsTests(TestCase):
     def setUp(self):
@@ -473,7 +503,7 @@ class EmptyFieldsTestCase(TestCase):
 
     def test_empty_fields_to_construct_instance(self):
         "No fields should be set on a model instance if construct_instance receives fields=()"
-        form = modelform_factory(Person)({'name': 'John Doe'})
+        form = modelform_factory(Person, fields="__all__")({'name': 'John Doe'})
         self.assertTrue(form.is_valid())
         instance = construct_instance(form, Person(), fields=())
         self.assertEqual(instance.name, '')
@@ -485,10 +515,25 @@ class CustomMetaclass(ModelFormMetaclass):
         new.base_fields = {}
         return new
 
+
 class CustomMetaclassForm(six.with_metaclass(CustomMetaclass, forms.ModelForm)):
     pass
 
+
 class CustomMetaclassTestCase(TestCase):
     def test_modelform_factory_metaclass(self):
-        new_cls = modelform_factory(Person, form=CustomMetaclassForm)
+        new_cls = modelform_factory(Person, fields="__all__", form=CustomMetaclassForm)
         self.assertEqual(new_cls.base_fields, {})
+
+
+class TestTicket19733(TestCase):
+    def test_modelform_factory_without_fields(self):
+        with warnings.catch_warnings(record=True) as w:
+            warnings.simplefilter("always", PendingDeprecationWarning)
+            # This should become an error once deprecation cycle is complete.
+            form = modelform_factory(Person)
+        self.assertEqual(w[0].category, PendingDeprecationWarning)
+
+    def test_modelform_factory_with_all_fields(self):
+        form = modelform_factory(Person, fields="__all__")
+        self.assertEqual(form.base_fields.keys(), ["name"])

+ 45 - 44
tests/model_formsets/tests.py

@@ -21,7 +21,7 @@ from .models import (Author, BetterAuthor, Book, BookWithCustomPK,
 
 class DeletionTests(TestCase):
     def test_deletion(self):
-        PoetFormSet = modelformset_factory(Poet, can_delete=True)
+        PoetFormSet = modelformset_factory(Poet, fields="__all__", can_delete=True)
         poet = Poet.objects.create(name='test')
         data = {
             'form-TOTAL_FORMS': '1',
@@ -41,7 +41,7 @@ class DeletionTests(TestCase):
         Make sure that an add form that is filled out, but marked for deletion
         doesn't cause validation errors.
         """
-        PoetFormSet = modelformset_factory(Poet, can_delete=True)
+        PoetFormSet = modelformset_factory(Poet, fields="__all__", can_delete=True)
         poet = Poet.objects.create(name='test')
         # One existing untouched and two new unvalid forms
         data = {
@@ -75,7 +75,7 @@ class DeletionTests(TestCase):
         Make sure that a change form that is filled out, but marked for deletion
         doesn't cause validation errors.
         """
-        PoetFormSet = modelformset_factory(Poet, can_delete=True)
+        PoetFormSet = modelformset_factory(Poet, fields="__all__", can_delete=True)
         poet = Poet.objects.create(name='test')
         data = {
             'form-TOTAL_FORMS': '1',
@@ -100,7 +100,7 @@ class DeletionTests(TestCase):
 class ModelFormsetTest(TestCase):
     def test_simple_save(self):
         qs = Author.objects.all()
-        AuthorFormSet = modelformset_factory(Author, extra=3)
+        AuthorFormSet = modelformset_factory(Author, fields="__all__", extra=3)
 
         formset = AuthorFormSet(queryset=qs)
         self.assertEqual(len(formset.forms), 3)
@@ -138,7 +138,7 @@ class ModelFormsetTest(TestCase):
         # we'll use it to display them in alphabetical order by name.
 
         qs = Author.objects.order_by('name')
-        AuthorFormSet = modelformset_factory(Author, extra=1, can_delete=False)
+        AuthorFormSet = modelformset_factory(Author, fields="__all__", extra=1, can_delete=False)
 
         formset = AuthorFormSet(queryset=qs)
         self.assertEqual(len(formset.forms), 3)
@@ -176,7 +176,7 @@ class ModelFormsetTest(TestCase):
         # marked for deletion, make sure we don't save that form.
 
         qs = Author.objects.order_by('name')
-        AuthorFormSet = modelformset_factory(Author, extra=1, can_delete=True)
+        AuthorFormSet = modelformset_factory(Author, fields="__all__", extra=1, can_delete=True)
 
         formset = AuthorFormSet(queryset=qs)
         self.assertEqual(len(formset.forms), 4)
@@ -256,7 +256,7 @@ class ModelFormsetTest(TestCase):
 
         author4 = Author.objects.create(name='John Steinbeck')
 
-        AuthorMeetingFormSet = modelformset_factory(AuthorMeeting, extra=1, can_delete=True)
+        AuthorMeetingFormSet = modelformset_factory(AuthorMeeting, fields="__all__", extra=1, can_delete=True)
         data = {
             'form-TOTAL_FORMS': '2', # the number of forms rendered
             'form-INITIAL_FORMS': '1', # the number of forms with initial data
@@ -294,22 +294,22 @@ class ModelFormsetTest(TestCase):
 
         qs = Author.objects.order_by('name')
 
-        AuthorFormSet = modelformset_factory(Author, max_num=None, extra=3)
+        AuthorFormSet = modelformset_factory(Author, fields="__all__", max_num=None, extra=3)
         formset = AuthorFormSet(queryset=qs)
         self.assertEqual(len(formset.forms), 6)
         self.assertEqual(len(formset.extra_forms), 3)
 
-        AuthorFormSet = modelformset_factory(Author, max_num=4, extra=3)
+        AuthorFormSet = modelformset_factory(Author, fields="__all__", max_num=4, extra=3)
         formset = AuthorFormSet(queryset=qs)
         self.assertEqual(len(formset.forms), 4)
         self.assertEqual(len(formset.extra_forms), 1)
 
-        AuthorFormSet = modelformset_factory(Author, max_num=0, extra=3)
+        AuthorFormSet = modelformset_factory(Author, fields="__all__", max_num=0, extra=3)
         formset = AuthorFormSet(queryset=qs)
         self.assertEqual(len(formset.forms), 3)
         self.assertEqual(len(formset.extra_forms), 0)
 
-        AuthorFormSet = modelformset_factory(Author, max_num=None)
+        AuthorFormSet = modelformset_factory(Author, fields="__all__", max_num=None)
         formset = AuthorFormSet(queryset=qs)
         self.assertQuerysetEqual(formset.get_queryset(), [
             '<Author: Charles Baudelaire>',
@@ -317,7 +317,7 @@ class ModelFormsetTest(TestCase):
             '<Author: Walt Whitman>',
         ])
 
-        AuthorFormSet = modelformset_factory(Author, max_num=0)
+        AuthorFormSet = modelformset_factory(Author, fields="__all__", max_num=0)
         formset = AuthorFormSet(queryset=qs)
         self.assertQuerysetEqual(formset.get_queryset(), [
             '<Author: Charles Baudelaire>',
@@ -325,7 +325,7 @@ class ModelFormsetTest(TestCase):
             '<Author: Walt Whitman>',
         ])
 
-        AuthorFormSet = modelformset_factory(Author, max_num=4)
+        AuthorFormSet = modelformset_factory(Author, fields="__all__", max_num=4)
         formset = AuthorFormSet(queryset=qs)
         self.assertQuerysetEqual(formset.get_queryset(), [
             '<Author: Charles Baudelaire>',
@@ -343,7 +343,7 @@ class ModelFormsetTest(TestCase):
                     author.save()
                 return author
 
-        PoetFormSet = modelformset_factory(Poet, form=PoetForm)
+        PoetFormSet = modelformset_factory(Poet, fields="__all__", form=PoetForm)
 
         data = {
             'form-TOTAL_FORMS': '3', # the number of forms rendered
@@ -387,7 +387,7 @@ class ModelFormsetTest(TestCase):
         self.assertFalse("subtitle" in formset.forms[0].fields)
 
     def test_model_inheritance(self):
-        BetterAuthorFormSet = modelformset_factory(BetterAuthor)
+        BetterAuthorFormSet = modelformset_factory(BetterAuthor, fields="__all__")
         formset = BetterAuthorFormSet()
         self.assertEqual(len(formset.forms), 1)
         self.assertHTMLEqual(formset.forms[0].as_p(),
@@ -440,7 +440,7 @@ class ModelFormsetTest(TestCase):
         # We can also create a formset that is tied to a parent model. This is
         # how the admin system's edit inline functionality works.
 
-        AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=3)
+        AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=3, fields="__all__")
         author = Author.objects.create(name='Charles Baudelaire')
 
         formset = AuthorBooksFormSet(instance=author)
@@ -474,7 +474,7 @@ class ModelFormsetTest(TestCase):
         # another one. This time though, an edit form will be available for
         # every existing book.
 
-        AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=2)
+        AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=2, fields="__all__")
         author = Author.objects.get(name='Charles Baudelaire')
 
         formset = AuthorBooksFormSet(instance=author)
@@ -514,7 +514,7 @@ class ModelFormsetTest(TestCase):
     def test_inline_formsets_save_as_new(self):
         # The save_as_new parameter lets you re-associate the data to a new
         # instance.  This is used in the admin for save_as functionality.
-        AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=2)
+        AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=2, fields="__all__")
         author = Author.objects.create(name='Charles Baudelaire')
 
         data = {
@@ -553,7 +553,7 @@ class ModelFormsetTest(TestCase):
         # primary key that is not the fk to the parent object.
         self.maxDiff = 1024
 
-        AuthorBooksFormSet2 = inlineformset_factory(Author, BookWithCustomPK, can_delete=False, extra=1)
+        AuthorBooksFormSet2 = inlineformset_factory(Author, BookWithCustomPK, can_delete=False, extra=1, fields="__all__")
         author = Author.objects.create(pk=1, name='Charles Baudelaire')
 
         formset = AuthorBooksFormSet2(instance=author)
@@ -585,7 +585,7 @@ class ModelFormsetTest(TestCase):
         # Test inline formsets where the inline-edited object uses multi-table
         # inheritance, thus has a non AutoField yet auto-created primary key.
 
-        AuthorBooksFormSet3 = inlineformset_factory(Author, AlternateBook, can_delete=False, extra=1)
+        AuthorBooksFormSet3 = inlineformset_factory(Author, AlternateBook, can_delete=False, extra=1, fields="__all__")
         author = Author.objects.create(pk=1, name='Charles Baudelaire')
 
         formset = AuthorBooksFormSet3(instance=author)
@@ -616,7 +616,7 @@ class ModelFormsetTest(TestCase):
         # Test inline formsets where the inline-edited object has a
         # unique_together constraint with a nullable member
 
-        AuthorBooksFormSet4 = inlineformset_factory(Author, BookWithOptionalAltEditor, can_delete=False, extra=2)
+        AuthorBooksFormSet4 = inlineformset_factory(Author, BookWithOptionalAltEditor, can_delete=False, extra=2, fields="__all__")
         author = Author.objects.create(pk=1, name='Charles Baudelaire')
 
         data = {
@@ -640,7 +640,7 @@ class ModelFormsetTest(TestCase):
         self.assertEqual(book2.title, 'Les Fleurs du Mal')
 
     def test_inline_formsets_with_custom_save_method(self):
-        AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=2)
+        AuthorBooksFormSet = inlineformset_factory(Author, Book, can_delete=False, extra=2, fields="__all__")
         author = Author.objects.create(pk=1, name='Charles Baudelaire')
         book1 = Book.objects.create(pk=1, author=author, title='Les Paradis Artificiels')
         book2 = Book.objects.create(pk=2, author=author, title='Les Fleurs du Mal')
@@ -655,7 +655,7 @@ class ModelFormsetTest(TestCase):
                     poem.save()
                 return poem
 
-        PoemFormSet = inlineformset_factory(Poet, Poem, form=PoemForm)
+        PoemFormSet = inlineformset_factory(Poet, Poem, form=PoemForm, fields="__all__")
 
         data = {
             'poem_set-TOTAL_FORMS': '3', # the number of forms rendered
@@ -732,7 +732,7 @@ class ModelFormsetTest(TestCase):
     def test_custom_pk(self):
         # We need to ensure that it is displayed
 
-        CustomPrimaryKeyFormSet = modelformset_factory(CustomPrimaryKey)
+        CustomPrimaryKeyFormSet = modelformset_factory(CustomPrimaryKey, fields="__all__")
         formset = CustomPrimaryKeyFormSet()
         self.assertEqual(len(formset.forms), 1)
         self.assertHTMLEqual(formset.forms[0].as_p(),
@@ -743,7 +743,7 @@ class ModelFormsetTest(TestCase):
 
         place = Place.objects.create(pk=1, name='Giordanos', city='Chicago')
 
-        FormSet = inlineformset_factory(Place, Owner, extra=2, can_delete=False)
+        FormSet = inlineformset_factory(Place, Owner, extra=2, can_delete=False, fields="__all__")
         formset = FormSet(instance=place)
         self.assertEqual(len(formset.forms), 2)
         self.assertHTMLEqual(formset.forms[0].as_p(),
@@ -799,7 +799,7 @@ class ModelFormsetTest(TestCase):
 
         # Ensure a custom primary key that is a ForeignKey or OneToOneField get rendered for the user to choose.
 
-        FormSet = modelformset_factory(OwnerProfile)
+        FormSet = modelformset_factory(OwnerProfile, fields="__all__")
         formset = FormSet()
         self.assertHTMLEqual(formset.forms[0].as_p(),
             '<p><label for="id_form-0-owner">Owner:</label> <select name="form-0-owner" id="id_form-0-owner">\n'
@@ -811,7 +811,7 @@ class ModelFormsetTest(TestCase):
             % (owner1.auto_id, owner2.auto_id))
 
         owner1 = Owner.objects.get(name='Joe Perry')
-        FormSet = inlineformset_factory(Owner, OwnerProfile, max_num=1, can_delete=False)
+        FormSet = inlineformset_factory(Owner, OwnerProfile, max_num=1, can_delete=False, fields="__all__")
         self.assertEqual(FormSet.max_num, 1)
 
         formset = FormSet(instance=owner1)
@@ -861,7 +861,7 @@ class ModelFormsetTest(TestCase):
 
         place = Place.objects.create(pk=1, name='Giordanos', city='Chicago')
 
-        FormSet = inlineformset_factory(Place, Location, can_delete=False)
+        FormSet = inlineformset_factory(Place, Location, can_delete=False, fields="__all__")
         self.assertEqual(FormSet.max_num, 1)
 
         formset = FormSet(instance=place)
@@ -875,7 +875,7 @@ class ModelFormsetTest(TestCase):
         self.assertEqual(type(_get_foreign_key(MexicanRestaurant, Owner)), models.ForeignKey)
 
     def test_unique_validation(self):
-        FormSet = modelformset_factory(Product, extra=1)
+        FormSet = modelformset_factory(Product, fields="__all__", extra=1)
         data = {
             'form-TOTAL_FORMS': '1',
             'form-INITIAL_FORMS': '0',
@@ -915,19 +915,19 @@ class ModelFormsetTest(TestCase):
             'form-1-quantity': '2',
         }
 
-        FormSet = modelformset_factory(Price, extra=1, max_num=1, validate_max=True)
+        FormSet = modelformset_factory(Price, fields="__all__", extra=1, max_num=1, validate_max=True)
         formset = FormSet(data)
         self.assertFalse(formset.is_valid())
         self.assertEqual(formset.non_form_errors(), ['Please submit 1 or fewer forms.'])
 
         # Now test the same thing without the validate_max flag to ensure
         # default behavior is unchanged
-        FormSet = modelformset_factory(Price, extra=1, max_num=1)
+        FormSet = modelformset_factory(Price, fields="__all__", extra=1, max_num=1)
         formset = FormSet(data)
         self.assertTrue(formset.is_valid())
 
     def test_unique_together_validation(self):
-        FormSet = modelformset_factory(Price, extra=1)
+        FormSet = modelformset_factory(Price, fields="__all__", extra=1)
         data = {
             'form-TOTAL_FORMS': '1',
             'form-INITIAL_FORMS': '0',
@@ -958,7 +958,7 @@ class ModelFormsetTest(TestCase):
         # Also see bug #8882.
 
         repository = Repository.objects.create(name='Test Repo')
-        FormSet = inlineformset_factory(Repository, Revision, extra=1)
+        FormSet = inlineformset_factory(Repository, Revision, extra=1, fields="__all__")
         data = {
             'revision_set-TOTAL_FORMS': '1',
             'revision_set-INITIAL_FORMS': '0',
@@ -1007,7 +1007,7 @@ class ModelFormsetTest(TestCase):
         # Use of callable defaults (see bug #7975).
 
         person = Person.objects.create(name='Ringo')
-        FormSet = inlineformset_factory(Person, Membership, can_delete=False, extra=1)
+        FormSet = inlineformset_factory(Person, Membership, can_delete=False, extra=1, fields="__all__")
         formset = FormSet(instance=person)
 
         # Django will render a hidden field for model fields that have a callable
@@ -1057,11 +1057,12 @@ class ModelFormsetTest(TestCase):
             date_joined = forms.SplitDateTimeField(initial=now)
             class Meta:
                 model = Membership
+                fields = "__all__"
             def __init__(self, **kwargs):
                 super(MembershipForm, self).__init__(**kwargs)
                 self.fields['date_joined'].widget = forms.SplitDateTimeWidget()
 
-        FormSet = inlineformset_factory(Person, Membership, form=MembershipForm, can_delete=False, extra=1)
+        FormSet = inlineformset_factory(Person, Membership, form=MembershipForm, can_delete=False, extra=1, fields="__all__")
         data = {
             'membership_set-TOTAL_FORMS': '1',
             'membership_set-INITIAL_FORMS': '0',
@@ -1081,7 +1082,7 @@ class ModelFormsetTest(TestCase):
         Player(name="Timmy").save()
         Player(name="Bobby", team=team).save()
 
-        PlayerInlineFormSet = inlineformset_factory(Team, Player)
+        PlayerInlineFormSet = inlineformset_factory(Team, Player, fields="__all__")
         formset = PlayerInlineFormSet()
         self.assertQuerysetEqual(formset.get_queryset(), [])
 
@@ -1101,7 +1102,7 @@ class ModelFormsetTest(TestCase):
     def test_model_formset_with_initial_model_instance(self):
         # has_changed should compare model instance and primary key
         # see #18898
-        FormSet = modelformset_factory(Poem)
+        FormSet = modelformset_factory(Poem, fields='__all__')
         john_milton = Poet(name="John Milton")
         john_milton.save()
         data = {
@@ -1117,7 +1118,7 @@ class ModelFormsetTest(TestCase):
     def test_model_formset_with_initial_queryset(self):
         # has_changed should work with queryset and list of pk's
         # see #18898
-        FormSet = modelformset_factory(AuthorMeeting)
+        FormSet = modelformset_factory(AuthorMeeting, fields='__all__')
         author = Author.objects.create(pk=1, name='Charles Baudelaire')
         data = {
             'form-TOTAL_FORMS': 1,
@@ -1131,7 +1132,7 @@ class ModelFormsetTest(TestCase):
         self.assertFalse(formset.extra_forms[0].has_changed())
 
     def test_prevent_duplicates_from_with_the_same_formset(self):
-        FormSet = modelformset_factory(Product, extra=2)
+        FormSet = modelformset_factory(Product, fields="__all__", extra=2)
         data = {
             'form-TOTAL_FORMS': 2,
             'form-INITIAL_FORMS': 0,
@@ -1144,7 +1145,7 @@ class ModelFormsetTest(TestCase):
         self.assertEqual(formset._non_form_errors,
             ['Please correct the duplicate data for slug.'])
 
-        FormSet = modelformset_factory(Price, extra=2)
+        FormSet = modelformset_factory(Price, fields="__all__", extra=2)
         data = {
             'form-TOTAL_FORMS': 2,
             'form-INITIAL_FORMS': 0,
@@ -1172,7 +1173,7 @@ class ModelFormsetTest(TestCase):
         formset = FormSet(data)
         self.assertTrue(formset.is_valid())
 
-        FormSet = inlineformset_factory(Author, Book, extra=0)
+        FormSet = inlineformset_factory(Author, Book, extra=0, fields="__all__")
         author = Author.objects.create(pk=1, name='Charles Baudelaire')
         book1 = Book.objects.create(pk=1, author=author, title='Les Paradis Artificiels')
         book2 = Book.objects.create(pk=2, author=author, title='Les Fleurs du Mal')
@@ -1199,7 +1200,7 @@ class ModelFormsetTest(TestCase):
         self.assertEqual(formset.errors,
             [{}, {'__all__': ['Please correct the duplicate values below.']}])
 
-        FormSet = modelformset_factory(Post, extra=2)
+        FormSet = modelformset_factory(Post, fields="__all__", extra=2)
         data = {
             'form-TOTAL_FORMS': '2',
             'form-INITIAL_FORMS': '0',
@@ -1265,7 +1266,7 @@ class TestModelFormsetWidgets(TestCase):
         widgets = {
             'name': forms.TextInput(attrs={'class': 'poet'})
         }
-        PoetFormSet = modelformset_factory(Poet, widgets=widgets)
+        PoetFormSet = modelformset_factory(Poet, fields="__all__", widgets=widgets)
         form = PoetFormSet.form()
         self.assertHTMLEqual(
             "%s" % form['name'],
@@ -1276,7 +1277,7 @@ class TestModelFormsetWidgets(TestCase):
         widgets = {
             'title': forms.TextInput(attrs={'class': 'book'})
         }
-        BookFormSet = inlineformset_factory(Author, Book, widgets=widgets)
+        BookFormSet = inlineformset_factory(Author, Book, widgets=widgets, fields="__all__")
         form = BookFormSet.form()
         self.assertHTMLEqual(
             "%s" % form['title'],

+ 15 - 13
tests/model_formsets_regress/tests.py

@@ -13,8 +13,8 @@ from .models import User, UserSite, Restaurant, Manager, Network, Host
 class InlineFormsetTests(TestCase):
     def test_formset_over_to_field(self):
         "A formset over a ForeignKey with a to_field can be saved. Regression for #10243"
-        Form = modelform_factory(User)
-        FormSet = inlineformset_factory(User, UserSite)
+        Form = modelform_factory(User, fields="__all__")
+        FormSet = inlineformset_factory(User, UserSite, fields="__all__")
 
         # Instantiate the Form and FormSet to prove
         # you can create a form with no data
@@ -89,8 +89,8 @@ class InlineFormsetTests(TestCase):
 
     def test_formset_over_inherited_model(self):
         "A formset over a ForeignKey with a to_field can be saved. Regression for #11120"
-        Form = modelform_factory(Restaurant)
-        FormSet = inlineformset_factory(Restaurant, Manager)
+        Form = modelform_factory(Restaurant, fields="__all__")
+        FormSet = inlineformset_factory(Restaurant, Manager, fields="__all__")
 
         # Instantiate the Form and FormSet to prove
         # you can create a form with no data
@@ -156,8 +156,8 @@ class InlineFormsetTests(TestCase):
 
     def test_formset_with_none_instance(self):
         "A formset with instance=None can be created. Regression for #11872"
-        Form = modelform_factory(User)
-        FormSet = inlineformset_factory(User, UserSite)
+        Form = modelform_factory(User, fields="__all__")
+        FormSet = inlineformset_factory(User, UserSite, fields="__all__")
 
         # Instantiate the Form and FormSet to prove
         # you can create a formset with an instance of None
@@ -182,7 +182,7 @@ class InlineFormsetTests(TestCase):
         efnet = Network.objects.create(name="EFNet")
         host1 = Host.objects.create(hostname="irc.he.net", network=efnet)
 
-        HostFormSet = inlineformset_factory(Network, Host)
+        HostFormSet = inlineformset_factory(Network, Host, fields="__all__")
 
         # Add a new host, modify previous host, and save-as-new
         data = {
@@ -208,7 +208,7 @@ class InlineFormsetTests(TestCase):
     def test_initial_data(self):
         user = User.objects.create(username="bibi", serial=1)
         UserSite.objects.create(user=user, data=7)
-        FormSet = inlineformset_factory(User, UserSite, extra=2)
+        FormSet = inlineformset_factory(User, UserSite, extra=2, fields="__all__")
 
         formset = FormSet(instance=user, initial=[{'data': 41}, {'data': 42}])
         self.assertEqual(formset.forms[0].initial['data'], 7)
@@ -221,7 +221,7 @@ class FormsetTests(TestCase):
         '''
         Test the type of Formset and Form error attributes
         '''
-        Formset = modelformset_factory(User)
+        Formset = modelformset_factory(User, fields="__all__")
         data = {
             'form-TOTAL_FORMS': '2',
             'form-INITIAL_FORMS': '0',
@@ -244,14 +244,14 @@ class FormsetTests(TestCase):
 
     def test_initial_data(self):
         User.objects.create(username="bibi", serial=1)
-        Formset = modelformset_factory(User, extra=2)
+        Formset = modelformset_factory(User, fields="__all__", extra=2)
         formset = Formset(initial=[{'username': 'apollo11'}, {'username': 'apollo12'}])
         self.assertEqual(formset.forms[0].initial['username'], "bibi")
         self.assertEqual(formset.extra_forms[0].initial['username'], "apollo11")
         self.assertTrue('value="apollo12"' in formset.extra_forms[1].as_p())
 
     def test_extraneous_query_is_not_run(self):
-        Formset = modelformset_factory(Network)
+        Formset = modelformset_factory(Network, fields="__all__")
         data = {'test-TOTAL_FORMS': '1',
                 'test-INITIAL_FORMS': '0',
                 'test-MAX_NUM_FORMS': '',
@@ -268,6 +268,7 @@ class CustomWidget(forms.widgets.TextInput):
 class UserSiteForm(forms.ModelForm):
     class Meta:
         model = UserSite
+        fields = "__all__"
         widgets = {
             'id': CustomWidget,
             'data': CustomWidget,
@@ -292,7 +293,7 @@ class FormfieldCallbackTests(TestCase):
     """
 
     def test_inlineformset_factory_default(self):
-        Formset = inlineformset_factory(User, UserSite, form=UserSiteForm)
+        Formset = inlineformset_factory(User, UserSite, form=UserSiteForm, fields="__all__")
         form = Formset().forms[0]
         self.assertTrue(isinstance(form['id'].field.widget, CustomWidget))
         self.assertTrue(isinstance(form['data'].field.widget, CustomWidget))
@@ -315,7 +316,7 @@ class FormfieldCallbackTests(TestCase):
     def test_inlineformset_custom_callback(self):
         callback = Callback()
         inlineformset_factory(User, UserSite, form=UserSiteForm,
-                              formfield_callback=callback)
+                              formfield_callback=callback, fields="__all__")
         self.assertCallbackCalled(callback)
 
     def test_modelformset_custom_callback(self):
@@ -353,6 +354,7 @@ class FormfieldShouldDeleteFormTests(TestCase):
         """ A model form with a 'should_delete' method """
         class Meta:
             model = User
+            fields = "__all__"
 
         def should_delete(self):
             """ delete form if odd PK """

+ 2 - 0
tests/model_inheritance_regress/tests.py

@@ -418,6 +418,8 @@ class ModelInheritanceTest(TestCase):
         class ProfileForm(forms.ModelForm):
             class Meta:
                 model = Profile
+                fields = '__all__'
+
         User.objects.create(username="user_only")
         p = Profile.objects.create(username="user_with_profile")
         form = ProfileForm({'username': "user_with_profile", 'extra': "hello"},

+ 1 - 8
tests/modeladmin/tests.py

@@ -229,9 +229,6 @@ class ModelAdminTests(TestCase):
         class AdminBandForm(forms.ModelForm):
             delete = forms.BooleanField()
 
-            class Meta:
-                model = Band
-
         class BandAdmin(ModelAdmin):
             form = AdminBandForm
 
@@ -319,8 +316,7 @@ class ModelAdminTests(TestCase):
             '</select>' % (band2.id, self.band.id))
 
         class AdminConcertForm(forms.ModelForm):
-            class Meta:
-                model = Concert
+            pass
 
             def __init__(self, *args, **kwargs):
                 super(AdminConcertForm, self).__init__(*args, **kwargs)
@@ -685,9 +681,6 @@ class ValidationTests(unittest.TestCase):
         class AdminBandForm(forms.ModelForm):
             delete = forms.BooleanField()
 
-            class Meta:
-                model = Band
-
         class BandAdmin(ModelAdmin):
             form = AdminBandForm
 

+ 1 - 0
tests/timezones/forms.py

@@ -11,3 +11,4 @@ class EventSplitForm(forms.Form):
 class EventModelForm(forms.ModelForm):
     class Meta:
         model = Event
+        fields = '__all__'