-
-
Notifications
You must be signed in to change notification settings - Fork 483
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix HistoricForeignKey when used together with prefetch_related() #1159
base: master
Are you sure you want to change the base?
Conversation
Codecov Report
@@ Coverage Diff @@
## master #1159 +/- ##
=======================================
Coverage 97.24% 97.24%
=======================================
Files 23 23
Lines 1234 1234
Branches 200 200
=======================================
Hits 1200 1200
Misses 16 16
Partials 18 18
📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more |
Plans to review/merge this? (I'm in the process of integrating this package for the first time and I spent a few hours chasing around a bug until I realized it was caused by this issue.) cc: @ddabble ? |
any progress? |
Any reason why this is not getting merged? We are also having the same issue |
I'm happy to help. If there is anything I can do, please let me know. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, good job investigating this solution! 😄 I agree with your reasoning for why it should be safe removing the call to _apply_rel_filters()
. Furthermore, it looks like create_reverse_many_to_one_manager()
usually gets a standard Manager
as its superclass
arg, which doesn't contain any _apply_rel_filters()
method. And so it seems likely that RelatedManager
was designed to be expecting that its superclass' get_queryset()
method doesn't do any of the things that _apply_rel_filters()
does, which again supports the decision to remove the call.
Do you think we should remove the try
block with self.instance._prefetched_objects_cache[...]
as well? The exact same call is in RelatedManager.get_queryset()
, but it doesn't seem to hurt performance much... Though I'm not sure about the consequences of removing it (or any unknown, existing bugs that might live on if we leave it there, for that matter), considering that RelatedManager.get_prefetch_querysets()
seems to skip that cache lookup by calling super().get_queryset()
, combined with the fact that the mentioned standard Manager
also doesn't make any cache lookups in get_queryset()
🤔
Lastly, could you add a test case for a nested prefetch_related()
call, like the SubSub
examples in the description of #1152? 🙂 I guess it should be enough e.g. adding a new history-tracked model with a HistoricForeignKey
referencing TestHistoricParticipanToHistoricOrganization
, and test with the different arguments to prefetch_related()
from the mentioned SubSub
examples, which would include "historic_participants__<subsub related_name>"
and various nested variants using Prefetch(...)
.
Other than that, very clear and thorough test cases! And sorry that it took so long getting to reviewing this!
@@ -4,6 +4,7 @@ Changes | |||
Unreleased | |||
---------- | |||
|
|||
- Fixed ``HistoricForeignKey`` behaviour when used together with ``prefetch_related()`` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be useful being a little more explicit on what the bug used to be; also, adding the issue number 🙂
- Fixed ``HistoricForeignKey`` behaviour when used together with ``prefetch_related()`` | |
- Fixed ``HistoricForeignKey``'s behaviour when used together with | |
``prefetch_related()``, which caused some related objects to be missing (gh-1152) |
with self.assertNumQueries(2): | ||
record1, record2 = TestOrganizationWithHistory.objects.prefetch_related( | ||
"participants" | ||
).all() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all()
after prefetch_related()
(or most other queryset-returning methods) is unnecessary.
).all() | |
) |
The same goes for the similar calls to all()
in the two test cases below.
@@ -2545,3 +2545,96 @@ def test_historic_to_historic(self): | |||
)[0] | |||
pt1i = pt1h.instance | |||
self.assertEqual(pt1i.organization.name, "original") | |||
|
|||
def test_non_historic_to_historic_prefetch(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a docstring detailing a little more explicitly what the test case is testing 🙂 E.g. something like this: (feel free to adjust the text)
def test_non_historic_to_historic_prefetch(self): | |
def test_non_historic_to_historic_prefetch(self): | |
"""Test that the field works properly with ``prefetch_related()`` when defined | |
on a model *without* history tracking, and with the ``to`` arg set to a model | |
*with* history tracking.""" |
The same goes for the two test cases below.
Description
This fixes behaviour of
HistoricForeignKey
when used together withprefetch_related()
.The following is sufficient to reproduce the bug:
Related Issue
#1152
Motivation and Context
Property
related_manager_cls
is defined inHistoricReverseManyToOneDescriptor
. This property returns a function call ofcreate_reverse_many_to_one_manager
function and passes itHistoricRelationModelManager
as an argument. However,create_reverse_many_to_one_manager
definesRelatedManager
, which subclasses the manager passed as an argument and returns thisRelatedManager
class.When
prefetch_related
is used,get_prefetch_queryset
method ofRelatedManager
is called.The problem lies in the fact that it bypasses
RelatedManager.get_queryset
method which does filtering to instance (_apply_rel_filters
) by callingsuper().get_queryset()
. But due to inheritance, it gets evaluated toHistoricRelationModelManager.get_queryset
which does the same filtering as well.This SQL is generated then (notice the
"poll_choice"."poll_id" = 1
condition):Removing
_apply_rel_filters
call fromHistoricRelationModelManager.get_queryset
solves this problem.On the other hand, since
create_reverse_many_to_one_manager
returnsRelatedManager
, rather thanHistoricRelationModelManager
, the only way to accessHistoricRelationModelManager.get_queryset
is by callingsuper().get_queryset()
fromRelatedManager
. This call can be found:RelatedManager.get_prefetch_queryset
where it is undesired,RelatedManager.get_queryset
where_apply_rel_filters
is called right aftersuper().get_queryset()
.Therefore, I believe it is safe to remove it.
How Has This Been Tested?
Added new tests.
Screenshots (if appropriate):
Types of changes
Checklist:
pre-commit run
command to format and lint.AUTHORS.rst
CHANGES.rst