summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/forms/models.py13
-rw-r--r--docs/releases/4.2.txt6
-rw-r--r--docs/topics/forms/modelforms.txt22
-rw-r--r--tests/model_forms/tests.py35
4 files changed, 63 insertions, 13 deletions
diff --git a/django/forms/models.py b/django/forms/models.py
index 192d9fad94..89cb000271 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -253,18 +253,11 @@ class ModelFormOptions:
self.help_texts = getattr(options, "help_texts", None)
self.error_messages = getattr(options, "error_messages", None)
self.field_classes = getattr(options, "field_classes", None)
+ self.formfield_callback = getattr(options, "formfield_callback", None)
class ModelFormMetaclass(DeclarativeFieldsMetaclass):
def __new__(mcs, name, bases, attrs):
- base_formfield_callback = None
- for b in bases:
- if hasattr(b, "Meta") and hasattr(b.Meta, "formfield_callback"):
- base_formfield_callback = b.Meta.formfield_callback
- break
-
- formfield_callback = attrs.pop("formfield_callback", base_formfield_callback)
-
new_class = super().__new__(mcs, name, bases, attrs)
if bases == (BaseModelForm,):
@@ -308,7 +301,7 @@ class ModelFormMetaclass(DeclarativeFieldsMetaclass):
opts.fields,
opts.exclude,
opts.widgets,
- formfield_callback,
+ opts.formfield_callback,
opts.localized_fields,
opts.labels,
opts.help_texts,
@@ -636,7 +629,7 @@ def modelform_factory(
class_name = model.__name__ + "Form"
# Class attributes for the new form class.
- form_class_attrs = {"Meta": Meta, "formfield_callback": formfield_callback}
+ form_class_attrs = {"Meta": Meta}
if getattr(Meta, "fields", None) is None and getattr(Meta, "exclude", None) is None:
raise ImproperlyConfigured(
diff --git a/docs/releases/4.2.txt b/docs/releases/4.2.txt
index 92ccb8e6dd..6760848976 100644
--- a/docs/releases/4.2.txt
+++ b/docs/releases/4.2.txt
@@ -151,7 +151,11 @@ File Uploads
Forms
~~~~~
-* ...
+* :class:`~django.forms.ModelForm` now accepts the new ``Meta`` option
+ ``formfield_callback`` to customize form fields.
+
+* :func:`~django.forms.models.modelform_factory` now respects the
+ ``formfield_callback`` attribute of the ``form``’s ``Meta``.
Generic Views
~~~~~~~~~~~~~
diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt
index 3332709089..9000ff24f4 100644
--- a/docs/topics/forms/modelforms.txt
+++ b/docs/topics/forms/modelforms.txt
@@ -548,8 +548,8 @@ the ``name`` field::
},
}
-You can also specify ``field_classes`` to customize the type of fields
-instantiated by the form.
+You can also specify ``field_classes`` or ``formfield_callback`` to customize
+the type of fields instantiated by the form.
For example, if you wanted to use ``MySlugFormField`` for the ``slug``
field, you could do the following::
@@ -565,6 +565,21 @@ field, you could do the following::
'slug': MySlugFormField,
}
+or::
+
+ from django.forms import ModelForm
+ from myapp.models import Article
+
+ def formfield_for_dbfield(db_field, **kwargs):
+ if db_field.name == "slug":
+ return MySlugFormField()
+ return db_field.formfield(**kwargs)
+
+ class ArticleForm(ModelForm):
+ class Meta:
+ model = Article
+ fields = ["pub_date", "headline", "content", "reporter", "slug"]
+ formfield_callback = formfield_for_dbfield
Finally, if you want complete control over of a field -- including its type,
validators, required, etc. -- you can do this by declaratively specifying
@@ -638,6 +653,9 @@ the field declaratively and setting its ``validators`` parameter::
See the :doc:`form field documentation </ref/forms/fields>` for more information
on fields and their arguments.
+.. versionchanged:: 4.2
+
+ The ``Meta.formfield_callback`` attribute was added.
Enabling localization of fields
-------------------------------
diff --git a/tests/model_forms/tests.py b/tests/model_forms/tests.py
index d2ce3348c5..8268032e3c 100644
--- a/tests/model_forms/tests.py
+++ b/tests/model_forms/tests.py
@@ -3496,6 +3496,41 @@ class FormFieldCallbackTests(SimpleTestCase):
type(NewForm.base_fields[name].widget),
)
+ def test_custom_callback_in_meta(self):
+ def callback(db_field, **kwargs):
+ return forms.CharField(widget=forms.Textarea)
+
+ class NewForm(forms.ModelForm):
+ class Meta:
+ model = Person
+ fields = ["id", "name"]
+ formfield_callback = callback
+
+ for field in NewForm.base_fields.values():
+ self.assertEqual(type(field.widget), forms.Textarea)
+
+ def test_custom_callback_from_base_form_meta(self):
+ def callback(db_field, **kwargs):
+ return forms.CharField(widget=forms.Textarea)
+
+ class BaseForm(forms.ModelForm):
+ class Meta:
+ model = Person
+ fields = "__all__"
+ formfield_callback = callback
+
+ NewForm = modelform_factory(model=Person, form=BaseForm)
+
+ class InheritedForm(NewForm):
+ pass
+
+ for name, field in NewForm.base_fields.items():
+ self.assertEqual(type(field.widget), forms.Textarea)
+ self.assertEqual(
+ type(field.widget),
+ type(InheritedForm.base_fields[name].widget),
+ )
+
class LocalizedModelFormTest(TestCase):
def test_model_form_applies_localize_to_some_fields(self):