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 Perform the query and return a single object matching the given
keyword arguments. 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: if self.query.can_filter() and not self.query.distinct_fields:
clone = clone.order_by() clone = clone.order_by()
limit = None limit = None
@ -890,6 +890,7 @@ class QuerySet:
Return a new QuerySet instance with the args ANDed to the existing Return a new QuerySet instance with the args ANDed to the existing
set. set.
""" """
self._not_support_combined_queries('filter')
return self._filter_or_exclude(False, *args, **kwargs) return self._filter_or_exclude(False, *args, **kwargs)
def exclude(self, *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 Return a new QuerySet instance with NOT (args) ANDed to the existing
set. set.
""" """
self._not_support_combined_queries('exclude')
return self._filter_or_exclude(True, *args, **kwargs) return self._filter_or_exclude(True, *args, **kwargs)
def _filter_or_exclude(self, negate, *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. If select_related(None) is called, clear the list.
""" """
self._not_support_combined_queries('select_related')
if self._fields is not None: if self._fields is not None:
raise TypeError("Cannot call select_related() after .values() or .values_list()") 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 When prefetch_related() is called more than once, append to the list of
prefetch lookups. If prefetch_related(None) is called, clear the list. prefetch lookups. If prefetch_related(None) is called, clear the list.
""" """
self._not_support_combined_queries('prefetch_related')
clone = self._chain() clone = self._chain()
if lookups == (None,): if lookups == (None,):
clone._prefetch_related_lookups = () clone._prefetch_related_lookups = ()
@ -1025,6 +1028,7 @@ class QuerySet:
Return a query set in which the returned objects have been annotated Return a query set in which the returned objects have been annotated
with extra data or aggregations. with extra data or aggregations.
""" """
self._not_support_combined_queries('annotate')
self._validate_values_are_expressions(args + tuple(kwargs.values()), method_name='annotate') self._validate_values_are_expressions(args + tuple(kwargs.values()), method_name='annotate')
annotations = {} annotations = {}
for arg in args: for arg in args:
@ -1088,6 +1092,7 @@ class QuerySet:
def extra(self, select=None, where=None, params=None, tables=None, def extra(self, select=None, where=None, params=None, tables=None,
order_by=None, select_params=None): order_by=None, select_params=None):
"""Add extra SQL fragments to the query.""" """Add extra SQL fragments to the query."""
self._not_support_combined_queries('extra')
assert self.query.can_filter(), \ assert self.query.can_filter(), \
"Cannot change a query once a slice has been taken" "Cannot change a query once a slice has been taken"
clone = self._chain() clone = self._chain()
@ -1109,6 +1114,7 @@ class QuerySet:
The only exception to this is if None is passed in as the only The only exception to this is if None is passed in as the only
parameter, in which case removal all deferrals. parameter, in which case removal all deferrals.
""" """
self._not_support_combined_queries('defer')
if self._fields is not None: if self._fields is not None:
raise TypeError("Cannot call defer() after .values() or .values_list()") raise TypeError("Cannot call defer() after .values() or .values_list()")
clone = self._chain() clone = self._chain()
@ -1124,6 +1130,7 @@ class QuerySet:
method and that are not already specified as deferred are loaded method and that are not already specified as deferred are loaded
immediately when the queryset is evaluated. immediately when the queryset is evaluated.
""" """
self._not_support_combined_queries('only')
if self._fields is not None: if self._fields is not None:
raise TypeError("Cannot call only() after .values() or .values_list()") raise TypeError("Cannot call only() after .values() or .values_list()")
if fields == (None,): 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): class InstanceCheckMeta(type):
def __instancecheck__(self, instance): 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.models import Exists, F, IntegerField, OuterRef, Value
from django.db.utils import DatabaseError, NotSupportedError from django.db.utils import DatabaseError, NotSupportedError
from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
@ -258,3 +259,29 @@ class QuerySetSetOperationTests(TestCase):
numbers = list(range(10)) numbers = list(range(10))
self.assertNumbersEqual(union.order_by('num'), numbers) self.assertNumbersEqual(union.order_by('num'), numbers)
self.assertNumbersEqual(union.order_by('other_num'), reversed(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)()