Fixed #31721 -- Allowed ModelForm meta to specify form fields.
This commit is contained in:
parent
88e67a54b7
commit
e03cdf76e7
|
@ -253,18 +253,11 @@ class ModelFormOptions:
|
||||||
self.help_texts = getattr(options, "help_texts", None)
|
self.help_texts = getattr(options, "help_texts", None)
|
||||||
self.error_messages = getattr(options, "error_messages", None)
|
self.error_messages = getattr(options, "error_messages", None)
|
||||||
self.field_classes = getattr(options, "field_classes", None)
|
self.field_classes = getattr(options, "field_classes", None)
|
||||||
|
self.formfield_callback = getattr(options, "formfield_callback", None)
|
||||||
|
|
||||||
|
|
||||||
class ModelFormMetaclass(DeclarativeFieldsMetaclass):
|
class ModelFormMetaclass(DeclarativeFieldsMetaclass):
|
||||||
def __new__(mcs, name, bases, attrs):
|
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)
|
new_class = super().__new__(mcs, name, bases, attrs)
|
||||||
|
|
||||||
if bases == (BaseModelForm,):
|
if bases == (BaseModelForm,):
|
||||||
|
@ -308,7 +301,7 @@ class ModelFormMetaclass(DeclarativeFieldsMetaclass):
|
||||||
opts.fields,
|
opts.fields,
|
||||||
opts.exclude,
|
opts.exclude,
|
||||||
opts.widgets,
|
opts.widgets,
|
||||||
formfield_callback,
|
opts.formfield_callback,
|
||||||
opts.localized_fields,
|
opts.localized_fields,
|
||||||
opts.labels,
|
opts.labels,
|
||||||
opts.help_texts,
|
opts.help_texts,
|
||||||
|
@ -636,7 +629,7 @@ def modelform_factory(
|
||||||
class_name = model.__name__ + "Form"
|
class_name = model.__name__ + "Form"
|
||||||
|
|
||||||
# Class attributes for the new form class.
|
# 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:
|
if getattr(Meta, "fields", None) is None and getattr(Meta, "exclude", None) is None:
|
||||||
raise ImproperlyConfigured(
|
raise ImproperlyConfigured(
|
||||||
|
|
|
@ -151,7 +151,11 @@ File Uploads
|
||||||
Forms
|
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
|
Generic Views
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
|
@ -548,8 +548,8 @@ the ``name`` field::
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
You can also specify ``field_classes`` to customize the type of fields
|
You can also specify ``field_classes`` or ``formfield_callback`` to customize
|
||||||
instantiated by the form.
|
the type of fields instantiated by the form.
|
||||||
|
|
||||||
For example, if you wanted to use ``MySlugFormField`` for the ``slug``
|
For example, if you wanted to use ``MySlugFormField`` for the ``slug``
|
||||||
field, you could do the following::
|
field, you could do the following::
|
||||||
|
@ -565,6 +565,21 @@ field, you could do the following::
|
||||||
'slug': MySlugFormField,
|
'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,
|
Finally, if you want complete control over of a field -- including its type,
|
||||||
validators, required, etc. -- you can do this by declaratively specifying
|
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
|
See the :doc:`form field documentation </ref/forms/fields>` for more information
|
||||||
on fields and their arguments.
|
on fields and their arguments.
|
||||||
|
|
||||||
|
.. versionchanged:: 4.2
|
||||||
|
|
||||||
|
The ``Meta.formfield_callback`` attribute was added.
|
||||||
|
|
||||||
Enabling localization of fields
|
Enabling localization of fields
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
|
@ -3496,6 +3496,41 @@ class FormFieldCallbackTests(SimpleTestCase):
|
||||||
type(NewForm.base_fields[name].widget),
|
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):
|
class LocalizedModelFormTest(TestCase):
|
||||||
def test_model_form_applies_localize_to_some_fields(self):
|
def test_model_form_applies_localize_to_some_fields(self):
|
||||||
|
|
Loading…
Reference in New Issue