summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/db/models/query.py18
-rw-r--r--tests/queries/test_qs_combinators.py27
2 files changed, 43 insertions, 2 deletions
diff --git a/django/db/models/query.py b/django/db/models/query.py
index a62947d9d0..3342ef2086 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -401,7 +401,7 @@ class QuerySet:
Perform the query and return a single object matching the given
keyword arguments.
"""
- clone = self.filter(*args, **kwargs)
+ clone = self._chain() if self.query.combinator else self.filter(*args, **kwargs)
if self.query.can_filter() and not self.query.distinct_fields:
clone = clone.order_by()
limit = None
@@ -890,6 +890,7 @@ class QuerySet:
Return a new QuerySet instance with the args ANDed to the existing
set.
"""
+ self._not_support_combined_queries('filter')
return self._filter_or_exclude(False, *args, **kwargs)
def exclude(self, *args, **kwargs):
@@ -897,6 +898,7 @@ class QuerySet:
Return a new QuerySet instance with NOT (args) ANDed to the existing
set.
"""
+ self._not_support_combined_queries('exclude')
return self._filter_or_exclude(True, *args, **kwargs)
def _filter_or_exclude(self, negate, *args, **kwargs):
@@ -985,7 +987,7 @@ class QuerySet:
If select_related(None) is called, clear the list.
"""
-
+ self._not_support_combined_queries('select_related')
if self._fields is not None:
raise TypeError("Cannot call select_related() after .values() or .values_list()")
@@ -1007,6 +1009,7 @@ class QuerySet:
When prefetch_related() is called more than once, append to the list of
prefetch lookups. If prefetch_related(None) is called, clear the list.
"""
+ self._not_support_combined_queries('prefetch_related')
clone = self._chain()
if lookups == (None,):
clone._prefetch_related_lookups = ()
@@ -1025,6 +1028,7 @@ class QuerySet:
Return a query set in which the returned objects have been annotated
with extra data or aggregations.
"""
+ self._not_support_combined_queries('annotate')
self._validate_values_are_expressions(args + tuple(kwargs.values()), method_name='annotate')
annotations = {}
for arg in args:
@@ -1088,6 +1092,7 @@ class QuerySet:
def extra(self, select=None, where=None, params=None, tables=None,
order_by=None, select_params=None):
"""Add extra SQL fragments to the query."""
+ self._not_support_combined_queries('extra')
assert self.query.can_filter(), \
"Cannot change a query once a slice has been taken"
clone = self._chain()
@@ -1109,6 +1114,7 @@ class QuerySet:
The only exception to this is if None is passed in as the only
parameter, in which case removal all deferrals.
"""
+ self._not_support_combined_queries('defer')
if self._fields is not None:
raise TypeError("Cannot call defer() after .values() or .values_list()")
clone = self._chain()
@@ -1124,6 +1130,7 @@ class QuerySet:
method and that are not already specified as deferred are loaded
immediately when the queryset is evaluated.
"""
+ self._not_support_combined_queries('only')
if self._fields is not None:
raise TypeError("Cannot call only() after .values() or .values_list()")
if fields == (None,):
@@ -1312,6 +1319,13 @@ class QuerySet:
)
)
+ def _not_support_combined_queries(self, operation_name):
+ if self.query.combinator:
+ raise NotSupportedError(
+ 'Calling QuerySet.%s() after %s() is not supported.'
+ % (operation_name, self.query.combinator)
+ )
+
class InstanceCheckMeta(type):
def __instancecheck__(self, instance):
diff --git a/tests/queries/test_qs_combinators.py b/tests/queries/test_qs_combinators.py
index e7cfbfb0d1..668d5e6ad6 100644
--- a/tests/queries/test_qs_combinators.py
+++ b/tests/queries/test_qs_combinators.py
@@ -1,3 +1,4 @@
+from django.db import connection
from django.db.models import Exists, F, IntegerField, OuterRef, Value
from django.db.utils import DatabaseError, NotSupportedError
from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
@@ -258,3 +259,29 @@ class QuerySetSetOperationTests(TestCase):
numbers = list(range(10))
self.assertNumbersEqual(union.order_by('num'), numbers)
self.assertNumbersEqual(union.order_by('other_num'), reversed(numbers))
+
+ def test_unsupported_operations_on_combined_qs(self):
+ qs = Number.objects.all()
+ msg = 'Calling QuerySet.%s() after %s() is not supported.'
+ combinators = ['union']
+ if connection.features.supports_select_difference:
+ combinators.append('difference')
+ if connection.features.supports_select_intersection:
+ combinators.append('intersection')
+ for combinator in combinators:
+ for operation in (
+ 'annotate',
+ 'defer',
+ 'exclude',
+ 'extra',
+ 'filter',
+ 'only',
+ 'prefetch_related',
+ 'select_related',
+ ):
+ with self.subTest(combinator=combinator, operation=operation):
+ with self.assertRaisesMessage(
+ NotSupportedError,
+ msg % (operation, combinator),
+ ):
+ getattr(getattr(qs, combinator)(qs), operation)()