@@ -114,7 +114,7 @@ def get_generated_manager_info(self, manager_fullname: str, base_manager_fullnam
114
114
# Not a generated manager
115
115
return None
116
116
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 ]:
118
118
"""
119
119
Create a Manager subclass with fallback to Any for unknown attributes
120
120
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)
123
123
The created class is reused if multiple unknown managers are encountered.
124
124
"""
125
125
126
- name = "UnknownManager" if not related_manager else "UnknownRelatedManager"
126
+ name = "UnknownManager"
127
127
128
128
# Check if we've already created a fallback manager class for this
129
129
# module, and if so reuse that.
@@ -134,9 +134,7 @@ def get_or_create_manager_with_any_fallback(self, related_manager: bool = False)
134
134
fallback_queryset = self .get_or_create_queryset_with_any_fallback ()
135
135
if fallback_queryset is None :
136
136
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
140
138
base_manager_info = self .lookup_typeinfo (base_manager_fullname )
141
139
if base_manager_info is None :
142
140
return None
@@ -455,6 +453,10 @@ class AddReverseLookups(ModelClassInitializer):
455
453
def reverse_one_to_one_descriptor (self ) -> TypeInfo :
456
454
return self .lookup_typeinfo_or_incomplete_defn_error (fullnames .REVERSE_ONE_TO_ONE_DESCRIPTOR )
457
455
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
+
458
460
@cached_property
459
461
def many_to_many_descriptor (self ) -> TypeInfo :
460
462
return self .lookup_typeinfo_or_incomplete_defn_error (fullnames .MANY_TO_MANY_DESCRIPTOR )
@@ -466,23 +468,21 @@ def process_relation(self, relation: ForeignObjectRel) -> None:
466
468
# explicitly declared(i.e. non-inferred) reverse accessors alone
467
469
return
468
470
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 )
471
473
472
474
if isinstance (relation , OneToOneRel ):
473
475
self .add_new_node_to_model_class (
474
476
attname ,
475
477
Instance (
476
478
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 , [])],
478
480
),
479
481
)
480
482
return
481
483
482
484
elif isinstance (relation , ManyToManyRel ):
483
485
# 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 )
486
486
assert relation .through is not None
487
487
through_fullname = helpers .get_class_fullname (relation .through )
488
488
through_model_info = self .lookup_typeinfo_or_incomplete_defn_error (through_fullname )
@@ -492,79 +492,65 @@ def process_relation(self, relation: ForeignObjectRel) -> None:
492
492
)
493
493
return
494
494
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 )
531
496
# 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
+ )
538
528
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
+ )
550
532
return
551
533
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 , [])])
552
537
# The reverse manager is based on the related model's manager, so it makes most sense to add the new
553
538
# related manager in that module
554
539
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 ],
558
543
)
559
- new_related_manager_info .metadata ["django" ] = {"related_manager_to_model" : related_model_info .fullname }
560
544
# Stash the new reverse manager type fullname on the related model, so we don't duplicate
561
545
# or have to create it again for other reverse relations
562
546
helpers .set_reverse_manager_info (
563
- related_model_info ,
547
+ to_model_info ,
564
548
derived_from = "_default_manager" ,
565
549
fullname = new_related_manager_info .fullname ,
566
550
)
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
+ )
568
554
569
555
def run_with_model_cls (self , model_cls : Type [Model ]) -> None :
570
556
# add related managers etc.
0 commit comments