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 = ()
|
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
|
||||||
|
|
|
@ -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 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
|
||||||
|
|
||||||
* 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):
|
||||||
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):
|
||||||
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):
|
||||||
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')
|
||||||
|
|
Loading…
Reference in New Issue