summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorLuke Plant <L.Plant.98@cantab.net>2013-02-21 21:56:55 +0000
committerLuke Plant <L.Plant.98@cantab.net>2013-05-09 16:44:36 +0100
commitf026a519aea8f3ea7ca339bfbbb007e1ee0068b0 (patch)
tree882e594178a78d507ede36c2c9b0b8d5a9305561 /django
parent1e37cb37cec330a6b78925e2ef5589817428d09a (diff)
Fixed #19733 - deprecated ModelForms without 'fields' or 'exclude', and added '__all__' shortcut
This also updates all dependent functionality, including modelform_factory and modelformset_factory, and the generic views `ModelFormMixin`, `CreateView` and `UpdateView` which gain a new `fields` attribute.
Diffstat (limited to 'django')
-rw-r--r--django/contrib/admin/options.py14
-rw-r--r--django/contrib/auth/forms.py1
-rw-r--r--django/contrib/contenttypes/generic.py9
-rw-r--r--django/contrib/flatpages/forms.py1
-rw-r--r--django/contrib/formtools/tests/wizard/test_forms.py4
-rw-r--r--django/contrib/formtools/tests/wizard/wizardtests/tests.py1
-rw-r--r--django/forms/models.py57
-rw-r--r--django/views/generic/edit.py11
8 files changed, 91 insertions, 7 deletions
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index 543655b357..9bc3d55454 100644
--- a/django/contrib/admin/options.py
+++ b/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):
diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index 42abde2f19..e44e7a703e 100644
--- a/django/contrib/auth/forms.py
+++ b/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)
diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py
index cc03f799f3..399d24aa87 100644
--- a/django/contrib/contenttypes/generic.py
+++ b/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):
diff --git a/django/contrib/flatpages/forms.py b/django/contrib/flatpages/forms.py
index a848875a9f..80938116ad 100644
--- a/django/contrib/flatpages/forms.py
+++ b/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']
diff --git a/django/contrib/formtools/tests/wizard/test_forms.py b/django/contrib/formtools/tests/wizard/test_forms.py
index 14c6e6a685..7425755cf5 100644
--- a/django/contrib/formtools/tests/wizard/test_forms.py
+++ b/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):
diff --git a/django/contrib/formtools/tests/wizard/wizardtests/tests.py b/django/contrib/formtools/tests/wizard/wizardtests/tests.py
index 1ee5dbdc78..3c2dbc3efb 100644
--- a/django/contrib/formtools/tests/wizard/wizardtests/tests.py
+++ b/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)
diff --git a/django/forms/models.py b/django/forms/models.py
index 5e7797809a..af5cda8faf 100644
--- a/django/forms/models.py
+++ b/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)
+ ))
diff --git a/django/views/generic/edit.py b/django/views/generic/edit.py
index 5b97fc81c9..e2cc741ffb 100644
--- a/django/views/generic/edit.py
+++ b/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):
"""