Opened 2 years ago

Last modified 2 years ago

#34137 closed Bug

model.refresh_from_db() doesn't seem to clear cached generic foreign keys — at Initial Version

Reported by: pascal chambon Owned by: nobody
Component: contrib.contenttypes Version: 3.2
Severity: Normal Keywords:
Cc: Simon Charette Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

In my code, Users have a generic foreign key like so:

    controlled_entity_content_type = models.ForeignKey(ContentType, blank=True, null=True, on_delete=models.CASCADE)
    controlled_entity_object_id = models.PositiveIntegerField(blank=True, null=True)
    controlled_entity = GenericForeignKey("controlled_entity_content_type", "controlled_entity_object_id")

However, in unit-tests, when I refresh a user instance, the controlled_entity relation isn't cleared from cache, as can be seen here with IDs:

        old_controlled_entity = authenticated_user.controlled_entity
        authenticated_user.refresh_from_db()
        new_controlled_entity = authenticated_user.controlled_entity
        assert id(old_controlled_entity) != id(new_controlled_entity)   # FAILS

And this leads to subtle bugs like non-transitive equalities in tests :

        assert authenticated_user.controlled_entity == fixtures.client1_project2_organization3
        assert fixtures.client1_project2_organization3.get_pricing_plan() == pricing_plan
        assert authenticated_user.controlled_entity.get_pricing_plan() == pricing_plan     # FAILS

Calling "authenticated_user.controlled_entity.refresh_from_db()" solevd this particular bug, but "authenticated_user.refresh_from_db()" isn't enough.

Tested under Django3.2.13 but the code of refresh_from_db() hasn't changed since then in Git's main branch (except few cosmetic adjustments on code format).

I'm a bit lost in the code of refresh_from_db(), but I've just seen that the generic relation appears once in this loop, but is not considered as "cached" in the if() branch.

        for field in self._meta.related_objects:
            #print("%% CLEARING RELATED FIELD", field)
            if field.is_cached(self):
                #print("%% DONE")   # not called
                field.delete_cached_value(self)

Change History (0)

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