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 = () | ||||
|         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 | ||||
|   | ||||
| @@ -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 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') | ||||
|   | ||||
		Reference in New Issue
	
	Block a user