feat: rewrote the method for getting m2m objects#2205
Conversation
|
Thanks for the patch. The change looks useful for enabling
Overall, the direction looks promising, but these cases seem worth verifying with focused regression tests. |
|
@waketzheng
|
|
Thanks for the update. Consider this: The previous concerns around One remaining edge case may be worth confirming: in the new through-table lookup, the parent PKs are converted with .where(forward_field.isin(tuple(related_objects_by_pks)))For A regression test in @pytest.mark.asyncio
async def test_prefetch_related(db, m2m_uuid_models):
UUIDPkModel, UUIDM2MRelatedModel = m2m_uuid_models
one = await UUIDPkModel.create()
two = await UUIDM2MRelatedModel.create()
await one.peers.add(two)
fetched = await UUIDPkModel.get(pk=one.pk).prefetch_related("peers")
assert list(fetched.peers) == [two]It may be safer to convert the related PKs before the through-table query as well, analogous to |
|
@waketzheng Thanks! I corrected it. |
|
The latest update appears to address the UUID primary-key case. The through-table lookup now keys This also covers the source-field UUID case in the same path: the related object is still used later when mapping back via One small follow-up that may be worth adding is a regression test in @pytest.mark.asyncio
async def test_prefetch_related(db, m2m_uuid_models):
UUIDPkModel, UUIDM2MRelatedModel = m2m_uuid_models
one = await UUIDPkModel.create()
two = await UUIDM2MRelatedModel.create()
await one.peers.add(two)
fetched = await UUIDPkModel.get(pk=one.pk).prefetch_related("peers")
assert list(fetched.peers) == [two] |
|
@waketzheng I added your test |
| instance_id_set: set = { | ||
| instance._meta.pk.to_db_value(instance.pk, instance) for instance in instance_list | ||
| } | ||
| to_attr, queryset = related_query |
There was a problem hiding this comment.
@VladislavYar Why do you change it from related_query to queryset?
There was a problem hiding this comment.
Because the tuple[str | None, QuerySet] (related_query) type is assigned a value with the QuerySet type. Therefore, it is better to create a separate variable and the name of the queryset is clearer.
I will rename it to related_queryset, because the _prefetch_direct_relation method has the same name.
| for instance in instance_list: | ||
| relation_container = getattr(instance, field) | ||
| relation_container._set_result_for_query(relation_map.get(instance.pk, []), to_attr) | ||
| getattr(instance, field)._set_result_for_query( |
There was a problem hiding this comment.
One-line style reduces readability; it would be better to roll it back.
| field_object: ManyToManyFieldInstance = self.model._meta.fields_map[field] # type: ignore | ||
|
|
||
| through_table = Table(field_object.through, schema=field_object.through_schema) | ||
| model_pk = self.model._meta.pk |
There was a problem hiding this comment.
model._meta.pk is a field object, not a model class or instance; therefore, the variable name can be pk_field.
|
|
||
| through_table = Table(field_object.through, schema=field_object.through_schema) | ||
| model_pk = self.model._meta.pk | ||
| instance_pks = [model_pk.to_db_value(instance.pk, instance) for instance in instance_list] |
There was a problem hiding this comment.
It would be better to keep instance_id_set instead of changing it to a list. Keeping the change minimal makes it easier to review.
Description
The
_prefetch_m2m_relationmethod of theBaseExecutorclass has been rewritten. Now objects are received directly through the specifiedqueryset, which allows you to correctly useselect_related,annotate, etc.Motivation and Context
prefetch_related(m2m) functionality becomes like inDjango.How Has This Been Tested?
Added tests to
tests/test_prefetching.pywith verification of receiving data fromannotateandselect_related.Checklist: