summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMalcolm Tredinnick <malcolm.tredinnick@gmail.com>2009-01-16 10:59:43 +0000
committerMalcolm Tredinnick <malcolm.tredinnick@gmail.com>2009-01-16 10:59:43 +0000
commitd579e716fef9f06f04861815cf949630d8633271 (patch)
tree103cbb5904b5a951be026ecbe2f7ff48a7056004
parent14b3f03015081a679a6b5efaf5ea925344ce14c0 (diff)
Fixed #9997 -- Fixed use of ValuesQuerySets as rvalues in filters.
Previous behaviour was pretty stupid. Let's never speak of it again. New behaviour both works and is documented. git-svn-id: http://code.djangoproject.com/svn/django/trunk@9759 bcc190cf-cafb-0310-a4f2-bffc1f526a37
-rw-r--r--django/db/models/query.py14
-rw-r--r--docs/ref/models/querysets.txt16
-rw-r--r--tests/regressiontests/queries/models.py16
3 files changed, 46 insertions, 0 deletions
diff --git a/django/db/models/query.py b/django/db/models/query.py
index b5c9d2f25d..04a1c513f6 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -798,6 +798,20 @@ class ValuesQuerySet(QuerySet):
super(ValuesQuerySet, self)._setup_aggregate_query()
+ def as_sql(self):
+ """
+ For ValueQuerySet (and subclasses like ValuesListQuerySet), they can
+ only be used as nested queries if they're already set up to select only
+ a single field (in which case, that is the field column that is
+ returned). This differs from QuerySet.as_sql(), where the column to
+ select is set up by Django.
+ """
+ if ((self._fields and len(self._fields) > 1) or
+ (not self._fields and len(self.model._meta.fields) > 1)):
+ raise TypeError('Cannot use a multi-field %s as a filter value.'
+ % self.__class__.__name__)
+ return self._clone().query.as_nested_sql()
+
class ValuesListQuerySet(ValuesQuerySet):
def iterator(self):
self.query.trim_extra_select(self.extra_names)
diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt
index 40a47f4c2c..1e32552570 100644
--- a/docs/ref/models/querysets.txt
+++ b/docs/ref/models/querysets.txt
@@ -1136,6 +1136,7 @@ The above code fragment could also be written as follows::
inner_q = Blog.objects.filter(name__contains='Cheddar').values('pk').query
entries = Entry.objects.filter(blog__in=inner_q)
+
.. versionchanged:: 1.1
In Django 1.0, only the latter piece of code is valid.
@@ -1144,6 +1145,21 @@ accesses the internal ``query`` attribute and requires a ``ValuesQuerySet``.
If your code doesn't require compatibility with Django 1.0, use the first
form, passing in a queryset directly.
+If you pass in a ``ValuesQuerySet`` or ``ValuesListQuerySet`` (the result of
+calling ``values()`` or ``values_list()`` on a queryset) as the value to an
+``__in`` lookup, you need to ensure you are only extracting one field in the
+result. For example, this will work (filtering on the blog names)::
+
+ inner_qs = Blog.objects.filter(name__contains='Ch').values('name')
+ entries = Entry.objects.filter(blog__name__in=inner_qs)
+
+This example will raise an exception, since the inner query is trying to
+extract two field values, where only one is expected::
+
+ # Bad code! Will raise a TypeError.
+ inner_qs = Blog.objects.filter(name__contains='Ch').values('name', 'id')
+ entries = Entry.objects.filter(blog__name__in=inner_qs)
+
.. warning::
This ``query`` attribute should be considered an opaque internal attribute.
diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py
index b32a0c5a63..e8cf645633 100644
--- a/tests/regressiontests/queries/models.py
+++ b/tests/regressiontests/queries/models.py
@@ -1022,6 +1022,22 @@ nothing).
>>> print Annotation.objects.filter(notes__in=Note.objects.filter(note="xyzzy")).query
SELECT ...
+Bug #9997 -- If a ValuesList or Values queryset is passed as an inner query, we
+make sure it's only requesting a single value and use that as the thing to
+select.
+>>> Tag.objects.filter(name__in=Tag.objects.filter(parent=t1).values('name'))
+[<Tag: t2>, <Tag: t3>]
+
+# Multi-valued values() and values_list() querysets should raise errors.
+>>> Tag.objects.filter(name__in=Tag.objects.filter(parent=t1).values('name', 'id'))
+Traceback (most recent call last):
+...
+TypeError: Cannot use a multi-field ValuesQuerySet as a filter value.
+>>> Tag.objects.filter(name__in=Tag.objects.filter(parent=t1).values_list('name', 'id'))
+Traceback (most recent call last):
+...
+TypeError: Cannot use a multi-field ValuesListQuerySet as a filter value.
+
Bug #9985 -- qs.values_list(...).values(...) combinations should work.
>>> Note.objects.values_list("note", flat=True).values("id").order_by("id")
[{'id': 1}, {'id': 2}, {'id': 3}]