Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle ForeignKey String References #272

Merged
merged 12 commits into from Jul 21, 2020
11 changes: 11 additions & 0 deletions pylint_django/tests/input/func_noerror_string_foreignkey.py
@@ -0,0 +1,11 @@
"""
Checks that PyLint correctly handles string foreign keys
https://github.com/PyCQA/pylint-django/issues/243
"""
# pylint: disable=missing-docstring
from django.db import models


class Book(models.Model):
author = models.ForeignKey("test_app.Author", models.CASCADE)
atodorov marked this conversation as resolved.
Show resolved Hide resolved
user = models.ForeignKey("auth.User", models.PROTECT)
3 changes: 2 additions & 1 deletion pylint_django/tests/input/models/author.py
Expand Up @@ -3,4 +3,5 @@


class Author(models.Model):
pass
class Meta:
app_label = 'test_app'
Empty file.
5 changes: 5 additions & 0 deletions pylint_django/tests/input/test_app/apps.py
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class TestAppConfig(AppConfig):
name = 'test_app'
1 change: 1 addition & 0 deletions pylint_django/tests/input/test_app/models.py
@@ -0,0 +1 @@
from models.author import Author # noqa: F401
12 changes: 12 additions & 0 deletions pylint_django/tests/settings.py
@@ -0,0 +1,12 @@
SECRET_KEY = 'fake-key'

INSTALLED_APPS = [
'django.contrib.auth',
atodorov marked this conversation as resolved.
Show resolved Hide resolved
'django.contrib.contenttypes',
'test_app',
]

MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
]
2 changes: 2 additions & 0 deletions pylint_django/tests/test_func.py
Expand Up @@ -6,6 +6,8 @@
import pylint


os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pylint_django.tests.settings')

atodorov marked this conversation as resolved.
Show resolved Hide resolved
try:
# pylint 2.5: test_functional has been moved to pylint.testutils
from pylint.testutils import FunctionalTestFile, LintModuleTest
Expand Down
24 changes: 20 additions & 4 deletions pylint_django/transforms/foreignkey.py
Expand Up @@ -41,6 +41,17 @@ def _get_model_class_defs_from_module(module, model_name, module_name):
return class_defs


def _module_name_from_django_model_resolution(model_name, module_name):
import django # pylint: disable=import-outside-toplevel
alejandro-angulo marked this conversation as resolved.
Show resolved Hide resolved
django.setup()
from django.apps import apps # pylint: disable=import-outside-toplevel
alejandro-angulo marked this conversation as resolved.
Show resolved Hide resolved
atodorov marked this conversation as resolved.
Show resolved Hide resolved

app = apps.get_app_config(module_name)
model = app.get_model(model_name)

return model.__module__


def infer_key_classes(node, context=None):
keyword_args = []
if node.keywords:
Expand Down Expand Up @@ -87,10 +98,15 @@ def infer_key_classes(node, context=None):
module_name = current_module.name
elif not module_name.endswith('models'):
# otherwise Django allows specifying an app name first, e.g.
# ForeignKey('auth.User') so we try to convert that to
# 'auth.models', 'User' which works nicely with the `endswith()`
# comparison below
module_name += '.models'
# ForeignKey('auth.User')
try:
module_name = _module_name_from_django_model_resolution(model_name, module_name)
except LookupError:
# If Django's model resolution fails we try to convert that to
# 'auth.models', 'User' which works nicely with the `endswith()`
# comparison below
module_name += '.models'

# ensure that module is loaded in astroid_cache, for cases when models is a package
if module_name not in MANAGER.astroid_cache:
MANAGER.ast_from_module_name(module_name)
Expand Down