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.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(
|
||||
|
|
|
@ -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
|
||||
~~~~~~~~~~~~~
|
||||
|
|
|
@ -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
|
||||
-------------------------------
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue