Skip to content

Commit 917a0fb

Browse files
aletor123Andrey Alekseev
andauthored
Fix invalidate_m2o for polymorphic models (#430)
* Fix `invalidate_m2o` for polymorphic models * Fix `invalidate_m2o` for polymorphic models Co-authored-by: Andrey Alekseev <andrey.alekseev@satitasa.com>
1 parent 7d19ba9 commit 917a0fb

File tree

3 files changed

+62
-2
lines changed

3 files changed

+62
-2
lines changed

cacheops/query.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,8 +529,13 @@ def invalidate_m2o(sender, instance, using=DEFAULT_DB_ALIAS):
529529
"""Invoke invalidation for m2o and m2m queries to a deleted instance"""
530530
all_fields = sender._meta.get_fields(include_hidden=True, include_parents=True)
531531
m2o_fields = [f for f in all_fields if isinstance(f, models.ManyToOneRel)]
532+
fk_fields_names_map = {
533+
f.name: f.attname
534+
for f in all_fields if isinstance(f, models.ForeignKey)
535+
}
532536
for f in m2o_fields:
533-
value = getattr(instance, f.field_name)
537+
attr = fk_fields_names_map.get(f.field_name, f.field_name)
538+
value = getattr(instance, attr)
534539
rmodel, rfield = f.related_model, f.remote_field.attname
535540
invalidate_dict(rmodel, {rfield: value}, using=using)
536541

tests/models.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,14 +151,34 @@ class Meta:
151151
proxy = True
152152

153153

154+
class MediaType(models.Model):
155+
name = models.CharField(max_length=50)
156+
154157
# Multi-table inheritance
155158
class Media(models.Model):
156159
name = models.CharField(max_length=128)
160+
media_type = models.ForeignKey(
161+
MediaType,
162+
on_delete=models.CASCADE,
163+
)
164+
165+
def __str__(self):
166+
return str(self.media_type)
167+
157168

158169
class Movie(Media):
159170
year = models.IntegerField()
160171

161172

173+
class Scene(models.Model):
174+
"""Model with FK to submodel."""
175+
name = models.CharField(max_length=50)
176+
movie = models.ForeignKey(
177+
Movie,
178+
on_delete=models.CASCADE,
179+
related_name="scenes",
180+
)
181+
162182
# M2M models
163183
class Label(models.Model):
164184
text = models.CharField(max_length=127, blank=True, default='')

tests/tests.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
from django.db.models.expressions import RawSQL
1515

1616
from cacheops import invalidate_model, invalidate_obj, \
17-
cached, cached_view, cached_as, cached_view_as
17+
cached, cached_view, cached_as, cached_view_as
1818
from cacheops import invalidate_fragment
19+
from cacheops.query import invalidate_m2o
1920
from cacheops.templatetags.cacheops import register
2021

2122
decorator_tag = register.decorator_tag
@@ -681,6 +682,40 @@ def test_407_reverse(self):
681682
with self.assertNumQueries(1):
682683
self.assertEqual(len(Brand.objects.filter(labels=1).cache()), 0)
683684

685+
@mock.patch('cacheops.query.invalidate_dict')
686+
def test_430(self, mock_invalidate_dict):
687+
media_type = MediaType.objects.create(
688+
name="some type"
689+
)
690+
movie = Movie.objects.create(
691+
year=2022,
692+
media_type=media_type,
693+
)
694+
Scene.objects.create(
695+
name="first scene",
696+
movie=movie,
697+
)
698+
invalidate_m2o(Movie, movie)
699+
700+
obj_movie_dict = mock_invalidate_dict.call_args[0][1]
701+
self.assertFalse(isinstance(obj_movie_dict['movie_id'], Media))
702+
self.assertTrue(isinstance(obj_movie_dict['movie_id'], int))
703+
704+
def test_430_no_error_raises(self):
705+
media_type = MediaType.objects.create(
706+
name="some type"
707+
)
708+
movie = Movie.objects.create(
709+
year=2022,
710+
media_type=media_type,
711+
)
712+
Scene.objects.create(
713+
name="first scene",
714+
movie=movie,
715+
)
716+
# no error raises on delete
717+
media_type.delete()
718+
684719

685720
class RelatedTests(BaseTestCase):
686721
fixtures = ['basic']

0 commit comments

Comments
 (0)