diff --git a/django/forms/forms.py b/django/forms/forms.py
index 8d0fa23daa..15c3e2c3f8 100644
--- a/django/forms/forms.py
+++ b/django/forms/forms.py
@@ -90,6 +90,11 @@ class DeclarativeFieldsMetaclass(MediaDefiningClass):
if hasattr(base, 'declared_fields'):
declared_fields.update(base.declared_fields)
+ # Field shadowing.
+ for attr in base.__dict__.keys():
+ if attr in declared_fields:
+ declared_fields.pop(attr)
+
new_class.base_fields = declared_fields
new_class.declared_fields = declared_fields
diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt
index 14092512dc..c15f748308 100644
--- a/docs/ref/forms/api.txt
+++ b/docs/ref/forms/api.txt
@@ -854,6 +854,13 @@ classes::
Instrument:
Haircut type:
+.. versionadded:: 1.7
+
+* It's possible to opt-out from a ``Field`` inherited from a parent class by
+ shadowing it. While any non-``Field`` value works for this purpose, it's
+ recommended to use ``None`` to make it explicit that a field is being
+ nullified.
+
.. _form-prefix:
Prefixes for forms
diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt
index 87bf8481a0..5c208e22ac 100644
--- a/docs/releases/1.7.txt
+++ b/docs/releases/1.7.txt
@@ -293,6 +293,9 @@ Forms
inheriting from both ``Form`` and ``ModelForm`` simultaneously have been
removed as long as ``ModelForm`` appears first in the MRO.
+* It's now possible to opt-out from a ``Form`` field declared in a parent class
+ by shadowing it with a non-``Field`` value.
+
Internationalization
^^^^^^^^^^^^^^^^^^^^
diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt
index edf9de17dd..2a48aa75dc 100644
--- a/docs/topics/forms/modelforms.txt
+++ b/docs/topics/forms/modelforms.txt
@@ -651,6 +651,18 @@ There are a couple of things to note, however.
because these classes rely on different metaclasses and a class can only have
one metaclass.
+.. versionadded:: 1.7
+
+* It's possible to opt-out from a ``Field`` inherited from a parent class by
+ shadowing it. While any non-``Field`` value works for this purpose, it's
+ recommended to use ``None`` to make it explicit that a field is being
+ nullified.
+
+ You can only use this technique to opt out from a field defined declaratively
+ by a parent class; it won't prevent the ``ModelForm`` metaclass from generating
+ a default field. To opt-out from default fields, see
+ :ref:`controlling-fields-with-fields-and-exclude`.
+
.. _modelforms-factory:
ModelForm factory function
@@ -749,6 +761,8 @@ instances of the model, you can specify an empty QuerySet::
>>> AuthorFormSet(queryset=Author.objects.none())
+.. _controlling-fields-with-fields-and-exclude:
+
Controlling which fields are used with ``fields`` and ``exclude``
-----------------------------------------------------------------
diff --git a/tests/model_forms/tests.py b/tests/model_forms/tests.py
index 67aedd06d0..2f94f64128 100644
--- a/tests/model_forms/tests.py
+++ b/tests/model_forms/tests.py
@@ -1815,3 +1815,26 @@ class ModelFormInheritanceTests(TestCase):
fields = '__all__'
self.assertEqual(list(ModelForm().fields.keys()), ['name', 'age'])
+
+ def test_field_shadowing(self):
+ class ModelForm(forms.ModelForm):
+ class Meta:
+ model = Writer
+ fields = '__all__'
+
+ class Mixin(object):
+ age = None
+
+ class Form(forms.Form):
+ age = forms.IntegerField()
+
+ class Form2(forms.Form):
+ foo = forms.IntegerField()
+
+ self.assertEqual(list(ModelForm().fields.keys()), ['name'])
+ self.assertEqual(list(type(str('NewForm'), (Mixin, Form), {})().fields.keys()), [])
+ self.assertEqual(list(type(str('NewForm'), (Form2, Mixin, Form), {})().fields.keys()), ['foo'])
+ self.assertEqual(list(type(str('NewForm'), (Mixin, ModelForm, Form), {})().fields.keys()), ['name'])
+ self.assertEqual(list(type(str('NewForm'), (ModelForm, Mixin, Form), {})().fields.keys()), ['name'])
+ self.assertEqual(list(type(str('NewForm'), (ModelForm, Form, Mixin), {})().fields.keys()), ['name', 'age'])
+ self.assertEqual(list(type(str('NewForm'), (ModelForm, Form), {'age': None})().fields.keys()), ['name'])