1
0
mirror of https://github.com/django/django.git synced 2024-12-22 17:16:24 +00:00

Fixed #28277 -- Added validation of QuerySet.annotate() and aggregate() args.

Thanks Tim Graham and Nick Pope for reviews.
This commit is contained in:
Mariusz Felisiak 2017-06-29 18:25:36 +02:00 committed by GitHub
parent f7f5edd50d
commit 6e228d0b65
3 changed files with 31 additions and 0 deletions

View File

@ -323,6 +323,7 @@ class QuerySet:
""" """
if self.query.distinct_fields: if self.query.distinct_fields:
raise NotImplementedError("aggregate() + distinct(fields) not implemented.") raise NotImplementedError("aggregate() + distinct(fields) not implemented.")
self._validate_values_are_expressions(args + tuple(kwargs.values()), method_name='aggregate')
for arg in args: for arg in args:
# The default_alias property raises TypeError if default_alias # The default_alias property raises TypeError if default_alias
# can't be set automatically or AttributeError if it isn't an # can't be set automatically or AttributeError if it isn't an
@ -895,6 +896,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._validate_values_are_expressions(args + tuple(kwargs.values()), method_name='annotate')
annotations = OrderedDict() # To preserve ordering of args annotations = OrderedDict() # To preserve ordering of args
for arg in args: for arg in args:
# The default_alias property may raise a TypeError. # The default_alias property may raise a TypeError.
@ -1145,6 +1147,17 @@ class QuerySet:
""" """
return self.query.has_filters() return self.query.has_filters()
@staticmethod
def _validate_values_are_expressions(values, method_name):
invalid_args = sorted(str(arg) for arg in values if not hasattr(arg, 'resolve_expression'))
if invalid_args:
raise TypeError(
'QuerySet.%s() received non-expression(s): %s.' % (
method_name,
', '.join(invalid_args),
)
)
class InstanceCheckMeta(type): class InstanceCheckMeta(type):
def __instancecheck__(self, instance): def __instancecheck__(self, instance):

View File

@ -1186,3 +1186,12 @@ class AggregateTestCase(TestCase):
).filter(rating_or_num_awards__gt=F('num_awards')).order_by('num_awards') ).filter(rating_or_num_awards__gt=F('num_awards')).order_by('num_awards')
self.assertQuerysetEqual( self.assertQuerysetEqual(
qs2, [1, 3], lambda v: v.num_awards) qs2, [1, 3], lambda v: v.num_awards)
def test_arguments_must_be_expressions(self):
msg = 'QuerySet.aggregate() received non-expression(s): %s.'
with self.assertRaisesMessage(TypeError, msg % FloatField()):
Book.objects.aggregate(FloatField())
with self.assertRaisesMessage(TypeError, msg % True):
Book.objects.aggregate(is_book=True)
with self.assertRaisesMessage(TypeError, msg % ', '.join([str(FloatField()), 'True'])):
Book.objects.aggregate(FloatField(), Avg('price'), is_book=True)

View File

@ -508,3 +508,12 @@ class NonAggregateAnnotationTestCase(TestCase):
self.assertIs(book.is_book, True) self.assertIs(book.is_book, True)
self.assertIs(book.is_pony, False) self.assertIs(book.is_pony, False)
self.assertIsNone(book.is_none) self.assertIsNone(book.is_none)
def test_arguments_must_be_expressions(self):
msg = 'QuerySet.annotate() received non-expression(s): %s.'
with self.assertRaisesMessage(TypeError, msg % BooleanField()):
Book.objects.annotate(BooleanField())
with self.assertRaisesMessage(TypeError, msg % True):
Book.objects.annotate(is_book=True)
with self.assertRaisesMessage(TypeError, msg % ', '.join([str(BooleanField()), 'True'])):
Book.objects.annotate(BooleanField(), Value(False), is_book=True)