33
44from django .db .models import Manager , Model
55from django .db .models .fields import DateField , DateTimeField , Field
6- from django .db .models .fields .reverse_related import ForeignObjectRel , OneToOneRel
6+ from django .db .models .fields .reverse_related import ManyToManyRel , OneToOneRel
77from mypy .checker import TypeChecker
88from mypy .nodes import (
99 ARG_STAR2 ,
@@ -448,23 +448,15 @@ def run_with_model_cls(self, model_cls: Type[Model]) -> None:
448448
449449
450450class AddReverseLookups (ModelClassInitializer ):
451- def get_reverse_manager_info (self , model_info : TypeInfo , derived_from : str ) -> Optional [TypeInfo ]:
452- manager_fullname = helpers .get_django_metadata (model_info ).get ("reverse_managers" , {}).get (derived_from )
453- if not manager_fullname :
454- return None
455-
456- symbol = self .api .lookup_fully_qualified_or_none (manager_fullname )
457- if symbol is None or not isinstance (symbol .node , TypeInfo ):
458- return None
459- return symbol .node
451+ @cached_property
452+ def reverse_one_to_one_descriptor (self ) -> TypeInfo :
453+ return self .lookup_typeinfo_or_incomplete_defn_error (fullnames .REVERSE_ONE_TO_ONE_DESCRIPTOR )
460454
461- def set_reverse_manager_info (self , model_info : TypeInfo , derived_from : str , fullname : str ) -> None :
462- helpers .get_django_metadata (model_info ).setdefault ("reverse_managers" , {})[derived_from ] = fullname
455+ @cached_property
456+ def many_to_many_descriptor (self ) -> TypeInfo :
457+ return self .lookup_typeinfo_or_incomplete_defn_error (fullnames .MANY_TO_MANY_DESCRIPTOR )
463458
464459 def run_with_model_cls (self , model_cls : Type [Model ]) -> None :
465- reverse_one_to_one_descriptor = self .lookup_typeinfo_or_incomplete_defn_error (
466- fullnames .REVERSE_ONE_TO_ONE_DESCRIPTOR
467- )
468460 # add related managers
469461 for relation in self .django_context .get_model_relations (model_cls ):
470462 attname = relation .get_accessor_name ()
@@ -487,13 +479,27 @@ def run_with_model_cls(self, model_cls: Type[Model]) -> None:
487479 self .add_new_node_to_model_class (
488480 attname ,
489481 Instance (
490- reverse_one_to_one_descriptor ,
482+ self . reverse_one_to_one_descriptor ,
491483 [Instance (self .model_classdef .info , []), Instance (related_model_info , [])],
492484 ),
493485 )
494486 continue
495487
496- if isinstance (relation , ForeignObjectRel ):
488+ elif isinstance (relation , ManyToManyRel ):
489+ # TODO: 'relation' should be based on `TypeInfo` instead of Django runtime.
490+ to_fullname = helpers .get_class_fullname (relation .remote_field .model )
491+ to_model_info = self .lookup_typeinfo_or_incomplete_defn_error (to_fullname )
492+ assert relation .through is not None
493+ through_fullname = helpers .get_class_fullname (relation .through )
494+ through_model_info = self .lookup_typeinfo_or_incomplete_defn_error (through_fullname )
495+ self .add_new_node_to_model_class (
496+ attname ,
497+ Instance (
498+ self .many_to_many_descriptor , [Instance (to_model_info , []), Instance (through_model_info , [])]
499+ ),
500+ )
501+
502+ else :
497503 related_manager_info = None
498504 try :
499505 related_manager_info = self .lookup_typeinfo_or_incomplete_defn_error (
@@ -534,8 +540,8 @@ def run_with_model_cls(self, model_cls: Type[Model]) -> None:
534540
535541 # Check if the related model has a related manager subclassed from the default manager
536542 # TODO: Support other reverse managers than `_default_manager`
537- default_reverse_manager_info = self .get_reverse_manager_info (
538- model_info = related_model_info , derived_from = "_default_manager"
543+ default_reverse_manager_info = helpers .get_reverse_manager_info (
544+ self . api , model_info = related_model_info , derived_from = "_default_manager"
539545 )
540546 if default_reverse_manager_info :
541547 self .add_new_node_to_model_class (attname , Instance (default_reverse_manager_info , []))
@@ -564,7 +570,7 @@ def run_with_model_cls(self, model_cls: Type[Model]) -> None:
564570 new_related_manager_info .metadata ["django" ] = {"related_manager_to_model" : related_model_info .fullname }
565571 # Stash the new reverse manager type fullname on the related model, so we don't duplicate
566572 # or have to create it again for other reverse relations
567- self .set_reverse_manager_info (
573+ helpers .set_reverse_manager_info (
568574 related_model_info ,
569575 derived_from = "_default_manager" ,
570576 fullname = new_related_manager_info .fullname ,
0 commit comments