summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Wobrock <david.wobrock@gmail.com>2022-06-17 09:20:27 +0200
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2022-06-17 11:13:05 +0200
commitd44dc31fcb98935a9e1ec7c5fe60862d2ae4febc (patch)
tree26d4af15b8696d165e9b016ba637a31192fbe6aa
parentccb243847e300283aca23af5eb3b6b4f2bdcfb28 (diff)
[4.1.x] Fixed #28897 -- Fixed QuerySet.update() on querysets ordered by annotations.
Backport of 3ef37a5245015f69a9b9f884ebc289a35d02c5f6 from main
-rw-r--r--django/db/backends/mysql/features.py1
-rw-r--r--django/db/models/query.py14
-rw-r--r--tests/update/tests.py31
3 files changed, 38 insertions, 8 deletions
diff --git a/django/db/backends/mysql/features.py b/django/db/backends/mysql/features.py
index 9583910b1e..1ac1c59be4 100644
--- a/django/db/backends/mysql/features.py
+++ b/django/db/backends/mysql/features.py
@@ -109,6 +109,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
"related fields.": {
"update.tests.AdvancedTests."
"test_update_ordered_by_inline_m2m_annotation",
+ "update.tests.AdvancedTests.test_update_ordered_by_m2m_annotation",
},
}
if "ONLY_FULL_GROUP_BY" in self.connection.sql_mode:
diff --git a/django/db/models/query.py b/django/db/models/query.py
index 67fcb411eb..4e2882e062 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -1159,6 +1159,20 @@ class QuerySet:
self._for_write = True
query = self.query.chain(sql.UpdateQuery)
query.add_update_values(kwargs)
+
+ # Inline annotations in order_by(), if possible.
+ new_order_by = []
+ for col in query.order_by:
+ if annotation := query.annotations.get(col):
+ if getattr(annotation, "contains_aggregate", False):
+ raise exceptions.FieldError(
+ f"Cannot update when ordering by an aggregate: {annotation}"
+ )
+ new_order_by.append(annotation)
+ else:
+ new_order_by.append(col)
+ query.order_by = tuple(new_order_by)
+
# Clear any annotations so that they won't be present in subqueries.
query.annotations = {}
with transaction.mark_for_rollback_on_error(using=self.db):
diff --git a/tests/update/tests.py b/tests/update/tests.py
index 0c3a399514..2162f5164d 100644
--- a/tests/update/tests.py
+++ b/tests/update/tests.py
@@ -225,6 +225,16 @@ class AdvancedTests(TestCase):
new_name=annotation,
).update(name=F("new_name"))
+ def test_update_ordered_by_m2m_aggregation_annotation(self):
+ msg = (
+ "Cannot update when ordering by an aggregate: "
+ "Count(Col(update_bar_m2m_foo, update.Bar_m2m_foo.foo))"
+ )
+ with self.assertRaisesMessage(FieldError, msg):
+ Bar.objects.annotate(m2m_count=Count("m2m_foo")).order_by(
+ "m2m_count"
+ ).update(x=2)
+
def test_update_ordered_by_inline_m2m_annotation(self):
foo = Foo.objects.create(target="test")
Bar.objects.create(foo=foo)
@@ -232,6 +242,13 @@ class AdvancedTests(TestCase):
Bar.objects.order_by(Abs("m2m_foo")).update(x=2)
self.assertEqual(Bar.objects.get().x, 2)
+ def test_update_ordered_by_m2m_annotation(self):
+ foo = Foo.objects.create(target="test")
+ Bar.objects.create(foo=foo)
+
+ Bar.objects.annotate(abs_id=Abs("m2m_foo")).order_by("abs_id").update(x=3)
+ self.assertEqual(Bar.objects.get().x, 3)
+
@unittest.skipUnless(
connection.vendor == "mysql",
@@ -259,14 +276,12 @@ class MySQLUpdateOrderByTest(TestCase):
self.assertEqual(updated, 2)
def test_order_by_update_on_unique_constraint_annotation(self):
- # Ordering by annotations is omitted because they cannot be resolved in
- # .update().
- with self.assertRaises(IntegrityError):
- UniqueNumber.objects.annotate(number_inverse=F("number").desc(),).order_by(
- "number_inverse"
- ).update(
- number=F("number") + 1,
- )
+ updated = (
+ UniqueNumber.objects.annotate(number_inverse=F("number").desc())
+ .order_by("number_inverse")
+ .update(number=F("number") + 1)
+ )
+ self.assertEqual(updated, 2)
def test_order_by_update_on_parent_unique_constraint(self):
# Ordering by inherited fields is omitted because joined fields cannot