From d7ae0bc372f8423e7bcf9b5408df46fd5c8dc27d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?= Date: Mon, 23 Sep 2013 18:18:20 +0300 Subject: [PATCH] [1.6.x] Fixed #21126 -- QuerySet value conversion failure A .annotate().select_related() query resulted in misaligned rows vs columns for compiler.resolve_columns() method. Report & patch by Michael Manfre. Backpatch of 83554b018ef283827c0e7459ab934d447b3419d5 from master. --- django/db/backends/__init__.py | 2 +- django/db/models/sql/compiler.py | 12 +++++++++--- tests/aggregation_regress/tests.py | 11 +++++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index 3184c11f48..f06682f630 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -1125,7 +1125,7 @@ class BaseDatabaseOperations(object): Coerce the value returned by the database backend into a consistent type that is compatible with the field type. """ - if value is None: + if value is None or field is None: return value internal_type = field.get_internal_type() if internal_type == 'FloatField': diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index e768cedf88..8ec624b2e6 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -709,6 +709,10 @@ class SQLCompiler(object): has_aggregate_select = bool(self.query.aggregate_select) for rows in self.execute_sql(MULTI): for row in rows: + if has_aggregate_select: + loaded_fields = self.query.get_loaded_field_names().get(self.query.model, set()) or self.query.select + aggregate_start = len(self.query.extra_select) + len(loaded_fields) + aggregate_end = aggregate_start + len(self.query.aggregate_select) if resolve_columns: if fields is None: # We only set this up here because @@ -735,12 +739,14 @@ class SQLCompiler(object): db_table = self.query.get_meta().db_table fields = [f for f in fields if db_table in only_load and f.column in only_load[db_table]] + if has_aggregate_select: + # pad None in to fields for aggregates + fields = fields[:aggregate_start] + [ + None for x in range(0, aggregate_end - aggregate_start) + ] + fields[aggregate_start:] row = self.resolve_columns(row, fields) if has_aggregate_select: - loaded_fields = self.query.get_loaded_field_names().get(self.query.model, set()) or self.query.select - aggregate_start = len(self.query.extra_select) + len(loaded_fields) - aggregate_end = aggregate_start + len(self.query.aggregate_select) row = tuple(row[:aggregate_start]) + tuple([ self.query.resolve_aggregate(value, aggregate, self.connection) for (alias, aggregate), value diff --git a/tests/aggregation_regress/tests.py b/tests/aggregation_regress/tests.py index 1f1561351f..f0d1e59f3c 100644 --- a/tests/aggregation_regress/tests.py +++ b/tests/aggregation_regress/tests.py @@ -387,6 +387,17 @@ class AggregationTests(TestCase): qs = Entries.objects.annotate(clue_count=Count('clues__ID')) self.assertQuerysetEqual(qs, []) + def test_boolean_conversion(self): + # Aggregates mixed up ordering of columns for backend's convert_values + # method. Refs #21126. + e = Entries.objects.create(Entry='foo') + c = Clues.objects.create(EntryID=e, Clue='bar') + qs = Clues.objects.select_related('EntryID').annotate(Count('ID')) + self.assertQuerysetEqual( + qs, [c], lambda x: x) + self.assertEqual(qs[0].EntryID, e) + self.assertIs(qs[0].EntryID.Exclude, False) + def test_empty(self): # Regression for #10089: Check handling of empty result sets with # aggregates