@@ -91,6 +91,35 @@ class LookupsAreUnsupported(Exception):
91
91
pass
92
92
93
93
94
+ def _get_field_type_from_model_type_info (info : Optional [TypeInfo ], field_name : str ) -> Optional [Instance ]:
95
+ if info is None :
96
+ return None
97
+ field_node = info .get (field_name )
98
+ if field_node is None or not isinstance (field_node .type , Instance ):
99
+ return None
100
+ # Field declares a set and a get type arg. Fallback to `None` when we can't find any args
101
+ elif len (field_node .type .args ) != 2 :
102
+ return None
103
+ else :
104
+ return field_node .type
105
+
106
+
107
+ def _get_field_set_type_from_model_type_info (info : Optional [TypeInfo ], field_name : str ) -> Optional [MypyType ]:
108
+ field_type = _get_field_type_from_model_type_info (info , field_name )
109
+ if field_type is not None :
110
+ return field_type .args [0 ]
111
+ else :
112
+ return None
113
+
114
+
115
+ def _get_field_get_type_from_model_type_info (info : Optional [TypeInfo ], field_name : str ) -> Optional [MypyType ]:
116
+ field_type = _get_field_type_from_model_type_info (info , field_name )
117
+ if field_type is not None :
118
+ return field_type .args [1 ]
119
+ else :
120
+ return None
121
+
122
+
94
123
class DjangoContext :
95
124
def __init__ (self , django_settings_module : str ) -> None :
96
125
self .django_settings_module = django_settings_module
@@ -152,13 +181,13 @@ def get_field_lookup_exact_type(
152
181
) -> MypyType :
153
182
if isinstance (field , (RelatedField , ForeignObjectRel )):
154
183
related_model_cls = self .get_field_related_model_cls (field )
155
- primary_key_field = self .get_primary_key_field (related_model_cls )
156
- primary_key_type = self .get_field_get_type (api , primary_key_field , method = "init" )
157
-
158
184
rel_model_info = helpers .lookup_class_typeinfo (api , related_model_cls )
159
185
if rel_model_info is None :
160
186
return AnyType (TypeOfAny .explicit )
161
187
188
+ primary_key_field = self .get_primary_key_field (related_model_cls )
189
+ primary_key_type = self .get_field_get_type (api , rel_model_info , primary_key_field , method = "init" )
190
+
162
191
model_and_primary_key_type = UnionType .make_union ([Instance (rel_model_info , []), primary_key_type ])
163
192
return helpers .make_optional (model_and_primary_key_type )
164
193
@@ -200,19 +229,6 @@ def get_expected_types(self, api: TypeChecker, model_cls: Type[Model], *, method
200
229
field_set_type = self .get_field_set_type (api , primary_key_field , method = method )
201
230
expected_types ["pk" ] = field_set_type
202
231
203
- def get_field_set_type_from_model_type_info (info : Optional [TypeInfo ], field_name : str ) -> Optional [MypyType ]:
204
- if info is None :
205
- return None
206
- field_node = info .get (field_name )
207
- if field_node is None or not isinstance (field_node .type , Instance ):
208
- return None
209
- elif not field_node .type .args :
210
- # Field declares a set and a get type arg. Fallback to `None` when we can't find any args
211
- return None
212
-
213
- set_type = field_node .type .args [0 ]
214
- return set_type
215
-
216
232
model_info = helpers .lookup_class_typeinfo (api , model_cls )
217
233
for field in model_cls ._meta .get_fields ():
218
234
if isinstance (field , Field ):
@@ -223,7 +239,7 @@ def get_field_set_type_from_model_type_info(info: Optional[TypeInfo], field_name
223
239
# Try to retrieve set type from a model's TypeInfo object and fallback to retrieving it manually
224
240
# from django-stubs own declaration. This is to align with the setter types declared for
225
241
# assignment.
226
- field_set_type = get_field_set_type_from_model_type_info (
242
+ field_set_type = _get_field_set_type_from_model_type_info (
227
243
model_info , field_name
228
244
) or self .get_field_set_type (api , field , method = method )
229
245
expected_types [field_name ] = field_set_type
@@ -340,20 +356,31 @@ def get_field_set_type(
340
356
return field_set_type
341
357
342
358
def get_field_get_type (
343
- self , api : TypeChecker , field : Union ["Field[Any, Any]" , ForeignObjectRel ], * , method : str
359
+ self ,
360
+ api : TypeChecker ,
361
+ model_info : Optional [TypeInfo ],
362
+ field : Union ["Field[Any, Any]" , ForeignObjectRel ],
363
+ * ,
364
+ method : str ,
344
365
) -> MypyType :
345
366
"""Get a type of __get__ for this specific Django field."""
367
+ if isinstance (field , Field ):
368
+ get_type = _get_field_get_type_from_model_type_info (model_info , field .attname )
369
+ if get_type is not None :
370
+ return get_type
371
+
346
372
field_info = helpers .lookup_class_typeinfo (api , field .__class__ )
347
373
if field_info is None :
348
374
return AnyType (TypeOfAny .unannotated )
349
375
350
376
is_nullable = self .get_field_nullability (field , method )
351
377
if isinstance (field , RelatedField ):
352
378
related_model_cls = self .get_field_related_model_cls (field )
379
+ rel_model_info = helpers .lookup_class_typeinfo (api , related_model_cls )
353
380
354
381
if method in ("values" , "values_list" ):
355
382
primary_key_field = self .get_primary_key_field (related_model_cls )
356
- return self .get_field_get_type (api , primary_key_field , method = method )
383
+ return self .get_field_get_type (api , rel_model_info , primary_key_field , method = method )
357
384
358
385
model_info = helpers .lookup_class_typeinfo (api , related_model_cls )
359
386
if model_info is None :
0 commit comments