diff --git a/mypy_django_plugin/django/context.py b/mypy_django_plugin/django/context.py index a2d9ee28f3..4e303022eb 100644 --- a/mypy_django_plugin/django/context.py +++ b/mypy_django_plugin/django/context.py @@ -129,6 +129,8 @@ def get_field_lookup_exact_type( ) -> MypyType: if isinstance(field, (RelatedField, ForeignObjectRel)): related_model_cls = self.get_field_related_model_cls(field) + if related_model_cls is None: + return AnyType(TypeOfAny.from_error) primary_key_field = self.get_primary_key_field(related_model_cls) primary_key_type = self.get_field_get_type(api, primary_key_field, method="init") @@ -354,7 +356,7 @@ def get_field_related_model_cls( def _resolve_field_from_parts( self, field_parts: Iterable[str], model_cls: Type[Model] - ) -> Union["Field[Any, Any]", ForeignObjectRel]: + ) -> Union["Field[Any, Any]", ForeignObjectRel, None]: currently_observed_model = model_cls field: Union["Field[Any, Any]", ForeignObjectRel, GenericForeignKey, None] = None for field_part in field_parts: @@ -364,13 +366,19 @@ def _resolve_field_from_parts( field = currently_observed_model._meta.get_field(field_part) if isinstance(field, RelatedField): - currently_observed_model = self.get_field_related_model_cls(field) + related_model = self.get_field_related_model_cls(field) + if related_model is None: + return None + currently_observed_model = related_model model_name = currently_observed_model._meta.model_name if model_name is not None and field_part == (model_name + "_id"): field = self.get_primary_key_field(currently_observed_model) if isinstance(field, ForeignObjectRel): - currently_observed_model = self.get_field_related_model_cls(field) + related_model = self.get_field_related_model_cls(field) + if related_model is None: + return None + currently_observed_model = related_model # Guaranteed by `query.solve_lookup_type` before. assert isinstance(field, (Field, ForeignObjectRel)) @@ -398,9 +406,18 @@ def solve_lookup_type( field = query.get_meta().get_field(query_parts[0]) except FieldDoesNotExist: return None + if len(query_parts) == 1: return [], [query_parts[0]], False - sub_query = Query(field.related_model).solve_lookup_type("__") + + related_model = None + if isinstance(field, (RelatedField, ForeignObjectRel)): + related_model = self.get_field_related_model_cls(field) + + if related_model is None: + return None + + sub_query = Query(related_model).solve_lookup_type("__".join(query_parts[1:])) entire_query_parts = [query_parts[0], *sub_query[1]] return sub_query[0], entire_query_parts, sub_query[2] @@ -429,6 +446,8 @@ def resolve_lookup_expected_type(self, ctx: MethodContext, model_cls: Type[Model return AnyType(TypeOfAny.explicit) field = self._resolve_field_from_parts(field_parts, model_cls) + if field is None: + return AnyType(TypeOfAny.from_error) lookup_cls = None if lookup_parts: diff --git a/tests/typecheck/fields/test_related.yml b/tests/typecheck/fields/test_related.yml index 30f6d7dd1e..e7c94e3553 100644 --- a/tests/typecheck/fields/test_related.yml +++ b/tests/typecheck/fields/test_related.yml @@ -932,6 +932,7 @@ - case: test_fails_if_app_label_is_unknown_in_relation_field main: | from installed.models import InstalledModel + InstalledModel.objects.filter(non_installed__isnull=True) installed_apps: - installed files: