Commits

Anonymous committed 682cfa9

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.

Comments (0)

Files changed (3)

django/db/models/query.py

 
         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)

docs/ref/models/querysets.txt

     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.
 
 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.

tests/regressiontests/queries/models.py

 >>> 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}]