Fixed #28781 -- Added QuerySet.values()/values_list() support for union(), difference(), and intersection().
Thanks Tim Graham for the review.
This commit is contained in:
parent
8f8a4d10d3
commit
2d3cc94284
|
@ -407,6 +407,11 @@ class SQLCompiler:
|
|||
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
|
||||
|
|
|
@ -813,10 +813,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.
|
||||
|
|
|
@ -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`).
|
||||
|
|
|
@ -30,6 +30,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)
|
||||
|
@ -66,6 +76,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()
|
||||
|
@ -89,6 +110,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')
|
||||
|
|
Loading…
Reference in New Issue