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)