From 556e914b204e7694e989ea661a79fae1a63bfa7a Mon Sep 17 00:00:00 2001 From: Petter Friberg Date: Sun, 22 Jan 2023 22:01:30 +0100 Subject: [PATCH] [CHERRY-PICK] Suppress `FieldDoesNotExist` raised from attribute on class definition If we've found an attribute on a model class that from all we can see appears to be a field, but Django still raises `FieldDoesNotExist` we'll suppress it. This can appear if one uses the (undocumented) `Field(name=..., ...)` argument. --- mypy_django_plugin/transformers/fields.py | 7 +++++-- tests/typecheck/fields/test_base.yml | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/mypy_django_plugin/transformers/fields.py b/mypy_django_plugin/transformers/fields.py index 2d01b50391..0e56b6e8d8 100644 --- a/mypy_django_plugin/transformers/fields.py +++ b/mypy_django_plugin/transformers/fields.py @@ -1,5 +1,6 @@ from typing import TYPE_CHECKING, Any, Optional, Tuple, Union, cast +from django.core.exceptions import FieldDoesNotExist from django.db.models.fields import AutoField, Field from django.db.models.fields.related import RelatedField from django.db.models.fields.reverse_related import ForeignObjectRel @@ -38,8 +39,10 @@ def _get_current_field_from_assignment( if model_cls is None: return None - current_field = model_cls._meta.get_field(field_name) - return current_field + try: + return model_cls._meta.get_field(field_name) + except FieldDoesNotExist: + return None def reparametrize_related_field_type(related_field_type: Instance, set_type: MypyType, get_type: MypyType) -> Instance: diff --git a/tests/typecheck/fields/test_base.yml b/tests/typecheck/fields/test_base.yml index 783b869653..4ccddca797 100644 --- a/tests/typecheck/fields/test_base.yml +++ b/tests/typecheck/fields/test_base.yml @@ -180,3 +180,24 @@ obj = MyModel() reveal_type(obj.small) # N: Revealed type is "builtins.int" + +- case: test_ignores_renamed_field + main: | + # Ref: https://github.com/typeddjango/django-stubs/issues/1261 + # Django modifies the model so it doesn't have 'modelname', but we don't follow + # along. But the 'name=' argument to a field isn't a documented feature. + from myapp.models import RenamedField + instance = RenamedField() + reveal_type(instance.modelname) # N: Revealed type is "builtins.int" + instance.fieldname # E: "RenamedField" has no attribute "fieldname" + instance.modelname = 1 + instance.fieldname = 1 # E: "RenamedField" has no attribute "fieldname" + installed_apps: + - myapp + files: + - path: myapp/__init__.py + - path: myapp/models.py + content: | + from django.db import models + class RenamedField(models.Model): + modelname = models.IntegerField(name="fieldname", choices=((1, 'One'),))