#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.

Reply via email to