From 78ad4b4b0201003792bfdbf1a7781cbc9ee03539 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 8 Jun 2020 07:21:54 +0200 Subject: [PATCH] Fixed #31660 -- Fixed queryset crash when grouping by m2o relation. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Regression in 3a941230c85b2702a5e1cd97e17251ce21057efa. Thanks Tomasz SzymaƄski for the report. --- django/db/models/expressions.py | 4 +++- docs/releases/3.0.8.txt | 3 +++ tests/annotations/tests.py | 27 ++++++++++++++++++++++++--- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index 6bd1471692..76f8fded41 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -383,7 +383,9 @@ class BaseExpression: Custom format for select clauses. For example, EXISTS expressions need to be wrapped in CASE WHEN on Oracle. """ - return self.output_field.select_format(compiler, sql, params) + if hasattr(self.output_field, 'select_format'): + return self.output_field.select_format(compiler, sql, params) + return sql, params @cached_property def identity(self): diff --git a/docs/releases/3.0.8.txt b/docs/releases/3.0.8.txt index e355f0a0ff..d21eac37c8 100644 --- a/docs/releases/3.0.8.txt +++ b/docs/releases/3.0.8.txt @@ -11,3 +11,6 @@ Bugfixes * Fixed messages of ``InvalidCacheKey`` exceptions and ``CacheKeyWarning`` warnings raised by cache key validation (:ticket:`31654`). + +* Fixed a regression in Django 3.0.7 that caused a queryset crash when grouping + by a many-to-one relationship (:ticket:`31660`). diff --git a/tests/annotations/tests.py b/tests/annotations/tests.py index 142c23ead6..c1ac0516ac 100644 --- a/tests/annotations/tests.py +++ b/tests/annotations/tests.py @@ -1,11 +1,13 @@ import datetime from decimal import Decimal +from unittest import skipIf from django.core.exceptions import FieldDoesNotExist, FieldError +from django.db import connection from django.db.models import ( - BooleanField, CharField, Count, DateTimeField, Exists, ExpressionWrapper, - F, Func, IntegerField, Max, NullBooleanField, OuterRef, Q, Subquery, Sum, - Value, + BooleanField, Case, CharField, Count, DateTimeField, Exists, + ExpressionWrapper, F, Func, IntegerField, Max, NullBooleanField, OuterRef, + Q, Subquery, Sum, Value, When, ) from django.db.models.expressions import RawSQL from django.db.models.functions import Length, Lower @@ -632,3 +634,22 @@ class NonAggregateAnnotationTestCase(TestCase): datetime.date(2008, 6, 23), datetime.date(2008, 11, 3), ]) + + @skipIf( + connection.vendor == 'mysql' and 'ONLY_FULL_GROUP_BY' in connection.sql_mode, + 'GROUP BY optimization does not work properly when ONLY_FULL_GROUP_BY ' + 'mode is enabled on MySQL, see #31331.', + ) + def test_annotation_aggregate_with_m2o(self): + qs = Author.objects.filter(age__lt=30).annotate( + max_pages=Case( + When(book_contact_set__isnull=True, then=Value(0)), + default=Max(F('book__pages')), + output_field=IntegerField(), + ), + ).values('name', 'max_pages') + self.assertCountEqual(qs, [ + {'name': 'James Bennett', 'max_pages': 300}, + {'name': 'Paul Bissex', 'max_pages': 0}, + {'name': 'Wesley J. Chun', 'max_pages': 0}, + ])