From d579e716fef9f06f04861815cf949630d8633271 Mon Sep 17 00:00:00 2001 From: Malcolm Tredinnick Date: Fri, 16 Jan 2009 10:59:43 +0000 Subject: [PATCH] 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 --- django/db/models/query.py | 14 ++++++++++++++ docs/ref/models/querysets.txt | 16 ++++++++++++++++ tests/regressiontests/queries/models.py | 16 ++++++++++++++++ 3 files changed, 46 insertions(+) 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')) +[, ] + +# 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}]