From b16dd1fe019b38d0dcdf4a400e9377fb06b33178 Mon Sep 17 00:00:00 2001 From: Loic Bistuer Date: Mon, 14 Oct 2013 22:42:33 +0700 Subject: [PATCH] Fixed #8620 -- Updated the Form metaclass to support excluding fields by shadowing them. --- django/forms/forms.py | 5 +++++ docs/ref/forms/api.txt | 7 +++++++ docs/releases/1.7.txt | 3 +++ docs/topics/forms/modelforms.txt | 14 ++++++++++++++ tests/model_forms/tests.py | 23 +++++++++++++++++++++++ 5 files changed, 52 insertions(+) diff --git a/django/forms/forms.py b/django/forms/forms.py index 8d0fa23daa6b6..15c3e2c3f881c 100644 --- a/django/forms/forms.py +++ b/django/forms/forms.py @@ -90,6 +90,11 @@ def __new__(mcs, name, bases, attrs): 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 14092512dc359..c15f7483081f6 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 87bf8481a0efc..5c208e22acf2d 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 edf9de17dd2a9..2a48aa75dc543 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 67aedd06d014e..2f94f641289d9 100644 --- a/tests/model_forms/tests.py +++ b/tests/model_forms/tests.py @@ -1815,3 +1815,26 @@ class Meta: 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'])