Skip to content

Commit

Permalink
Fixed #9997 -- Fixed use of ValuesQuerySets as rvalues in filters.
Browse files Browse the repository at this point in the history
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
malcolmt committed Jan 16, 2009
1 parent 14b3f03 commit d579e71
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 0 deletions.
14 changes: 14 additions & 0 deletions django/db/models/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,20 @@ def _setup_aggregate_query(self):

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)
Expand Down
16 changes: 16 additions & 0 deletions docs/ref/models/querysets.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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.
Expand Down
16 changes: 16 additions & 0 deletions tests/regressiontests/queries/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,22 @@ class PointerB(models.Model):
>>> 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}]
Expand Down

0 comments on commit d579e71

Please sign in to comment.