Unverified Kaydet (Commit) ca0a9c93 authored tarafından Mariusz Felisiak's avatar Mariusz Felisiak

[2.0.x] Fixed #28781 -- Added QuerySet.values()/values_list() support for…

[2.0.x] Fixed #28781 -- Added QuerySet.values()/values_list() support for union(), difference(), and intersection().

Thanks Tim Graham for the review.
Backport of 2d3cc942 from master
üst 3c8c3ff6
...@@ -407,6 +407,11 @@ class SQLCompiler: ...@@ -407,6 +407,11 @@ class SQLCompiler:
parts = () parts = ()
for compiler in compilers: for compiler in compilers:
try: 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(),) parts += (compiler.as_sql(),)
except EmptyResultSet: except EmptyResultSet:
# Omit the empty queryset with UNION and with DIFFERENCE if the # Omit the empty queryset with UNION and with DIFFERENCE if the
......
...@@ -823,10 +823,17 @@ duplicate values, use the ``all=True`` argument. ...@@ -823,10 +823,17 @@ duplicate values, use the ``all=True`` argument.
of the type of the first ``QuerySet`` even if the arguments are ``QuerySet``\s 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 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 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. >>> qs1 = Author.objects.values_list('name')
slicing, :meth:`count`, and :meth:`order_by`) are allowed on the resulting >>> 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 ``QuerySet``. Further, databases place restrictions on what operations are
allowed in the combined queries. For example, most databases don't allow allowed in the combined queries. For example, most databases don't allow
``LIMIT`` or ``OFFSET`` in the combined queries. ``LIMIT`` or ``OFFSET`` in the combined queries.
......
...@@ -11,3 +11,7 @@ Bugfixes ...@@ -11,3 +11,7 @@ Bugfixes
* Reallowed, following a regression in Django 1.10, ``AuthenticationForm`` to * Reallowed, following a regression in Django 1.10, ``AuthenticationForm`` to
raise the inactive user error when using ``ModelBackend`` (:ticket:`28645`). 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`).
...@@ -30,6 +30,16 @@ class QuerySetSetOperationTests(TestCase): ...@@ -30,6 +30,16 @@ class QuerySetSetOperationTests(TestCase):
qs3 = Number.objects.filter(num__gte=4, num__lte=6) qs3 = Number.objects.filter(num__gte=4, num__lte=6)
self.assertNumbersEqual(qs1.intersection(qs2, qs3), [5], ordered=False) 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') @skipUnlessDBFeature('supports_select_difference')
def test_simple_difference(self): def test_simple_difference(self):
qs1 = Number.objects.filter(num__lte=5) qs1 = Number.objects.filter(num__lte=5)
...@@ -66,6 +76,17 @@ class QuerySetSetOperationTests(TestCase): ...@@ -66,6 +76,17 @@ class QuerySetSetOperationTests(TestCase):
self.assertEqual(len(qs2.difference(qs2)), 0) self.assertEqual(len(qs2.difference(qs2)), 0)
self.assertEqual(len(qs3.difference(qs3)), 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): def test_union_with_empty_qs(self):
qs1 = Number.objects.all() qs1 = Number.objects.all()
qs2 = Number.objects.none() qs2 = Number.objects.none()
...@@ -89,6 +110,15 @@ class QuerySetSetOperationTests(TestCase): ...@@ -89,6 +110,15 @@ class QuerySetSetOperationTests(TestCase):
qs2 = Number.objects.filter(num__gte=2, num__lte=3) qs2 = Number.objects.filter(num__gte=2, num__lte=3)
self.assertNumbersEqual(qs1.union(qs2).order_by('-num'), [3, 2, 1, 0]) 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): def test_count_union(self):
qs1 = Number.objects.filter(num__lte=1).values('num') qs1 = Number.objects.filter(num__lte=1).values('num')
qs2 = Number.objects.filter(num__gte=2, num__lte=3).values('num') qs2 = Number.objects.filter(num__gte=2, num__lte=3).values('num')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment