/
foreignkey.py
117 lines (101 loc) · 4.67 KB
/
foreignkey.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
from itertools import chain
from astroid import (
MANAGER, nodes, InferenceError, inference_tip,
UseInferenceDefault
)
from astroid.nodes import ClassDef, Attribute
from pylint_django.utils import node_is_subclass
def is_foreignkey_in_class(node):
# is this of the form field = models.ForeignKey
if not isinstance(node.parent, nodes.Assign):
return False
if not isinstance(node.parent.parent, ClassDef):
return False
if isinstance(node.func, Attribute):
attr = node.func.attrname
elif isinstance(node.func, nodes.Name):
attr = node.func.name
else:
return False
return attr in ('OneToOneField', 'ForeignKey')
def _get_model_class_defs_from_module(module, model_name, module_name):
class_defs = []
for module_node in module.lookup(model_name)[1]:
if (isinstance(module_node, nodes.ClassDef)
and node_is_subclass(module_node, 'django.db.models.base.Model')):
class_defs.append(module_node)
elif isinstance(module_node, nodes.ImportFrom):
imported_module = module_node.do_import_module()
class_defs.extend(
_get_model_class_defs_from_module(
imported_module, model_name, module_name
)
)
return class_defs
def infer_key_classes(node, context=None):
keyword_args = []
if node.keywords:
keyword_args = [kw.value for kw in node.keywords if kw.arg == 'to']
all_args = chain(node.args, keyword_args)
for arg in all_args:
# typically the class of the foreign key will
# be the first argument, so we'll go from left to right
if isinstance(arg, (nodes.Name, nodes.Attribute)):
try:
key_cls = None
for inferred in arg.infer(context=context):
key_cls = inferred
break
except InferenceError:
continue
else:
if key_cls is not None:
break
elif isinstance(arg, nodes.Const):
try:
# can be 'self' , 'Model' or 'app.Model'
if arg.value == 'self':
module_name = ''
# for relations with `to` first parent be Keyword(arg='to')
# and we need to go deeper in parent tree to get model name
if isinstance(arg.parent, nodes.Keyword) and arg.parent.arg == 'to':
model_name = arg.parent.parent.parent.parent.name
else:
model_name = arg.parent.parent.parent.name
else:
module_name, _, model_name = arg.value.rpartition('.')
except AttributeError:
break
# when ForeignKey is specified only by class name we assume that
# this class must be found in the current module
if not module_name:
current_module = node.frame()
while not isinstance(current_module, nodes.Module):
current_module = current_module.parent.frame()
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'
# 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)
# create list from dict_values, because it may be modified in a loop
for module in list(MANAGER.astroid_cache.values()):
# only load model classes from modules which match the module in
# which *we think* they are defined. This will prevent infering
# other models of the same name which are found elsewhere!
if model_name in module.locals and module.name.endswith(module_name):
class_defs = _get_model_class_defs_from_module(
module, model_name, module_name
)
if class_defs:
return iter([class_defs[0].instantiate_class()])
else:
raise UseInferenceDefault
return iter([key_cls.instantiate_class()])
def add_transform(manager):
manager.register_transform(nodes.Call, inference_tip(infer_key_classes),
is_foreignkey_in_class)