Refresh `GeneratedField` column when calling save

When using a GeneratedField there’s the follow caveat mentioned in the docs.

Since the database always computed the value, the object must be reloaded to access the new value after save(), for example, by using refresh_from_db().

Taking this Square model example from the Django 5.0 release notes, we can see that when we create a square instance the generated area is returned for us and set on the in-memory instance (note, this is true when using either the model manager create() method or calling save() on an unsaved model instance). However, when we update and call save() on that instance, as mentioned, the generated area is not updated and we have to explicitly use refresh_from_db(). For example:

>>> square = models.Square.objects.create(side=2)
# INSERT INTO "square" ("side") VALUES (2) RETURNING "square"."id", "square"."area";

>>> square.area
4

>>> square.side = 5

>>> square.save()
# UPDATE "square" SET "side" = 5 WHERE "square"."id" = 1;

>>> square.area
4

>>> square.refresh_from_db()
# SELECT "square"."id", "square"."side", "square"."area" FROM "square" WHERE "square"."id" = 1 LIMIT 21;

>>> square.area
25

Would it be feasible to add support into Django to get that generated area field updated on the in-memory instance as part of calling save by making use of the RETURNING SQL clause? Perhaps this could happen when using the update_fields kwarg or maybe more explicitly with a new returning_fields kwarg? Dummy example:

>>> square.save(update_fields=["side", "area"])
# UPDATE "square" SET "side" = 5 WHERE "square"."id" = 1 RETURNING "square"."side", "square"."area";

>>> square.save(returning_fields=["area"])
# UPDATE "square" SET "side" = 5 WHERE "square"."id" = 1 RETURNING "square"."area";

Hello @jamesbeith!

In terms of what the API should look like I believe that a distinct returning_fields might be preferable to avoid ambiguity but update_fields is also technically feasible given it’s currently a noop for GeneratedField.

This feature request has a bit of overlap with the accepted one about adding support for update(returning) so could I see them go hand-in-hand at least in how we expose both features and how they reuse the same compile logic. In this case though we don’t have update API collisions for op-tin in as save doesn’t special case **kwargs.

You might find the proposed patch for the above interesting as it implements the required bits of the compiler except that it doesn’t use the API that was initially agreed upon. Given this feature might be useful for non-GeneratedField as well (e.g. imagine some user defined triggers) I think it warrants a specialized kwarg.

So to answer your question, yes it’s doable there is interest. What’s left to do to make things happen though is for someone to shepherd the changes to mergeable state which will inevitably involve a bit of bike shedding around what the exact API should look like. If that’s something that is of interest to you please dive in and we’ll do our best to support you through the journey.

Digging a bit more I also found this long standing ticket about having fields that expressions automatically refreshed using RETURNING.

This made me think we might want to have the kwarg default to all fields assigned as expressions if not specified to resolve the ticket in the same iteration?

In other words doing something like

author.book_count = F("book_count") + 1
author.save()

Would default returning_fields to {"book_count"} assuming it’s supported by the backend.

I thought we could also name he kwarg refresh_fields but I think it’s worth keeping as an alternative name for now given refresh is currently tied to the concept of a doing a distinct query to retrieve data which is not the case when using RETURNING.

1 Like