diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index a6b37bcda6..fe24fab589 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -636,7 +636,7 @@ class Field(object): def save_form_data(self, instance, data): setattr(instance, self.name, data) - def formfield(self, form_class=None, **kwargs): + def formfield(self, form_class=None, choices_form_class=None, **kwargs): """ Returns a django.forms.Field instance for this database Field. """ @@ -657,7 +657,9 @@ class Field(object): defaults['coerce'] = self.to_python if self.null: defaults['empty_value'] = None - if form_class is None or not issubclass(form_class, forms.TypedChoiceField): + if choices_form_class is not None: + form_class = choices_form_class + else: form_class = forms.TypedChoiceField # Many of the subclass-specific formfield arguments (min_value, # max_value) don't apply for choice fields, so be sure to only pass diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt index 6493c18f99..0692e97156 100644 --- a/docs/howto/custom-model-fields.txt +++ b/docs/howto/custom-model-fields.txt @@ -617,17 +617,23 @@ prepared with :meth:`.get_prep_lookup`. Specifying the form field for a model field ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. method:: Field.formfield(self, form_class=forms.CharField, **kwargs) +.. method:: Field.formfield(self, form_class=None, choices_form_class=None, **kwargs) -Returns the default form field to use when this field is displayed in a model. -This method is called by the :class:`~django.forms.ModelForm` helper. +Returns the default form field to use when this model field is displayed in a +form. This method is called by the :class:`~django.forms.ModelForm` helper. + +The form field class can be specified via the ``form_class`` and +``choices_form_class`` arguments; the latter is used if the field has choices +specified, the former otherwise. If these arguments are not provided, +:class:`~django.forms.CharField` or :class:`~django.forms.TypedChoiceField` +will be used. All of the ``kwargs`` dictionary is passed directly to the form field's ``__init__()`` method. Normally, all you need to do is set up a good default -for the ``form_class`` argument and then delegate further handling to the -parent class. This might require you to write a custom form field (and even a -form widget). See the :doc:`forms documentation ` for -information about this. +for the ``form_class`` (and maybe ``choices_form_class``) argument and then +delegate further handling to the parent class. This might require you to write +a custom form field (and even a form widget). See the :doc:`forms documentation +` for information about this. Continuing our ongoing example, we can write the :meth:`.formfield` method as:: diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py index 0ad0e168d7..eee3f31532 100644 --- a/tests/model_fields/tests.py +++ b/tests/model_fields/tests.py @@ -78,14 +78,12 @@ class BasicFieldTests(test.TestCase): self.assertEqual(m._meta.get_field('id').verbose_name, 'verbose pk') - def test_formclass_with_choices(self): - # regression for 18162 - class CustomChoiceField(forms.TypedChoiceField): - pass - choices = [('a@b.cc', 'a@b.cc'), ('b@b.cc', 'b@b.cc')] + def test_choices_form_class(self): + """Can supply a custom choices form class. Regression for #20999.""" + choices = [('a', 'a')] field = models.CharField(choices=choices) - klass = CustomChoiceField - self.assertIsInstance(field.formfield(form_class=klass), klass) + klass = forms.TypedMultipleChoiceField + self.assertIsInstance(field.formfield(choices_form_class=klass), klass) class DecimalFieldTests(test.TestCase):