mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	[2.0.x] Fixed #28781 -- Added QuerySet.values()/values_list() support for union(), difference(), and intersection().
Thanks Tim Graham for the review.
Backport of 2d3cc94284 from master
			
			
This commit is contained in:
		| @@ -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. | |||||||
| 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') | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user