14
14
from pylint .checkers import utils
15
15
from pylint_django .__pkginfo__ import BASE_ID
16
16
from pylint_django import compat
17
+ from pylint_django .utils import is_migrations_module
17
18
18
19
19
20
def _is_addfield_with_default (call ):
@@ -37,13 +38,6 @@ def _is_addfield_with_default(call):
37
38
return False
38
39
39
40
40
- def _is_migrations_module (node ):
41
- if not isinstance (node , astroid .Module ):
42
- return False
43
-
44
- return 'migrations' in node .path [0 ] and not node .path [0 ].endswith ('__init__.py' )
45
-
46
-
47
41
class NewDbFieldWithDefaultChecker (checkers .BaseChecker ):
48
42
"""
49
43
Looks for migrations which add new model fields and these fields have a
@@ -69,7 +63,7 @@ class NewDbFieldWithDefaultChecker(checkers.BaseChecker):
69
63
_possible_offences = {}
70
64
71
65
def visit_module (self , node ):
72
- if _is_migrations_module (node ):
66
+ if is_migrations_module (node ):
73
67
self ._migration_modules .append (node )
74
68
75
69
def visit_call (self , node ):
@@ -78,7 +72,7 @@ def visit_call(self, node):
78
72
except : # noqa: E722, pylint: disable=bare-except
79
73
return
80
74
81
- if not _is_migrations_module (module ):
75
+ if not is_migrations_module (module ):
82
76
return
83
77
84
78
if _is_addfield_with_default (node ):
@@ -114,6 +108,38 @@ def _path(node):
114
108
self .add_message ('new-db-field-with-default' , args = module .name , node = node )
115
109
116
110
111
+ class MissingBackwardsMigrationChecker (checkers .BaseChecker ):
112
+ __implements__ = (interfaces .IAstroidChecker ,)
113
+
114
+ name = 'missing-backwards-migration-callable'
115
+
116
+ msgs = {'W%s05' % BASE_ID : ('%s Always include backwards migration callable' ,
117
+ 'missing-backwards-migration-callable' ,
118
+ 'Always include a backwards/reverse callable counterpart'
119
+ ' so that the migration is not irreversable.' )}
120
+
121
+ @utils .check_messages ('missing-backwards-migration-callable' )
122
+ def visit_call (self , node ):
123
+ try :
124
+ module = node .frame ().parent
125
+ except : # noqa: E722, pylint: disable=bare-except
126
+ return
127
+
128
+ if not is_migrations_module (module ):
129
+ return
130
+
131
+ if node .func .as_string ().endswith ('RunPython' ) and len (node .args ) < 2 :
132
+ if node .keywords :
133
+ for keyword in node .keywords :
134
+ if keyword .arg == 'reverse_code' :
135
+ return
136
+ self .add_message ('missing-backwards-migration-callable' ,
137
+ args = module .name , node = node )
138
+ else :
139
+ self .add_message ('missing-backwards-migration-callable' ,
140
+ args = module .name , node = node )
141
+
142
+
117
143
def load_configuration (linter ):
118
144
# don't blacklist migrations for this checker
119
145
new_black_list = list (linter .config .black_list )
@@ -125,5 +151,6 @@ def load_configuration(linter):
125
151
def register (linter ):
126
152
"""Required method to auto register this checker."""
127
153
linter .register_checker (NewDbFieldWithDefaultChecker (linter ))
154
+ linter .register_checker (MissingBackwardsMigrationChecker (linter ))
128
155
if not compat .LOAD_CONFIGURATION_SUPPORTED :
129
156
load_configuration (linter )
0 commit comments