Opened 4 years ago

Last modified 2 years ago

#31891 new Bug

Remove cached value of reverse side of 020 relation when updating attname.

Reported by: Josh Owned by: nobody
Component: Database layer (models, ORM) Version: 3.1
Severity: Normal Keywords: orm, related, onetoone
Cc: Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by Josh)

Repro:

from django.db import models


class AppUser(models.Model):
    pass


class Profile(models.Model):
    user = models.OneToOneField(
        to=AppUser,
        on_delete=models.PROTECT,
    )


a = AppUser.objects.create()
b = AppUser.objects.create()
Profile.objects.create(user=a)

b.profile # Errors, since it has not been created yet

a.profile.user_id = b.id
a.profile.save()

# Works!
Profile.objects.get(user=b)
# <Profile: Profile object (1)>

b.profile # Errors, since it uses the cached value

I can fix it by using a.profile.user = b instead of a.profile.user_id = b.id which does the cache invalidation as expected. Or using b.refresh_from_db() after the save() call. However I think it makes sense to fix this within the ORM because it's a subtle bug that can cause issues.
This is a contrived example but the real use-case was cloning a model instance. And instead of erroring it just kept returning the cached value

Change History (6)

comment:1 by Josh, 4 years ago

Description: modified (diff)

comment:2 by Josh, 4 years ago

Description: modified (diff)

comment:3 by Josh, 4 years ago

Description: modified (diff)

comment:4 by Josh, 4 years ago

Type: UncategorizedBug

comment:5 by Josh, 4 years ago

Component: UncategorizedDatabase layer (models, ORM)

comment:6 by Mariusz Felisiak, 4 years ago

Summary: Inconsistent related field cache invalidationRemove cached value of reverse side of 020 relation when updating attname.
Triage Stage: UnreviewedAccepted

Thanks for this ticket, I can see inconsistency but I'm not sure if it's feasible. Tentatively accepting for investigation.

The main difference is that profile.user = b uses one-to-one descriptors and has access to both side instances, on the other hand profile.user_id = b.id uses a deferred attribute.

This is a contrived example but the real use-case was cloning a model instance.

This is probably fixed by #31863.

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