I have a use case for accessing side effects trapped on a model’s save()
method but aborting the underlying save with update_fields=[]
, as documented.
To do that, I needed to alter my update_fields
helper (also documented) from:
if (update_fields := kwargs.get("update_fields")) is not None: # wrong
kwargs["update_fields"] = ...
to:
if update_fields := kwargs.get("update_fields"):
...
to avoid adding a field to update_fields
in the event that it was deliberately an empty iterable. This brought my helper in line with the documentation.
However, this caused the regression test for my original helper to fail.
obj, created = Model.objects.update_or_create(pk=foo, other=bar)
baz_value = obj.baz
obj.refresh_from_db()
self.assertEqual(obj.baz, baz_value)
The baz
value is calculated in save()
but not persisted because update_or_create()
sends set()
instead of None
when defaults=None
. This in turn makes it impossible for my helper to distinguish whether it’s appropriate to add baz
to the update_fields
.
In short, it seems I can either ensure .update_or_create(defaults=None)
works, or .save(update_fields=[])
works, but not both.
This is a nice demonstration of the pitfalls of implementing logic inside save()
, but I’m not in a position to direct any such rework like that. Instead, I’m wondering: should update_or_create(defaults=None)
be a little more careful about sending None
rather than set()
to update_fields? The docs say update_or_create()
is shorthand for calling save()
, so my impression is that it should delegate these decisions to save()
instead of presumptuously passing set()
.
This diff passes the test suite:
diff --git a/django/db/models/query.py b/django/db/models/query.py
index 1270141619..cef769c6c2 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -1009,7 +1009,7 @@ class QuerySet(AltersData):
update_fields.add(field.name)
if field.name != field.attname:
update_fields.add(field.attname)
- obj.save(using=self.db, update_fields=update_fields)
+ obj.save(using=self.db, update_fields=update_fields or None)
else:
obj.save(using=self.db)
return obj, False
Ideas?