diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index 0ba9d260a0..025b7d28ed 100644 --- a/django/contrib/admin/views/main.py +++ b/django/contrib/admin/views/main.py @@ -714,7 +714,7 @@ class ChangeList(object): qs = qs & other_qs if self.opts.one_to_one_field: - qs = qs.filter(**self.opts.one_to_one_field.rel.limit_choices_to) + qs = qs.complex_filter(self.opts.one_to_one_field.rel.limit_choices_to) return qs diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 8cc17079a9..023d505eec 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -295,7 +295,7 @@ class Field(object): return first_choice + list(self.choices) rel_model = self.rel.to return first_choice + [(x._get_pk_val(), str(x)) - for x in rel_model._default_manager.filter(**self.rel.limit_choices_to)] + for x in rel_model._default_manager.complex_filter(self.rel.limit_choices_to)] def get_choices_default(self): if self.radio_admin: diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index f3b67862dd..5f6ec83bb6 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -679,7 +679,9 @@ class ManyToOneRel: self.num_in_admin, self.edit_inline = num_in_admin, edit_inline self.min_num_in_admin, self.max_num_in_admin = min_num_in_admin, max_num_in_admin self.num_extra_on_change, self.related_name = num_extra_on_change, related_name - self.limit_choices_to = limit_choices_to or {} + if limit_choices_to is None: + limit_choices_to = {} + self.limit_choices_to = limit_choices_to self.lookup_overrides = lookup_overrides or {} self.raw_id_admin = raw_id_admin self.multiple = True @@ -695,7 +697,9 @@ class OneToOneRel(ManyToOneRel): self.to, self.field_name = to, field_name self.num_in_admin, self.edit_inline = num_in_admin, edit_inline self.related_name = related_name - self.limit_choices_to = limit_choices_to or {} + if limit_choices_to is None: + limit_choices_to = {} + self.limit_choices_to = limit_choices_to self.lookup_overrides = lookup_overrides or {} self.raw_id_admin = raw_id_admin self.multiple = False @@ -707,7 +711,9 @@ class ManyToManyRel: self.num_in_admin = num_in_admin self.related_name = related_name self.filter_interface = filter_interface - self.limit_choices_to = limit_choices_to or {} + if limit_choices_to is None: + limit_choices_to = {} + self.limit_choices_to = limit_choices_to self.edit_inline = False self.raw_id_admin = raw_id_admin self.symmetrical = symmetrical diff --git a/django/db/models/manager.py b/django/db/models/manager.py index d847631c82..93de4a6adc 100644 --- a/django/db/models/manager.py +++ b/django/db/models/manager.py @@ -68,6 +68,9 @@ class Manager(object): def filter(self, *args, **kwargs): return self.get_query_set().filter(*args, **kwargs) + def complex_filter(self, *args, **kwargs): + return self.get_query_set().complex_filter(*args, **kwargs) + def exclude(self, *args, **kwargs): return self.get_query_set().exclude(*args, **kwargs) diff --git a/django/db/models/manipulators.py b/django/db/models/manipulators.py index fc553bc90c..454c318e5d 100644 --- a/django/db/models/manipulators.py +++ b/django/db/models/manipulators.py @@ -261,9 +261,9 @@ class AutomaticChangeManipulator(AutomaticManipulator): # Sanity check -- Make sure the "parent" object exists. # For example, make sure the Place exists for the Restaurant. # Let the ObjectDoesNotExist exception propagate up. - lookup_kwargs = self.opts.one_to_one_field.rel.limit_choices_to - lookup_kwargs['%s__exact' % self.opts.one_to_one_field.rel.field_name] = obj_key - self.opts.one_to_one_field.rel.to.get_model_module().get(**lookup_kwargs) + limit_choices_to = self.opts.one_to_one_field.rel.limit_choices_to + lookup_kwargs = {'%s__exact' % self.opts.one_to_one_field.rel.field_name: obj_key} + self.opts.one_to_one_field.rel.to.get_model_module().complex_filter(limit_choices_to).get(**lookup_kwargs) params = dict([(f.attname, f.get_default()) for f in self.opts.fields]) params[self.opts.pk.attname] = obj_key self.original_object = self.opts.get_model_module().Klass(**params) diff --git a/django/db/models/query.py b/django/db/models/query.py index 365ead2a3a..36d7d405bf 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -297,6 +297,17 @@ class QuerySet(object): clone._filters = clone._filters & reduce(operator.and_, args) return clone + def complex_filter(self, filter_obj): + """Returns a new QuerySet instance with filter_obj added to the filters. + filter_obj can be a Q object (has 'get_sql' method) or a dictionary of + keyword lookup arguments.""" + # This exists to support framework features such as 'limit_choices_to', + # and usually it will be more natural to use other methods. + if hasattr(filter_obj, 'get_sql'): + return self._filter_or_exclude(None, filter_obj) + else: + return self._filter_or_exclude(Q, **filter_obj) + def select_related(self, true_or_false=True): "Returns a new QuerySet instance with '_select_related' modified." return self._clone(_select_related=true_or_false) diff --git a/docs/model-api.txt b/docs/model-api.txt index a807efc0d6..72c7f5d8b7 100644 --- a/docs/model-api.txt +++ b/docs/model-api.txt @@ -704,6 +704,10 @@ relationship should work. All are optional: ``pub_date`` before the current date/time to be chosen. + Instead of a dictionary this can also be a ``Q`` object + (an object with a ``get_sql()`` method) for more complex + queries. + Not compatible with ``edit_inline``. ``max_num_in_admin`` For inline-edited objects, this is the maximum diff --git a/tests/modeltests/or_lookups/models.py b/tests/modeltests/or_lookups/models.py index 9d62a1266c..cd01447a35 100644 --- a/tests/modeltests/or_lookups/models.py +++ b/tests/modeltests/or_lookups/models.py @@ -86,4 +86,11 @@ Hello and goodbye >>> Article.objects.filter(Q(headline__startswith='Hello')).in_bulk([1,2]) {1: Hello} +# The 'complex_filter' method supports framework features such as +# 'limit_choices_to' which normally take a single dictionary of lookup arguments +# but need to support arbitrary queries via Q objects too. +>>> Article.objects.complex_filter({'pk': 1}) +[Hello] +>>> Article.objects.complex_filter(Q(pk=1) | Q(pk=2)) +[Hello, Goodbye] """