django-modelcluster icon indicating copy to clipboard operation
django-modelcluster copied to clipboard

Fix prefetching in DeferringManyRelatedManager._apply_rel_filters()

Open bbliem opened this issue 6 months ago • 0 comments

Suppose we have a ParentalManyToManyField and we use prefetch_related with a lookup queryset. Django's prefetch_one_level() in django.db.models.query iterates over a list of instances. For each of them, it calls DeferringManyRelatedManager._apply_rel_filters(lookup.queryset) and thus obtains a queryset, for which prefetch_one_level() then sets the attribute _result_cache.

This is currently buggy in modelcluster as DeferringManyRelatedManager._apply_rel_filters() directly returns the given queryset instead of a copy. When _apply_rel_filters(lookup.queryset) is called twice, the second call returns the same object as the first. This is a problem because, in the second loop iteration of prefetch_one_level(), the cache for the first object will be overwritten by the cache for the second object. In fact, all objects will end up with the same cache -- the one of the last object.

The current commit fixes this by making DeferringManyRelatedManager._apply_rel_filters() return a copy of the given queryset. It also adds a unit test.

Note that basically the same problem was already fixed in https://github.com/wagtail/django-modelcluster/pull/130 for RelatedManager, but apparently applying the fix to DeferringManyRelatedManager has been forgotten.

bbliem avatar Oct 29 '25 13:10 bbliem