Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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
  • Loading branch information...
commit d579e716fef9f06f04861815cf949630d8633271 1 parent 14b3f03
Malcolm Tredinnick authored January 16, 2009
14  django/db/models/query.py
@@ -798,6 +798,20 @@ def _setup_aggregate_query(self):
798 798
 
799 799
         super(ValuesQuerySet, self)._setup_aggregate_query()
800 800
 
  801
+    def as_sql(self):
  802
+        """
  803
+        For ValueQuerySet (and subclasses like ValuesListQuerySet), they can
  804
+        only be used as nested queries if they're already set up to select only
  805
+        a single field (in which case, that is the field column that is
  806
+        returned). This differs from QuerySet.as_sql(), where the column to
  807
+        select is set up by Django.
  808
+        """
  809
+        if ((self._fields and len(self._fields) > 1) or
  810
+                (not self._fields and len(self.model._meta.fields) > 1)):
  811
+            raise TypeError('Cannot use a multi-field %s as a filter value.'
  812
+                    % self.__class__.__name__)
  813
+        return self._clone().query.as_nested_sql()
  814
+
801 815
 class ValuesListQuerySet(ValuesQuerySet):
802 816
     def iterator(self):
803 817
         self.query.trim_extra_select(self.extra_names)
16  docs/ref/models/querysets.txt
@@ -1136,6 +1136,7 @@ The above code fragment could also be written as follows::
1136 1136
     inner_q = Blog.objects.filter(name__contains='Cheddar').values('pk').query
1137 1137
     entries = Entry.objects.filter(blog__in=inner_q)
1138 1138
 
  1139
+
1139 1140
 .. versionchanged:: 1.1
1140 1141
     In Django 1.0, only the latter piece of code is valid.
1141 1142
 
@@ -1144,6 +1145,21 @@ accesses the internal ``query`` attribute and requires a ``ValuesQuerySet``.
1144 1145
 If your code doesn't require compatibility with Django 1.0, use the first
1145 1146
 form, passing in a queryset directly.
1146 1147
 
  1148
+If you pass in a ``ValuesQuerySet`` or ``ValuesListQuerySet`` (the result of
  1149
+calling ``values()`` or ``values_list()`` on a queryset) as the value to an
  1150
+``__in`` lookup, you need to ensure you are only extracting one field in the
  1151
+result. For example, this will work (filtering on the blog names)::
  1152
+
  1153
+    inner_qs = Blog.objects.filter(name__contains='Ch').values('name')
  1154
+    entries = Entry.objects.filter(blog__name__in=inner_qs)
  1155
+
  1156
+This example will raise an exception, since the inner query is trying to
  1157
+extract two field values, where only one is expected::
  1158
+
  1159
+    # Bad code! Will raise a TypeError.
  1160
+    inner_qs = Blog.objects.filter(name__contains='Ch').values('name', 'id')
  1161
+    entries = Entry.objects.filter(blog__name__in=inner_qs)
  1162
+
1147 1163
 .. warning::
1148 1164
 
1149 1165
     This ``query`` attribute should be considered an opaque internal attribute.
16  tests/regressiontests/queries/models.py
@@ -1022,6 +1022,22 @@ class PointerB(models.Model):
1022 1022
 >>> print Annotation.objects.filter(notes__in=Note.objects.filter(note="xyzzy")).query
1023 1023
 SELECT ...
1024 1024
 
  1025
+Bug #9997 -- If a ValuesList or Values queryset is passed as an inner query, we
  1026
+make sure it's only requesting a single value and use that as the thing to
  1027
+select.
  1028
+>>> Tag.objects.filter(name__in=Tag.objects.filter(parent=t1).values('name'))
  1029
+[<Tag: t2>, <Tag: t3>]
  1030
+
  1031
+# Multi-valued values() and values_list() querysets should raise errors.
  1032
+>>> Tag.objects.filter(name__in=Tag.objects.filter(parent=t1).values('name', 'id'))
  1033
+Traceback (most recent call last):
  1034
+...
  1035
+TypeError: Cannot use a multi-field ValuesQuerySet as a filter value.
  1036
+>>> Tag.objects.filter(name__in=Tag.objects.filter(parent=t1).values_list('name', 'id'))
  1037
+Traceback (most recent call last):
  1038
+...
  1039
+TypeError: Cannot use a multi-field ValuesListQuerySet as a filter value.
  1040
+
1025 1041
 Bug #9985 -- qs.values_list(...).values(...) combinations should work.
1026 1042
 >>> Note.objects.values_list("note", flat=True).values("id").order_by("id")
1027 1043
 [{'id': 1}, {'id': 2}, {'id': 3}]

0 notes on commit d579e71

Please sign in to comment.
Something went wrong with that request. Please try again.