summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMariusz Felisiak <felisiak.mariusz@gmail.com>2017-11-12 14:28:11 +0100
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2017-11-12 14:33:41 +0100
commit67316720603821ebb64dfe8fa592ba6edcef5f3e (patch)
tree02ec6368544391e560a0ffda127ac24ac8eaa7ec
parent308f64462421b09b21ef0dcd9cc3654cc25bceba (diff)
[1.11.x] Fixed #28781 -- Added QuerySet.values()/values_list() support for union(), difference(), and intersection().
Thanks Tim Graham for the review. Backport of 2d3cc94284674638c334670903d49565039d77ae from master
-rw-r--r--django/db/models/sql/compiler.py5
-rw-r--r--docs/ref/models/querysets.txt13
-rw-r--r--docs/releases/1.11.8.txt4
-rw-r--r--tests/queries/test_qs_combinators.py30
4 files changed, 49 insertions, 3 deletions
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
index 6ec2284a91..9888816c8d 100644
--- a/django/db/models/sql/compiler.py
+++ b/django/db/models/sql/compiler.py
@@ -390,6 +390,11 @@ class SQLCompiler(object):
parts = ()
for compiler in compilers:
try:
+ # If the columns list is limited, then all combined queries
+ # must have the same columns list. Set the selects defined on
+ # the query on all combined queries, if not already set.
+ if not compiler.query.values_select and self.query.values_select:
+ compiler.query.set_values(self.query.values_select)
parts += (compiler.as_sql(),)
except EmptyResultSet:
# Omit the empty queryset with UNION and with DIFFERENCE if the
diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt
index 179696d764..c1c2a249f8 100644
--- a/docs/ref/models/querysets.txt
+++ b/docs/ref/models/querysets.txt
@@ -811,10 +811,17 @@ duplicate values, use the ``all=True`` argument.
of the type of the first ``QuerySet`` even if the arguments are ``QuerySet``\s
of other models. Passing different models works as long as the ``SELECT`` list
is the same in all ``QuerySet``\s (at least the types, the names don't matter
-as long as the types in the same order).
+as long as the types in the same order). In such cases, you must use the column
+names from the first ``QuerySet`` in ``QuerySet`` methods applied to the
+resulting ``QuerySet``. For example::
-In addition, only ``LIMIT``, ``OFFSET``, ``COUNT(*)``, and ``ORDER BY`` (i.e.
-slicing, :meth:`count`, and :meth:`order_by`) are allowed on the resulting
+ >>> qs1 = Author.objects.values_list('name')
+ >>> qs2 = Entry.objects.values_list('headline')
+ >>> qs1.union(qs2).order_by('name')
+
+In addition, only ``LIMIT``, ``OFFSET``, ``COUNT(*)``, ``ORDER BY``, and
+specifying columns (i.e. slicing, :meth:`count`, :meth:`order_by`, and
+:meth:`values()`/:meth:`values_list()`) are allowed on the resulting
``QuerySet``. Further, databases place restrictions on what operations are
allowed in the combined queries. For example, most databases don't allow
``LIMIT`` or ``OFFSET`` in the combined queries.
diff --git a/docs/releases/1.11.8.txt b/docs/releases/1.11.8.txt
index dd9fb0c3ce..7e4963f713 100644
--- a/docs/releases/1.11.8.txt
+++ b/docs/releases/1.11.8.txt
@@ -11,3 +11,7 @@ Bugfixes
* Reallowed, following a regression in Django 1.10, ``AuthenticationForm`` to
raise the inactive user error when using ``ModelBackend`` (:ticket:`28645`).
+
+* Added support for ``QuerySet.values()`` and ``values_list()`` for
+ ``union()``, ``difference()``, and ``intersection()`` queries
+ (:ticket:`28781`).
diff --git a/tests/queries/test_qs_combinators.py b/tests/queries/test_qs_combinators.py
index ef8c188c05..3450014d94 100644
--- a/tests/queries/test_qs_combinators.py
+++ b/tests/queries/test_qs_combinators.py
@@ -33,6 +33,16 @@ class QuerySetSetOperationTests(TestCase):
qs3 = Number.objects.filter(num__gte=4, num__lte=6)
self.assertNumbersEqual(qs1.intersection(qs2, qs3), [5], ordered=False)
+ @skipUnlessDBFeature('supports_select_intersection')
+ def test_intersection_with_values(self):
+ ReservedName.objects.create(name='a', order=2)
+ qs1 = ReservedName.objects.all()
+ reserved_name = qs1.intersection(qs1).values('name', 'order', 'id').get()
+ self.assertEqual(reserved_name['name'], 'a')
+ self.assertEqual(reserved_name['order'], 2)
+ reserved_name = qs1.intersection(qs1).values_list('name', 'order', 'id').get()
+ self.assertEqual(reserved_name[:2], ('a', 2))
+
@skipUnlessDBFeature('supports_select_difference')
def test_simple_difference(self):
qs1 = Number.objects.filter(num__lte=5)
@@ -69,6 +79,17 @@ class QuerySetSetOperationTests(TestCase):
self.assertEqual(len(qs2.difference(qs2)), 0)
self.assertEqual(len(qs3.difference(qs3)), 0)
+ @skipUnlessDBFeature('supports_select_difference')
+ def test_difference_with_values(self):
+ ReservedName.objects.create(name='a', order=2)
+ qs1 = ReservedName.objects.all()
+ qs2 = ReservedName.objects.none()
+ reserved_name = qs1.difference(qs2).values('name', 'order', 'id').get()
+ self.assertEqual(reserved_name['name'], 'a')
+ self.assertEqual(reserved_name['order'], 2)
+ reserved_name = qs1.difference(qs2).values_list('name', 'order', 'id').get()
+ self.assertEqual(reserved_name[:2], ('a', 2))
+
def test_union_with_empty_qs(self):
qs1 = Number.objects.all()
qs2 = Number.objects.none()
@@ -98,6 +119,15 @@ class QuerySetSetOperationTests(TestCase):
qs2 = Number.objects.filter(num__gte=2, num__lte=3)
self.assertNumbersEqual(qs1.union(qs2).order_by('-num'), [3, 2, 1, 0])
+ def test_union_with_values(self):
+ ReservedName.objects.create(name='a', order=2)
+ qs1 = ReservedName.objects.all()
+ reserved_name = qs1.union(qs1).values('name', 'order', 'id').get()
+ self.assertEqual(reserved_name['name'], 'a')
+ self.assertEqual(reserved_name['order'], 2)
+ reserved_name = qs1.union(qs1).values_list('name', 'order', 'id').get()
+ self.assertEqual(reserved_name[:2], ('a', 2))
+
def test_count_union(self):
qs1 = Number.objects.filter(num__lte=1).values('num')
qs2 = Number.objects.filter(num__gte=2, num__lte=3).values('num')