Skip to content

Commit 2a6f464

Browse files
authored
Populate model argument for dynamically created managers (#1033)
* Populate model for dynamically created managers * fixup! Populate model for dynamically created managers
1 parent 84eff75 commit 2a6f464

File tree

2 files changed

+59
-11
lines changed

2 files changed

+59
-11
lines changed

mypy_django_plugin/transformers/models.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -243,26 +243,25 @@ def run_with_model_cls(self, model_cls: Type[Model]) -> None:
243243
except helpers.IncompleteDefnException as exc:
244244
# Check if manager is a generated (dynamic class) manager
245245
base_manager_fullname = helpers.get_class_fullname(manager.__class__.__bases__[0])
246-
if manager_fullname not in self.get_generated_manager_mappings(base_manager_fullname):
246+
generated_managers = self.get_generated_manager_mappings(base_manager_fullname)
247+
if manager_fullname not in generated_managers:
247248
# Manager doesn't appear to be generated. Track that we encountered an
248249
# incomplete definition and skip
249250
incomplete_manager_defs.add(manager_name)
250-
continue
251+
continue
252+
253+
manager_info = self.lookup_typeinfo(generated_managers[manager_fullname])
254+
if manager_info is None:
255+
continue
251256

252-
if manager_name not in self.model_classdef.info.names:
257+
is_dynamically_generated = manager_info.metadata.get("django", {}).get("from_queryset_manager") is not None
258+
if manager_name not in self.model_classdef.info.names or is_dynamically_generated:
253259
manager_type = Instance(manager_info, [Instance(self.model_classdef.info, [])])
254260
self.add_new_node_to_model_class(manager_name, manager_type)
255-
else:
261+
elif self.has_any_parametrized_manager_as_base(manager_info):
256262
# Ending up here could for instance be due to having a custom _Manager_
257263
# that is not built from a custom QuerySet. Another example is a
258264
# related manager.
259-
# Don't interfere with dynamically generated manager classes
260-
is_dynamically_generated = "django" in manager_info.metadata and manager_info.metadata["django"].get(
261-
"from_queryset_manager"
262-
)
263-
if not self.has_any_parametrized_manager_as_base(manager_info) or is_dynamically_generated:
264-
continue
265-
266265
custom_model_manager_name = manager.model.__name__ + "_" + manager_class_name
267266
try:
268267
custom_manager_type = self.create_new_model_parametrized_manager(

tests/typecheck/managers/querysets/test_from_queryset.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,55 @@
7171
class MyModel(models.Model):
7272
objects = NewManager()
7373
74+
- case: from_queryset_generated_manager_imported_from_other_module
75+
main: |
76+
from myapp.models import MyModel
77+
reveal_type(MyModel.objects) # N: Revealed type is "myapp.querysets.NewManager[myapp.models.MyModel]"
78+
reveal_type(MyModel.objects.get()) # N: Revealed type is "myapp.models.MyModel"
79+
reveal_type(MyModel.objects.queryset_method()) # N: Revealed type is "myapp.querysets.ModelQuerySet"
80+
reveal_type(MyModel.objects.queryset_method_2()) # N: Revealed type is "typing.Iterable[myapp.querysets.Custom]"
81+
reveal_type(MyModel.objects.queryset_method_3()) # N: Revealed type is "builtins.str"
82+
reveal_type(MyModel.objects.queryset_method_4([])) # N: Revealed type is "None"
83+
reveal_type(MyModel.objects.filter(id=1).queryset_method()) # N: Revealed type is "myapp.querysets.ModelQuerySet"
84+
reveal_type(MyModel.objects.filter(id=1)) # N: Revealed type is "myapp.querysets.ModelQuerySet[myapp.models.MyModel]"
85+
installed_apps:
86+
- myapp
87+
files:
88+
- path: myapp/__init__.py
89+
- path: myapp/querysets.py
90+
content: |
91+
from typing import TYPE_CHECKING, Iterable, Sequence
92+
from django.db import models
93+
from django.db.models.manager import BaseManager
94+
if TYPE_CHECKING:
95+
from .models import MyModel
96+
97+
class Custom:
98+
...
99+
100+
class ModelQuerySet(models.QuerySet["MyModel"]):
101+
def queryset_method(self) -> "ModelQuerySet":
102+
return self.filter()
103+
104+
def queryset_method_2(self) -> Iterable[Custom]:
105+
return []
106+
107+
def queryset_method_3(self) -> str:
108+
return 'hello'
109+
110+
def queryset_method_4(self, arg: Sequence) -> None:
111+
return None
112+
113+
NewManager = BaseManager.from_queryset(ModelQuerySet)
114+
115+
- path: myapp/models.py
116+
content: |
117+
from django.db import models
118+
from .querysets import NewManager
119+
120+
class MyModel(models.Model):
121+
objects = NewManager()
122+
74123
- case: from_queryset_with_manager
75124
main: |
76125
from myapp.models import MyModel

0 commit comments

Comments
 (0)