diff --git a/django/forms/fields.py b/django/forms/fields.py index 81b3e2f4f9..950f9481a3 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -181,17 +181,6 @@ class Field(six.with_metaclass(RenameFieldMethods, object)): """ return {} - def get_limit_choices_to(self): - """ - Returns ``limit_choices_to`` for this form field. - - If it is a callable, it will be invoked and the result will be - returned. - """ - if callable(self.limit_choices_to): - return self.limit_choices_to() - return self.limit_choices_to - def has_changed(self, initial, data): """ Return True if data differs from initial. diff --git a/django/forms/models.py b/django/forms/models.py index acb7f91a76..b560ee84a9 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -328,11 +328,9 @@ class BaseModelForm(BaseForm): # Apply ``limit_choices_to`` to each field. for field_name in self.fields: formfield = self.fields[field_name] - if hasattr(formfield, 'queryset'): - limit_choices_to = formfield.limit_choices_to + if hasattr(formfield, 'queryset') and hasattr(formfield, 'get_limit_choices_to'): + limit_choices_to = formfield.get_limit_choices_to() if limit_choices_to is not None: - if callable(limit_choices_to): - limit_choices_to = limit_choices_to() formfield.queryset = formfield.queryset.complex_filter(limit_choices_to) def _get_validation_exclusions(self): @@ -1133,6 +1131,17 @@ class ModelChoiceField(ChoiceField): self.choice_cache = None self.to_field_name = to_field_name + def get_limit_choices_to(self): + """ + Returns ``limit_choices_to`` for this form field. + + If it is a callable, it will be invoked and the result will be + returned. + """ + if callable(self.limit_choices_to): + return self.limit_choices_to() + return self.limit_choices_to + def __deepcopy__(self, memo): result = super(ChoiceField, self).__deepcopy__(memo) # Need to force a new ModelChoiceIterator to be created, bug #11183 diff --git a/docs/releases/1.7.2.txt b/docs/releases/1.7.2.txt index 3d9a74616b..207b7d2a0c 100644 --- a/docs/releases/1.7.2.txt +++ b/docs/releases/1.7.2.txt @@ -52,3 +52,7 @@ Bugfixes * Fixed a migration serializing bug involving ``float("nan")`` and ``float("inf")`` (:ticket:23770:). + +* Fixed a regression where custom form fields having a ``queryset`` attribute + but no ``limit_choices_to`` could not be used in a + :class:`~django.forms.ModelForm` (:ticket:`23795`). diff --git a/tests/model_forms/tests.py b/tests/model_forms/tests.py index 98de9c95f2..86a027e4d4 100644 --- a/tests/model_forms/tests.py +++ b/tests/model_forms/tests.py @@ -2377,6 +2377,17 @@ class StumpJokeForm(forms.ModelForm): fields = '__all__' +class CustomFieldWithQuerysetButNoLimitChoicesTo(forms.Field): + queryset = 42 + + +class StumpJokeWithCustomFieldForm(forms.ModelForm): + custom = CustomFieldWithQuerysetButNoLimitChoicesTo() + class Meta: + model = StumpJoke + fields = () # We don't need any fields from the model + + class LimitChoicesToTest(TestCase): """ Tests the functionality of ``limit_choices_to``. @@ -2407,6 +2418,14 @@ class LimitChoicesToTest(TestCase): self.assertIn(self.threepwood, stumpjokeform.fields['has_fooled_today'].queryset) self.assertNotIn(self.marley, stumpjokeform.fields['has_fooled_today'].queryset) + def test_custom_field_with_queryset_but_no_limit_choices_to(self): + """ + Regression test for #23795: Make sure a custom field with a `queryset` + attribute but no `limit_choices_to` still works. + """ + f = StumpJokeWithCustomFieldForm() + self.assertEqual(f.fields['custom'].queryset, 42) + class FormFieldCallbackTests(TestCase):