summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Dufresne <jon.dufresne@gmail.com>2017-06-28 20:30:19 -0700
committerTim Graham <timograham@gmail.com>2017-06-30 21:28:47 -0400
commit8641489f4dffdeff0897d7d715d02a50ff0b23cf (patch)
treec7c80a252fe4af689def4bdc9ea1ce9e0cd25f17
parentc1621d80089cb3e77c8a504310feeb24fea61afe (diff)
[1.11.x] Fixed #28345 -- Applied limit_choices_to during ModelForm.__init__().
field_for_model() now has an additional keyword argument, apply_limit_choices_to, allowing it to continue to be used to create form fields dynamically after ModelForm.__init__() is called. Thanks Tim Graham for the review. Backport of a1be12fe193c8f3de8a0b0820f460a302472375f from master
-rw-r--r--django/forms/models.py34
-rw-r--r--docs/releases/1.11.3.txt3
-rw-r--r--tests/model_forms/tests.py12
3 files changed, 37 insertions, 12 deletions
diff --git a/django/forms/models.py b/django/forms/models.py
index ed9df60321..132e4255bb 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -97,10 +97,18 @@ def model_to_dict(instance, fields=None, exclude=None):
return data
+def apply_limit_choices_to_to_formfield(formfield):
+ """Apply limit_choices_to to the formfield's queryset if needed."""
+ 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:
+ formfield.queryset = formfield.queryset.complex_filter(limit_choices_to)
+
+
def fields_for_model(model, fields=None, exclude=None, widgets=None,
formfield_callback=None, localized_fields=None,
labels=None, help_texts=None, error_messages=None,
- field_classes=None):
+ field_classes=None, apply_limit_choices_to=True):
"""
Returns a ``OrderedDict`` containing form fields for the given model.
@@ -127,6 +135,9 @@ def fields_for_model(model, fields=None, exclude=None, widgets=None,
``field_classes`` is a dictionary of model field names mapped to a form
field class.
+
+ ``apply_limit_choices_to`` is a boolean indicating if limit_choices_to
+ should be applied to a field's queryset.
"""
field_list = []
ignored = []
@@ -170,11 +181,8 @@ def fields_for_model(model, fields=None, exclude=None, widgets=None,
formfield = formfield_callback(f, **kwargs)
if formfield:
- # Apply ``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:
- formfield.queryset = formfield.queryset.complex_filter(limit_choices_to)
+ if apply_limit_choices_to:
+ apply_limit_choices_to_to_formfield(formfield)
field_list.append((f.name, formfield))
else:
ignored.append(f.name)
@@ -245,11 +253,13 @@ class ModelFormMetaclass(DeclarativeFieldsMetaclass):
# fields from the model"
opts.fields = None
- fields = fields_for_model(opts.model, opts.fields, opts.exclude,
- opts.widgets, formfield_callback,
- opts.localized_fields, opts.labels,
- opts.help_texts, opts.error_messages,
- opts.field_classes)
+ fields = fields_for_model(
+ opts.model, opts.fields, opts.exclude, opts.widgets,
+ formfield_callback, opts.localized_fields, opts.labels,
+ opts.help_texts, opts.error_messages, opts.field_classes,
+ # limit_choices_to will be applied during ModelForm.__init__().
+ apply_limit_choices_to=False,
+ )
# make sure opts.fields doesn't specify an invalid field
none_model_fields = [k for k, v in six.iteritems(fields) if not v]
@@ -296,6 +306,8 @@ class BaseModelForm(BaseForm):
data, files, auto_id, prefix, object_data, error_class,
label_suffix, empty_permitted, use_required_attribute=use_required_attribute,
)
+ for formfield in self.fields.values():
+ apply_limit_choices_to_to_formfield(formfield)
def _get_validation_exclusions(self):
"""
diff --git a/docs/releases/1.11.3.txt b/docs/releases/1.11.3.txt
index 227c94abb4..79fd3edb6a 100644
--- a/docs/releases/1.11.3.txt
+++ b/docs/releases/1.11.3.txt
@@ -57,3 +57,6 @@ Bugfixes
* Fixed ``UnboundLocalError`` crash in ``RenameField`` with nonexistent field
(:ticket:`28350`).
+
+* Fixed a regression preventing a model field's ``limit_choices_to`` from being
+ evaluated when a ``ModelForm`` is instantiated (:ticket:`28345`).
diff --git a/tests/model_forms/tests.py b/tests/model_forms/tests.py
index fe9a153a90..f33278f88a 100644
--- a/tests/model_forms/tests.py
+++ b/tests/model_forms/tests.py
@@ -19,7 +19,7 @@ from django.forms.models import (
)
from django.forms.widgets import CheckboxSelectMultiple
from django.template import Context, Template
-from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
+from django.test import SimpleTestCase, TestCase, mock, skipUnlessDBFeature
from django.utils import six
from django.utils._os import upath
@@ -2943,6 +2943,16 @@ class LimitChoicesToTests(TestCase):
fields = fields_for_model(StumpJoke, ['has_fooled_today'])
self.assertSequenceEqual(fields['has_fooled_today'].queryset, [self.threepwood])
+ def test_callable_called_each_time_form_is_instantiated(self):
+ field = StumpJokeForm.base_fields['most_recently_fooled']
+ with mock.patch.object(field, 'limit_choices_to') as today_callable_dict:
+ StumpJokeForm()
+ self.assertEqual(today_callable_dict.call_count, 1)
+ StumpJokeForm()
+ self.assertEqual(today_callable_dict.call_count, 2)
+ StumpJokeForm()
+ self.assertEqual(today_callable_dict.call_count, 3)
+
class FormFieldCallbackTests(SimpleTestCase):