#33984: Related managers cache gets stale after saving a fetched model with new
PK
-------------------------------------+-------------------------------------
Reporter: joeli | Owner: nobody
Type: Bug | Status: new
Component: Database | Version: 4.1
layer (models, ORM) |
Severity: Normal | Keywords:
Triage Stage: | Has patch: 0
Unreviewed |
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
-------------------------------------+-------------------------------------
I'm upgrading our codebase from Django 3.2 to 4.1, and came upon a
regression when running our test suite. I bisected it down to commit
[4f8c7fd9d9 Fixed #32980 -- Made models cache related
managers](https://github.com/django/django/commit/4f8c7fd9d91b35e2c2922de4bb50c8c8066cbbc6).
The main problem is that when you have fetched a model containing a m2m
field from the database, and access its m2m field the manager gets cached.
If you then set the model's `.pk` to `None` and do a `.save()` to save it
as a copy of the old one, the related manager cache is not cleared. Here's
some code with inline comments to demonstrate:
{{{
# models.py
from django.db import models
class Tag(models.Model):
tag = models.SlugField(max_length=64, unique=True)
def __str__(self):
return self.tag
class Thing(models.Model):
tags = models.ManyToManyField(Tag, blank=True)
def clone(self) -> 'Thing':
# To clone a thing, we first save a list of the tags it has
tags = list(self.tags.all())
# Then set its pk to none and save, creating the copy
self.pk = None
self.save()
# In django 3.2 the following sets the original tags for the new
instance.
# In 4.x it's a no-op because self.tags returns the old instance's
manager.
self.tags.set(tags)
return self
@staticmethod
def has_bug():
one, _ = Tag.objects.get_or_create(tag='one')
two, _ = Tag.objects.get_or_create(tag='two')
obj = Thing.objects.create()
obj.tags.set([one, two])
new_thing = obj.clone()
# new_thing.tags.all() returns the expected tags, but it is
actually returning obj.tags.all()
# If we fetch new_thing again it returns the actual tags for
new_thing, which is empty.
#
# Even `new_thing.refresh_from_db()` -- which is not required with
3.x -- does not help.
# `new_thing._state.related_managers_cache.clear()` works, but
feels like something I
# shouldn't have to do.
return list(new_thing.tags.all()) !=
list(Thing.objects.get(pk=new_thing.pk).tags.all())
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/33984>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/0107018312927eaf-bd9bffac-d1ba-4054-905c-6046e658c574-000000%40eu-central-1.amazonses.com.