Fixed #26706 -- Made RelatedManager modification methods clear prefetch_related() cache.
This commit is contained in:
parent
ea65c7cb48
commit
d30febb4e5
|
@ -577,6 +577,12 @@ def create_reverse_many_to_one_manager(superclass, rel):
|
||||||
queryset._known_related_objects = {self.field: {self.instance.pk: self.instance}}
|
queryset._known_related_objects = {self.field: {self.instance.pk: self.instance}}
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
def _remove_prefetched_objects(self):
|
||||||
|
try:
|
||||||
|
self.instance._prefetched_objects_cache.pop(self.field.related_query_name())
|
||||||
|
except (AttributeError, KeyError):
|
||||||
|
pass # nothing to clear from cache
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
try:
|
try:
|
||||||
return self.instance._prefetched_objects_cache[self.field.related_query_name()]
|
return self.instance._prefetched_objects_cache[self.field.related_query_name()]
|
||||||
|
@ -606,6 +612,7 @@ def create_reverse_many_to_one_manager(superclass, rel):
|
||||||
return queryset, rel_obj_attr, instance_attr, False, cache_name
|
return queryset, rel_obj_attr, instance_attr, False, cache_name
|
||||||
|
|
||||||
def add(self, *objs, **kwargs):
|
def add(self, *objs, **kwargs):
|
||||||
|
self._remove_prefetched_objects()
|
||||||
bulk = kwargs.pop('bulk', True)
|
bulk = kwargs.pop('bulk', True)
|
||||||
objs = list(objs)
|
objs = list(objs)
|
||||||
db = router.db_for_write(self.model, instance=self.instance)
|
db = router.db_for_write(self.model, instance=self.instance)
|
||||||
|
@ -680,6 +687,7 @@ def create_reverse_many_to_one_manager(superclass, rel):
|
||||||
clear.alters_data = True
|
clear.alters_data = True
|
||||||
|
|
||||||
def _clear(self, queryset, bulk):
|
def _clear(self, queryset, bulk):
|
||||||
|
self._remove_prefetched_objects()
|
||||||
db = router.db_for_write(self.model, instance=self.instance)
|
db = router.db_for_write(self.model, instance=self.instance)
|
||||||
queryset = queryset.using(db)
|
queryset = queryset.using(db)
|
||||||
if bulk:
|
if bulk:
|
||||||
|
@ -856,6 +864,12 @@ def create_forward_many_to_many_manager(superclass, rel, reverse):
|
||||||
queryset = queryset.using(self._db)
|
queryset = queryset.using(self._db)
|
||||||
return queryset._next_is_sticky().filter(**self.core_filters)
|
return queryset._next_is_sticky().filter(**self.core_filters)
|
||||||
|
|
||||||
|
def _remove_prefetched_objects(self):
|
||||||
|
try:
|
||||||
|
self.instance._prefetched_objects_cache.pop(self.prefetch_cache_name)
|
||||||
|
except (AttributeError, KeyError):
|
||||||
|
pass # nothing to clear from cache
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
try:
|
try:
|
||||||
return self.instance._prefetched_objects_cache[self.prefetch_cache_name]
|
return self.instance._prefetched_objects_cache[self.prefetch_cache_name]
|
||||||
|
@ -909,7 +923,7 @@ def create_forward_many_to_many_manager(superclass, rel, reverse):
|
||||||
"intermediary model. Use %s.%s's Manager instead." %
|
"intermediary model. Use %s.%s's Manager instead." %
|
||||||
(opts.app_label, opts.object_name)
|
(opts.app_label, opts.object_name)
|
||||||
)
|
)
|
||||||
|
self._remove_prefetched_objects()
|
||||||
db = router.db_for_write(self.through, instance=self.instance)
|
db = router.db_for_write(self.through, instance=self.instance)
|
||||||
with transaction.atomic(using=db, savepoint=False):
|
with transaction.atomic(using=db, savepoint=False):
|
||||||
self._add_items(self.source_field_name, self.target_field_name, *objs)
|
self._add_items(self.source_field_name, self.target_field_name, *objs)
|
||||||
|
@ -927,6 +941,7 @@ def create_forward_many_to_many_manager(superclass, rel, reverse):
|
||||||
"an intermediary model. Use %s.%s's Manager instead." %
|
"an intermediary model. Use %s.%s's Manager instead." %
|
||||||
(opts.app_label, opts.object_name)
|
(opts.app_label, opts.object_name)
|
||||||
)
|
)
|
||||||
|
self._remove_prefetched_objects()
|
||||||
self._remove_items(self.source_field_name, self.target_field_name, *objs)
|
self._remove_items(self.source_field_name, self.target_field_name, *objs)
|
||||||
remove.alters_data = True
|
remove.alters_data = True
|
||||||
|
|
||||||
|
@ -938,6 +953,7 @@ def create_forward_many_to_many_manager(superclass, rel, reverse):
|
||||||
instance=self.instance, reverse=self.reverse,
|
instance=self.instance, reverse=self.reverse,
|
||||||
model=self.model, pk_set=None, using=db,
|
model=self.model, pk_set=None, using=db,
|
||||||
)
|
)
|
||||||
|
self._remove_prefetched_objects()
|
||||||
filters = self._build_remove_filters(super(ManyRelatedManager, self).get_queryset().using(db))
|
filters = self._build_remove_filters(super(ManyRelatedManager, self).get_queryset().using(db))
|
||||||
self.through._default_manager.using(db).filter(filters).delete()
|
self.through._default_manager.using(db).filter(filters).delete()
|
||||||
|
|
||||||
|
|
|
@ -979,6 +979,18 @@ database.
|
||||||
performance, since you have done a database query that you haven't used. So
|
performance, since you have done a database query that you haven't used. So
|
||||||
use this feature with caution!
|
use this feature with caution!
|
||||||
|
|
||||||
|
Also, if you call the database-altering methods
|
||||||
|
:meth:`~django.db.models.fields.related.RelatedManager.add`,
|
||||||
|
:meth:`~django.db.models.fields.related.RelatedManager.remove`,
|
||||||
|
:meth:`~django.db.models.fields.related.RelatedManager.clear` or
|
||||||
|
:meth:`~django.db.models.fields.related.RelatedManager.set`, on
|
||||||
|
:class:`related managers<django.db.models.fields.related.RelatedManager>`,
|
||||||
|
any prefetched cache for the relation will be cleared.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.11
|
||||||
|
|
||||||
|
The clearing of the prefetched cache described above was added.
|
||||||
|
|
||||||
You can also use the normal join syntax to do related fields of related
|
You can also use the normal join syntax to do related fields of related
|
||||||
fields. Suppose we have an additional model to the example above::
|
fields. Suppose we have an additional model to the example above::
|
||||||
|
|
||||||
|
|
|
@ -170,6 +170,14 @@ Related objects reference
|
||||||
``add()``, ``create()``, ``remove()``, and ``set()`` methods are
|
``add()``, ``create()``, ``remove()``, and ``set()`` methods are
|
||||||
disabled.
|
disabled.
|
||||||
|
|
||||||
|
If you use :meth:`~django.db.models.query.QuerySet.prefetch_related`,
|
||||||
|
the ``add()``, ``remove()``, ``clear()``, and ``set()`` methods clear
|
||||||
|
the prefetched cache.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.11
|
||||||
|
|
||||||
|
The clearing of the prefetched cache described above was added.
|
||||||
|
|
||||||
Direct Assignment
|
Direct Assignment
|
||||||
=================
|
=================
|
||||||
|
|
||||||
|
|
|
@ -357,6 +357,13 @@ Miscellaneous
|
||||||
* The ``checked`` attribute rendered by form widgets now uses HTML5 boolean
|
* The ``checked`` attribute rendered by form widgets now uses HTML5 boolean
|
||||||
syntax rather than XHTML's ``checked='checked'``.
|
syntax rather than XHTML's ``checked='checked'``.
|
||||||
|
|
||||||
|
* :meth:`RelatedManager.add()
|
||||||
|
<django.db.models.fields.related.RelatedManager.add>`,
|
||||||
|
:meth:`~django.db.models.fields.related.RelatedManager.remove`,
|
||||||
|
:meth:`~django.db.models.fields.related.RelatedManager.clear`, and
|
||||||
|
:meth:`~django.db.models.fields.related.RelatedManager.set` now
|
||||||
|
clear the ``prefetch_related()`` cache.
|
||||||
|
|
||||||
.. _deprecated-features-1.11:
|
.. _deprecated-features-1.11:
|
||||||
|
|
||||||
Features deprecated in 1.11
|
Features deprecated in 1.11
|
||||||
|
|
|
@ -518,6 +518,40 @@ class ManyToManyTests(TestCase):
|
||||||
self.assertQuerysetEqual(self.a4.publications.all(), [])
|
self.assertQuerysetEqual(self.a4.publications.all(), [])
|
||||||
self.assertQuerysetEqual(self.p2.article_set.all(), ['<Article: NASA finds intelligent life on Earth>'])
|
self.assertQuerysetEqual(self.p2.article_set.all(), ['<Article: NASA finds intelligent life on Earth>'])
|
||||||
|
|
||||||
|
def test_clear_after_prefetch(self):
|
||||||
|
a4 = Article.objects.prefetch_related('publications').get(id=self.a4.id)
|
||||||
|
self.assertQuerysetEqual(a4.publications.all(), ['<Publication: Science News>'])
|
||||||
|
a4.publications.clear()
|
||||||
|
self.assertQuerysetEqual(a4.publications.all(), [])
|
||||||
|
|
||||||
|
def test_remove_after_prefetch(self):
|
||||||
|
a4 = Article.objects.prefetch_related('publications').get(id=self.a4.id)
|
||||||
|
self.assertQuerysetEqual(a4.publications.all(), ['<Publication: Science News>'])
|
||||||
|
a4.publications.remove(self.p2)
|
||||||
|
self.assertQuerysetEqual(a4.publications.all(), [])
|
||||||
|
|
||||||
|
def test_add_after_prefetch(self):
|
||||||
|
a4 = Article.objects.prefetch_related('publications').get(id=self.a4.id)
|
||||||
|
self.assertEqual(a4.publications.count(), 1)
|
||||||
|
a4.publications.add(self.p1)
|
||||||
|
self.assertEqual(a4.publications.count(), 2)
|
||||||
|
|
||||||
|
def test_set_after_prefetch(self):
|
||||||
|
a4 = Article.objects.prefetch_related('publications').get(id=self.a4.id)
|
||||||
|
self.assertEqual(a4.publications.count(), 1)
|
||||||
|
a4.publications.set([self.p2, self.p1])
|
||||||
|
self.assertEqual(a4.publications.count(), 2)
|
||||||
|
a4.publications.set([self.p1])
|
||||||
|
self.assertEqual(a4.publications.count(), 1)
|
||||||
|
|
||||||
|
def test_add_then_remove_after_prefetch(self):
|
||||||
|
a4 = Article.objects.prefetch_related('publications').get(id=self.a4.id)
|
||||||
|
self.assertEqual(a4.publications.count(), 1)
|
||||||
|
a4.publications.add(self.p1)
|
||||||
|
self.assertEqual(a4.publications.count(), 2)
|
||||||
|
a4.publications.remove(self.p1)
|
||||||
|
self.assertQuerysetEqual(a4.publications.all(), ['<Publication: Science News>'])
|
||||||
|
|
||||||
def test_inherited_models_selects(self):
|
def test_inherited_models_selects(self):
|
||||||
"""
|
"""
|
||||||
#24156 - Objects from child models where the parent's m2m field uses
|
#24156 - Objects from child models where the parent's m2m field uses
|
||||||
|
|
|
@ -43,7 +43,7 @@ class City(models.Model):
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class District(models.Model):
|
class District(models.Model):
|
||||||
city = models.ForeignKey(City, models.CASCADE)
|
city = models.ForeignKey(City, models.CASCADE, related_name='districts', null=True)
|
||||||
name = models.CharField(max_length=50)
|
name = models.CharField(max_length=50)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
|
@ -624,3 +624,48 @@ class ManyToOneTests(TestCase):
|
||||||
# doesn't exist should be an instance of a subclass of `AttributeError`
|
# doesn't exist should be an instance of a subclass of `AttributeError`
|
||||||
# refs #21563
|
# refs #21563
|
||||||
self.assertFalse(hasattr(Article(), 'reporter'))
|
self.assertFalse(hasattr(Article(), 'reporter'))
|
||||||
|
|
||||||
|
def test_clear_after_prefetch(self):
|
||||||
|
c = City.objects.create(name='Musical City')
|
||||||
|
District.objects.create(name='Ladida', city=c)
|
||||||
|
city = City.objects.prefetch_related('districts').get(id=c.id)
|
||||||
|
self.assertQuerysetEqual(city.districts.all(), ['<District: Ladida>'])
|
||||||
|
city.districts.clear()
|
||||||
|
self.assertQuerysetEqual(city.districts.all(), [])
|
||||||
|
|
||||||
|
def test_remove_after_prefetch(self):
|
||||||
|
c = City.objects.create(name='Musical City')
|
||||||
|
d = District.objects.create(name='Ladida', city=c)
|
||||||
|
city = City.objects.prefetch_related('districts').get(id=c.id)
|
||||||
|
self.assertQuerysetEqual(city.districts.all(), ['<District: Ladida>'])
|
||||||
|
city.districts.remove(d)
|
||||||
|
self.assertQuerysetEqual(city.districts.all(), [])
|
||||||
|
|
||||||
|
def test_add_after_prefetch(self):
|
||||||
|
c = City.objects.create(name='Musical City')
|
||||||
|
District.objects.create(name='Ladida', city=c)
|
||||||
|
d2 = District.objects.create(name='Ladidu')
|
||||||
|
city = City.objects.prefetch_related('districts').get(id=c.id)
|
||||||
|
self.assertEqual(city.districts.count(), 1)
|
||||||
|
city.districts.add(d2)
|
||||||
|
self.assertEqual(city.districts.count(), 2)
|
||||||
|
|
||||||
|
def test_set_after_prefetch(self):
|
||||||
|
c = City.objects.create(name='Musical City')
|
||||||
|
District.objects.create(name='Ladida', city=c)
|
||||||
|
d2 = District.objects.create(name='Ladidu')
|
||||||
|
city = City.objects.prefetch_related('districts').get(id=c.id)
|
||||||
|
self.assertEqual(city.districts.count(), 1)
|
||||||
|
city.districts.set([d2])
|
||||||
|
self.assertQuerysetEqual(city.districts.all(), ['<District: Ladidu>'])
|
||||||
|
|
||||||
|
def test_add_then_remove_after_prefetch(self):
|
||||||
|
c = City.objects.create(name='Musical City')
|
||||||
|
District.objects.create(name='Ladida', city=c)
|
||||||
|
d2 = District.objects.create(name='Ladidu')
|
||||||
|
city = City.objects.prefetch_related('districts').get(id=c.id)
|
||||||
|
self.assertEqual(city.districts.count(), 1)
|
||||||
|
city.districts.add(d2)
|
||||||
|
self.assertEqual(city.districts.count(), 2)
|
||||||
|
city.districts.remove(d2)
|
||||||
|
self.assertEqual(city.districts.count(), 1)
|
||||||
|
|
Loading…
Reference in New Issue