Opened 2 hours ago

Last modified 24 minutes ago

#35890 new Bug

pre_save field in parent models are not called during update in update_or_create

Reported by: Gagaro Owned by:
Component: Database layer (models, ORM) Version: dev
Severity: Normal Keywords:
Cc: Gagaro Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: yes
Easy pickings: no UI/UX: no

Description (last modified by Gagaro)

With the following models:

class Parent(models.Model):
    modified_at = models.DateTimeField(auto_now=True)


class Child(Parent):
    modified_at_child = models.DateTimeField(auto_now=True)

When calling update_or_create, the pre_save of the modified_at field is not called, and the field is not updated:

# Create

>>> instance, _ = Child.objects.update_or_create(pk=1)
>>> instance.modified_at
datetime.datetime(2024, 11, 6, 14, 1, 11, 52881, tzinfo=datetime.timezone.utc)
>>> instance.modified_at_child
datetime.datetime(2024, 11, 6, 14, 1, 11, 54363, tzinfo=datetime.timezone.utc)


# Update

>>> instance, _ = Child.objects.update_or_create(pk=1)
>>> instance.modified_at
datetime.datetime(2024, 11, 6, 14, 1, 11, 52881, tzinfo=datetime.timezone.utc)
>>> instance.modified_at_child
datetime.datetime(2024, 11, 6, 14, 1, 21, 517245, tzinfo=datetime.timezone.utc)

The regression seems to have happened in https://github.com/django/django/commit/6cc0f22a73970dd7c0d29d4d8d2ff9e1cc862b30

Using self.model._meta.concrete_fields instead of self.model._meta.local_concrete_fields seems to solve the issue, but I don't know the internal API well enough to understand all of the implications.

PR : https://github.com/django/django/pull/18777

Change History (4)

comment:1 by Gagaro, 2 hours ago

Has patch: set

comment:2 by Gagaro, 2 hours ago

Description: modified (diff)

comment:3 by Simon Charette, 39 minutes ago

Patch needs improvement: set
Triage Stage: UnreviewedAccepted

I see no reasons why this should only work for local concrete fields given update_or_create works with MTI.

There's a nuance not captured in the patch though that I believe should be addressed at the same time. Only pre-save fields for the model associated with at-least-one update field should be augmented. What I mean by that is that given the following models

class Location(models.Model):
    location_updated_at = models.DateTimeField(auto_now=True)
    location_street = models.TextField()

class Restaurant(Location):
    restaurant_updated_at = models.DateTimeField(auto_now=True)
    menu = models.JSONField()

Then

Restaurant.objects.update_or_create(
   ...,
   defaults={"menu": ...}  # Only restaurant_updated_at should be included in `update_fields`
)
Restaurant.objects.update_or_create(
   ...,
   defaults={"location_street": ...}  # Only location_updated_at should be included in `update_fields`
)
Restaurant.objects.update_or_create(
   ...,
   defaults={"menu": ..., "location_street": ...}  # Both restaurant_updated_at and location_updated_at should be included in `update_fields`
)

comment:4 by Gagaro, 24 minutes ago

Why do you think it should be that way?

In our cases, we have an auto_now field on the parent model only, and we need it to be updated every time, even if there is only a single change in a field from a child model.

Note: See TracTickets for help on using tickets.
Back to Top