@@ -114,7 +114,7 @@ def get_generated_manager_info(self, manager_fullname: str, base_manager_fullnam
114114 # Not a generated manager
115115 return None
116116
117- def get_or_create_manager_with_any_fallback (self , related_manager : bool = False ) -> Optional [TypeInfo ]:
117+ def get_or_create_manager_with_any_fallback (self ) -> Optional [TypeInfo ]:
118118 """
119119 Create a Manager subclass with fallback to Any for unknown attributes
120120 and methods. This is used for unresolved managers, where we don't know
@@ -123,7 +123,7 @@ def get_or_create_manager_with_any_fallback(self, related_manager: bool = False)
123123 The created class is reused if multiple unknown managers are encountered.
124124 """
125125
126- name = "UnknownManager" if not related_manager else "UnknownRelatedManager"
126+ name = "UnknownManager"
127127
128128 # Check if we've already created a fallback manager class for this
129129 # module, and if so reuse that.
@@ -134,9 +134,7 @@ def get_or_create_manager_with_any_fallback(self, related_manager: bool = False)
134134 fallback_queryset = self .get_or_create_queryset_with_any_fallback ()
135135 if fallback_queryset is None :
136136 return None
137- base_manager_fullname = (
138- fullnames .MANAGER_CLASS_FULLNAME if not related_manager else fullnames .RELATED_MANAGER_CLASS
139- )
137+ base_manager_fullname = fullnames .MANAGER_CLASS_FULLNAME
140138 base_manager_info = self .lookup_typeinfo (base_manager_fullname )
141139 if base_manager_info is None :
142140 return None
@@ -455,6 +453,10 @@ class AddReverseLookups(ModelClassInitializer):
455453 def reverse_one_to_one_descriptor (self ) -> TypeInfo :
456454 return self .lookup_typeinfo_or_incomplete_defn_error (fullnames .REVERSE_ONE_TO_ONE_DESCRIPTOR )
457455
456+ @cached_property
457+ def reverse_many_to_one_descriptor (self ) -> TypeInfo :
458+ return self .lookup_typeinfo_or_incomplete_defn_error (fullnames .REVERSE_MANY_TO_ONE_DESCRIPTOR )
459+
458460 @cached_property
459461 def many_to_many_descriptor (self ) -> TypeInfo :
460462 return self .lookup_typeinfo_or_incomplete_defn_error (fullnames .MANY_TO_MANY_DESCRIPTOR )
@@ -466,23 +468,21 @@ def process_relation(self, relation: ForeignObjectRel) -> None:
466468 # explicitly declared(i.e. non-inferred) reverse accessors alone
467469 return
468470
469- related_model_cls = self .django_context .get_field_related_model_cls (relation )
470- related_model_info = self .lookup_class_typeinfo_or_incomplete_defn_error (related_model_cls )
471+ to_model_cls = self .django_context .get_field_related_model_cls (relation )
472+ to_model_info = self .lookup_class_typeinfo_or_incomplete_defn_error (to_model_cls )
471473
472474 if isinstance (relation , OneToOneRel ):
473475 self .add_new_node_to_model_class (
474476 attname ,
475477 Instance (
476478 self .reverse_one_to_one_descriptor ,
477- [Instance (self .model_classdef .info , []), Instance (related_model_info , [])],
479+ [Instance (self .model_classdef .info , []), Instance (to_model_info , [])],
478480 ),
479481 )
480482 return
481483
482484 elif isinstance (relation , ManyToManyRel ):
483485 # TODO: 'relation' should be based on `TypeInfo` instead of Django runtime.
484- to_fullname = helpers .get_class_fullname (relation .remote_field .model )
485- to_model_info = self .lookup_typeinfo_or_incomplete_defn_error (to_fullname )
486486 assert relation .through is not None
487487 through_fullname = helpers .get_class_fullname (relation .through )
488488 through_model_info = self .lookup_typeinfo_or_incomplete_defn_error (through_fullname )
@@ -492,79 +492,65 @@ def process_relation(self, relation: ForeignObjectRel) -> None:
492492 )
493493 return
494494
495- related_manager_info = None
496- try :
497- related_manager_info = self .lookup_typeinfo_or_incomplete_defn_error (fullnames .RELATED_MANAGER_CLASS )
498- default_manager = related_model_info .names .get ("_default_manager" )
499- if not default_manager :
500- raise helpers .IncompleteDefnException ()
501- except helpers .IncompleteDefnException as exc :
502- if not self .api .final_iteration :
503- raise exc
504-
505- # If a django model has a Manager class that cannot be
506- # resolved statically (if it is generated in a way where we
507- # cannot import it, like `objects = my_manager_factory()`),
508- # we fallback to the default related manager, so you at
509- # least get a base level of working type checking.
510- #
511- # See https://github.com/typeddjango/django-stubs/pull/993
512- # for more information on when this error can occur.
513- fallback_manager = self .get_or_create_manager_with_any_fallback (related_manager = True )
514- if fallback_manager is not None :
515- self .add_new_node_to_model_class (
516- attname , Instance (fallback_manager , [Instance (related_model_info , [])])
517- )
518- related_model_fullname = related_model_cls .__module__ + "." + related_model_cls .__name__
519- self .ctx .api .fail (
520- (
521- "Couldn't resolve related manager for relation "
522- f"{ relation .name !r} (from { related_model_fullname } ."
523- f"{ relation .field } )."
524- ),
525- self .ctx .cls ,
526- code = MANAGER_MISSING ,
527- )
528- return
529-
530- # Check if the related model has a related manager subclassed from the default manager
495+ related_manager_info = self .lookup_typeinfo_or_incomplete_defn_error (fullnames .RELATED_MANAGER_CLASS )
531496 # TODO: Support other reverse managers than `_default_manager`
532- default_reverse_manager_info = helpers .get_reverse_manager_info (
533- self .api , model_info = related_model_info , derived_from = "_default_manager"
534- )
535- if default_reverse_manager_info :
536- self .add_new_node_to_model_class (attname , Instance (default_reverse_manager_info , []))
537- return
497+ default_manager = to_model_info .names .get ("_default_manager" )
498+ if default_manager is None and not self .api .final_iteration :
499+ raise helpers .IncompleteDefnException ()
500+ elif (
501+ # When we get no default manager we can't customize the reverse manager any
502+ # further and will just fall back to the manager declared on the descriptor
503+ default_manager is None
504+ # '_default_manager' attribute is a node type we can't process
505+ or not isinstance (default_manager .type , Instance )
506+ # Already has a related manager subclassed from the default manager
507+ or helpers .get_reverse_manager_info (self .api , model_info = to_model_info , derived_from = "_default_manager" )
508+ is not None
509+ # When the default manager isn't custom there's no need to create a new type
510+ # as `RelatedManager` has `models.Manager` as base
511+ or default_manager .type .type .fullname == fullnames .MANAGER_CLASS_FULLNAME
512+ ):
513+ if default_manager is None and self .api .final_iteration :
514+ # If a django model has a Manager class that cannot be
515+ # resolved statically (if it is generated in a way where we
516+ # cannot import it, like `objects = my_manager_factory()`),
517+ #
518+ # See https://github.com/typeddjango/django-stubs/pull/993
519+ # for more information on when this error can occur.
520+ self .ctx .api .fail (
521+ (
522+ f"Couldn't resolve related manager { attname !r} for relation "
523+ f"'{ to_model_info .fullname } .{ relation .field .name } '."
524+ ),
525+ self .ctx .cls ,
526+ code = MANAGER_MISSING ,
527+ )
538528
539- # The reverse manager we're looking for doesn't exist. So we
540- # create it. The (default) reverse manager type is built from a
541- # RelatedManager and the default manager on the related model
542- parametrized_related_manager_type = Instance (related_manager_info , [Instance (related_model_info , [])])
543- default_manager_type = default_manager .type
544- assert default_manager_type is not None
545- assert isinstance (default_manager_type , Instance )
546- # When the default manager isn't custom there's no need to create a new type
547- # as `RelatedManager` has `models.Manager` as base
548- if default_manager_type .type .fullname == fullnames .MANAGER_CLASS_FULLNAME :
549- self .add_new_node_to_model_class (attname , parametrized_related_manager_type )
529+ self .add_new_node_to_model_class (
530+ attname , Instance (self .reverse_many_to_one_descriptor , [Instance (to_model_info , [])])
531+ )
550532 return
551533
534+ # Create a reverse manager subclassed from the default manager of the related
535+ # model and 'RelatedManager'
536+ related_manager = Instance (related_manager_info , [Instance (to_model_info , [])])
552537 # The reverse manager is based on the related model's manager, so it makes most sense to add the new
553538 # related manager in that module
554539 new_related_manager_info = helpers .add_new_class_for_module (
555- module = self .api .modules [related_model_info .module_name ],
556- name = f"{ related_model_cls . __name__ } _RelatedManager" ,
557- bases = [parametrized_related_manager_type , default_manager_type ],
540+ module = self .api .modules [to_model_info .module_name ],
541+ name = f"{ to_model_info . name } _RelatedManager" ,
542+ bases = [related_manager , default_manager . type ],
558543 )
559- new_related_manager_info .metadata ["django" ] = {"related_manager_to_model" : related_model_info .fullname }
560544 # Stash the new reverse manager type fullname on the related model, so we don't duplicate
561545 # or have to create it again for other reverse relations
562546 helpers .set_reverse_manager_info (
563- related_model_info ,
547+ to_model_info ,
564548 derived_from = "_default_manager" ,
565549 fullname = new_related_manager_info .fullname ,
566550 )
567- self .add_new_node_to_model_class (attname , Instance (new_related_manager_info , []))
551+ self .add_new_node_to_model_class (
552+ attname , Instance (self .reverse_many_to_one_descriptor , [Instance (to_model_info , [])])
553+ )
568554
569555 def run_with_model_cls (self , model_cls : Type [Model ]) -> None :
570556 # add related managers etc.
0 commit comments