Fixed #27995 -- Added error messages on unsupported operations following union(), intersection(), and difference().

This commit is contained in:
Hasan Ramezani 2019-07-24 17:38:28 +02:00 committed by Mariusz Felisiak
parent f13147c8de
commit 1853383969
2 changed files with 43 additions and 2 deletions

View File

@ -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):

View File

@ -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)()