From 0e16c3e3cd61b119e067b369f3ff928a2e4045b4 Mon Sep 17 00:00:00 2001 From: Gabe Jackson Date: Tue, 2 Sep 2014 15:30:53 +0200 Subject: [PATCH] Fixed #23396 -- Ensured ValueQuerySets are not checked by check_related_objects. --- django/db/models/query.py | 16 ++++++++++++++++ django/db/models/sql/query.py | 26 +++++++++++--------------- tests/queries/tests.py | 8 ++++++++ 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/django/db/models/query.py b/django/db/models/query.py index 85f1981662..d35f54b0b5 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -1052,6 +1052,15 @@ class QuerySet(object): """ return self.query.has_filters() + def is_compatible_query_object_type(self, opts): + model = self.model + return ( + model == opts.concrete_model or + opts.concrete_model in model._meta.get_parent_list() or + model in opts.get_parent_list() + ) + is_compatible_query_object_type.queryset_only = True + class InstanceCheckMeta(type): def __instancecheck__(self, instance): @@ -1209,6 +1218,13 @@ class ValuesQuerySet(QuerySet): % self.__class__.__name__) return self + def is_compatible_query_object_type(self, opts): + """ + ValueQuerySets do not need to be checked for compatibility. + We trust that users of ValueQuerySets know what they are doing. + """ + return True + class ValuesListQuerySet(ValuesQuerySet): def iterator(self): diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 2e7b37d423..08af1fb008 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -1094,21 +1094,17 @@ class Query(object): Checks the type of object passed to query relations. """ if field.rel: - # testing for iterable of models - if hasattr(value, '__iter__'): - # Check if the iterable has a model attribute, if so - # it is likely something like a QuerySet. - if hasattr(value, 'model') and hasattr(value.model, '_meta'): - model = value.model - if not (model == opts.concrete_model - or opts.concrete_model in model._meta.get_parent_list() - or model in opts.get_parent_list()): - raise ValueError( - 'Cannot use QuerySet for "%s": Use a QuerySet for "%s".' % - (model._meta.model_name, opts.object_name)) - else: - for v in value: - self.check_query_object_type(v, opts) + # QuerySets implement is_compatible_query_object_type() to + # determine compatibility with the given field. + if hasattr(value, 'is_compatible_query_object_type'): + if not value.is_compatible_query_object_type(opts): + raise ValueError( + 'Cannot use QuerySet for "%s": Use a QuerySet for "%s".' % + (value.model._meta.model_name, opts.object_name) + ) + elif hasattr(value, '__iter__'): + for v in value: + self.check_query_object_type(v, opts) else: # expecting single model instance here self.check_query_object_type(value, opts) diff --git a/tests/queries/tests.py b/tests/queries/tests.py index d6f7aa38a0..8ec3fd5945 100644 --- a/tests/queries/tests.py +++ b/tests/queries/tests.py @@ -3487,6 +3487,14 @@ class RelatedLookupTypeTests(TestCase): with self.assertNumQueries(0): ObjectB.objects.filter(objecta__in=ObjectA.objects.all()) + def test_values_queryset_lookup(self): + """ + #23396 - Ensure ValueQuerySets are not checked for compatibility with the lookup field + """ + self.assertQuerysetEqual(ObjectB.objects.filter( + objecta__in=ObjectB.objects.all().values_list('pk') + ).order_by('pk'), ['', '']) + class Ticket14056Tests(TestCase): def test_ticket_14056(self):