Fixed #23659 -- Kept annotate() args ordering

Thanks Loic Bistuer and Simon Charette for the review.
This commit is contained in:
Claude Paroz 2014-10-15 15:52:18 +02:00
parent 947af46db3
commit 374c14b7fd
2 changed files with 18 additions and 5 deletions

View File

@ -2,7 +2,7 @@
The main QuerySet implementation. This provides the public API for the ORM. The main QuerySet implementation. This provides the public API for the ORM.
""" """
from collections import deque from collections import deque, OrderedDict
import copy import copy
import sys import sys
import warnings import warnings
@ -786,27 +786,29 @@ class QuerySet(object):
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 data aggregated from related fields. with data aggregated from related fields.
""" """
aggrs = OrderedDict() # To preserve ordering of args
for arg in args: for arg in args:
if arg.default_alias in kwargs: if arg.default_alias in kwargs:
raise ValueError("The named annotation '%s' conflicts with the " raise ValueError("The named annotation '%s' conflicts with the "
"default name for another annotation." "default name for another annotation."
% arg.default_alias) % arg.default_alias)
kwargs[arg.default_alias] = arg aggrs[arg.default_alias] = arg
aggrs.update(kwargs)
names = getattr(self, '_fields', None) names = getattr(self, '_fields', None)
if names is None: if names is None:
names = set(self.model._meta.get_all_field_names()) names = set(self.model._meta.get_all_field_names())
for aggregate in kwargs: for aggregate in aggrs:
if aggregate in names: if aggregate in names:
raise ValueError("The annotation '%s' conflicts with a field on " raise ValueError("The annotation '%s' conflicts with a field on "
"the model." % aggregate) "the model." % aggregate)
obj = self._clone() obj = self._clone()
obj._setup_aggregate_query(list(kwargs)) obj._setup_aggregate_query(list(aggrs))
# Add the aggregates to the query # Add the aggregates to the query
for (alias, aggregate_expr) in kwargs.items(): for (alias, aggregate_expr) in aggrs.items():
obj.query.add_aggregate(aggregate_expr, self.model, alias, obj.query.add_aggregate(aggregate_expr, self.model, alias,
is_summary=False) is_summary=False)

View File

@ -740,6 +740,17 @@ class AggregationTests(TestCase):
list(qs), list(Book.objects.values_list("name", flat=True)) list(qs), list(Book.objects.values_list("name", flat=True))
) )
def test_values_list_annotation_args_ordering(self):
"""
Annotate *args ordering should be preserved in values_list results.
**kwargs comes after *args.
Regression test for #23659.
"""
books = Book.objects.values_list("publisher__name").annotate(
Count("id"), Avg("price"), Avg("authors__age"), avg_pgs=Avg("pages")
).order_by("-publisher__name")
self.assertEqual(books[0], ('Sams', 1, 23.09, 45.0, 528.0))
def test_annotation_disjunction(self): def test_annotation_disjunction(self):
qs = Book.objects.annotate(n_authors=Count("authors")).filter( qs = Book.objects.annotate(n_authors=Count("authors")).filter(
Q(n_authors=2) | Q(name="Python Web Development with Django") Q(n_authors=2) | Q(name="Python Web Development with Django")