From 28c95a35fbf1a64bb342f622a90fbaaf46315819 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Mon, 13 Oct 2025 17:22:17 -0400 Subject: [PATCH] [6.0.x] Fixed #36648, Refs #33772 -- Accounted for composite pks in first()/last() when aggregating. Backport of 02eed4f37879b2077496f86bb1378a076b981233 from main. --- django/db/models/query.py | 10 ++++++++-- docs/releases/5.2.8.txt | 4 ++++ tests/composite_pk/test_aggregate.py | 20 ++++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/django/db/models/query.py b/django/db/models/query.py index 721bf33e57..7ff28b74e0 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -2077,8 +2077,14 @@ class QuerySet(AltersData): raise TypeError(f"Cannot use {operator_} operator with combined queryset.") def _check_ordering_first_last_queryset_aggregation(self, method): - if isinstance(self.query.group_by, tuple) and not any( - col.output_field is self.model._meta.pk for col in self.query.group_by + if ( + isinstance(self.query.group_by, tuple) + # Raise if the pk fields are not in the group_by. + and self.model._meta.pk + not in {col.output_field for col in self.query.group_by} + and set(self.model._meta.pk_fields).difference( + {col.target for col in self.query.group_by} + ) ): raise TypeError( f"Cannot use QuerySet.{method}() on an unordered queryset performing " diff --git a/docs/releases/5.2.8.txt b/docs/releases/5.2.8.txt index ef18d08022..dc750e4636 100644 --- a/docs/releases/5.2.8.txt +++ b/docs/releases/5.2.8.txt @@ -10,3 +10,7 @@ Bugfixes ======== * Added compatibility for ``oracledb`` 3.4.0 (:ticket:`36646`). + +* Fixed a bug in Django 5.2 where ``QuerySet.first()`` and ``QuerySet.last()`` + raised an error on querysets performing aggregation that selected all fields + of a composite primary key. diff --git a/tests/composite_pk/test_aggregate.py b/tests/composite_pk/test_aggregate.py index d852fdce30..8a2067cb90 100644 --- a/tests/composite_pk/test_aggregate.py +++ b/tests/composite_pk/test_aggregate.py @@ -141,3 +141,23 @@ class CompositePKAggregateTests(TestCase): msg = "Max expression does not support composite primary keys." with self.assertRaisesMessage(ValueError, msg): Comment.objects.aggregate(Max("pk")) + + def test_first_from_unordered_queryset_aggregation_pk_selected(self): + self.assertEqual( + Comment.objects.values("pk").annotate(max=Max("id")).first(), + {"pk": (1, 1), "max": 1}, + ) + + def test_first_from_unordered_queryset_aggregation_pk_selected_separately(self): + self.assertEqual( + Comment.objects.values("tenant", "id").annotate(max=Max("id")).first(), + {"tenant": 1, "id": 1, "max": 1}, + ) + + def test_first_from_unordered_queryset_aggregation_pk_incomplete(self): + msg = ( + "Cannot use QuerySet.first() on an unordered queryset performing " + "aggregation. Add an ordering with order_by()." + ) + with self.assertRaisesMessage(TypeError, msg): + Comment.objects.values("tenant").annotate(max=Max("id")).first()