Skip to content

Commit ec4f005

Browse files
Rely on Django to resolve string references in ForeignKey fields. Refs #243
this commit tries to load & configure Django and use its internal machinery to resolve apps and models in case they are referenced as a string. If this fails it falls back to appending ".models" to the first part of the name and looking for a module with that name to augment.
1 parent 6df2eeb commit ec4f005

File tree

8 files changed

+53
-5
lines changed

8 files changed

+53
-5
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
"""
2+
Checks that PyLint correctly handles string foreign keys
3+
https://github.com/PyCQA/pylint-django/issues/243
4+
"""
5+
# pylint: disable=missing-docstring
6+
from django.db import models
7+
8+
9+
class Book(models.Model):
10+
author = models.ForeignKey("test_app.Author", models.CASCADE)
11+
user = models.ForeignKey("auth.User", models.PROTECT)

pylint_django/tests/input/models/author.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33

44

55
class Author(models.Model):
6-
pass
6+
class Meta:
7+
app_label = 'test_app'

pylint_django/tests/input/test_app/__init__.py

Whitespace-only changes.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from django.apps import AppConfig
2+
3+
4+
class TestAppConfig(AppConfig):
5+
name = 'test_app'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from models.author import Author # noqa: F401

pylint_django/tests/settings.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
SECRET_KEY = 'fake-key'
2+
3+
INSTALLED_APPS = [
4+
'django.contrib.auth',
5+
'django.contrib.contenttypes',
6+
'test_app',
7+
]
8+
9+
MIDDLEWARE = [
10+
'django.contrib.sessions.middleware.SessionMiddleware',
11+
'django.contrib.auth.middleware.AuthenticationMiddleware',
12+
]

pylint_django/tests/test_func.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import pylint
77

88

9+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pylint_django.tests.settings')
10+
911
try:
1012
# pylint 2.5: test_functional has been moved to pylint.testutils
1113
from pylint.testutils import FunctionalTestFile, LintModuleTest

pylint_django/transforms/foreignkey.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,17 @@ def _get_model_class_defs_from_module(module, model_name, module_name):
4141
return class_defs
4242

4343

44+
def _module_name_from_django_model_resolution(model_name, module_name):
45+
import django # pylint: disable=import-outside-toplevel
46+
django.setup()
47+
from django.apps import apps # pylint: disable=import-outside-toplevel
48+
49+
app = apps.get_app_config(module_name)
50+
model = app.get_model(model_name)
51+
52+
return model.__module__
53+
54+
4455
def infer_key_classes(node, context=None):
4556
keyword_args = []
4657
if node.keywords:
@@ -87,10 +98,15 @@ def infer_key_classes(node, context=None):
8798
module_name = current_module.name
8899
elif not module_name.endswith('models'):
89100
# otherwise Django allows specifying an app name first, e.g.
90-
# ForeignKey('auth.User') so we try to convert that to
91-
# 'auth.models', 'User' which works nicely with the `endswith()`
92-
# comparison below
93-
module_name += '.models'
101+
# ForeignKey('auth.User')
102+
try:
103+
module_name = _module_name_from_django_model_resolution(model_name, module_name)
104+
except LookupError:
105+
# If Django's model resolution fails we try to convert that to
106+
# 'auth.models', 'User' which works nicely with the `endswith()`
107+
# comparison below
108+
module_name += '.models'
109+
94110
# ensure that module is loaded in astroid_cache, for cases when models is a package
95111
if module_name not in MANAGER.astroid_cache:
96112
MANAGER.ast_from_module_name(module_name)

0 commit comments

Comments
 (0)