Add warning if RunPython model variable name not the same as model class

This commit is contained in:
David-Wobrock authored and David Wobrock committed 2020-11-26 19:02:57 +01:00
1 parent 601e02e024
commit 780630131f
4 files changed
+169 -1

No files matched your search

+1
View File
@@ -2,6 +2,7 @@
* Add Python 3.9 support * Add Python 3.9 support
* Make data migration model import error less strict (issue #121) * Make data migration model import error less strict (issue #121)
* Add warning detection on RunPython call when model variable name is not the same as model class name
Switched from Travis CI to GitHub Actions for tests Switched from Travis CI to GitHub Actions for tests
@@ -456,6 +456,25 @@ class MigrationLinter(object):
ignored.append(issue) ignored.append(issue)
else: else:
error.append(issue) error.append(issue)
# Detect warning if model variable name is not the same as model class
issues = self.get_runpython_model_variable_naming_issues(runpython.code)
for issue in issues:
if issue and issue["code"] in self.exclude_migration_tests:
ignored.append(issue)
else:
warning.append(issue)
if runpython.reversible:
issues = self.get_runpython_model_variable_naming_issues(
runpython.reverse_code
)
for issue in issues:
if issue and issue["code"] in self.exclude_migration_tests:
ignored.append(issue)
else:
warning.append(issue)
return error, ignored, warning return error, ignored, warning
@staticmethod @staticmethod
@@ -487,3 +506,43 @@ class MigrationLinter(object):
} }
) )
return issues return issues
@staticmethod
def get_runpython_model_variable_naming_issues(code):
model_object_regex = re.compile(r"[^a-zA-Z]?([a-zA-Z0-9]+?)\.objects")
function_name = code.__name__
source_code = inspect.getsource(code)
called_models = model_object_regex.findall(source_code)
issues = []
for model in called_models:
has_same_model_name = (
re.search(
"{model}.*= +\w+?\.get_model\([^\)]+?\.{model}.*?\)".format(
model=model
),
source_code,
re.MULTILINE | re.DOTALL,
)
is not None
or re.search(
"{model}.*= +\w+?\.get_model\([^\)]+?,[^\)]*?{model}.*?\)".format(
model=model
),
source_code,
re.MULTILINE | re.DOTALL,
)
is not None
)
if not has_same_model_name:
issues.append(
{
"code": "DATA_MIGRATION_MODEL_VARIABLE_NAME",
"msg": (
"'{}': Model variable name {} is different from the "
"model class name that was found in the apps.get_model(...) call."
).format(function_name, model),
}
)
return issues
+3 -1
View File
@@ -19,13 +19,14 @@ We are happy to add more if you can specify them to us.
On data migrations, the linter will show a warning when: On data migrations, the linter will show a warning when:
* you are missing a reverse migration * you are missing a reverse migration
* the RunPython arguments do not respect the naming convention `apps, schema_editor` * the RunPython arguments do not respect the naming convention `apps, schema_editor`
* the model variable name is different from the model class name in the `get_model` call
## Codes ## Codes
You can ignore check through the `--exclude-migration-test` option and specifying any of the codes: You can ignore check through the `--exclude-migration-test` option and specifying any of the codes:
| Code | Description | | Code | Description |
|-----------------------------------|----------------------------------------------------------------------| |-------------------------------------|----------------------------------------------------------------------|
|`NOT_NULL` | Not NULL constraint on columns |`NOT_NULL` | Not NULL constraint on columns
|`DROP_COLUMN` | Dropping columns |`DROP_COLUMN` | Dropping columns
|`DROP_TABLE` | Dropping tables |`DROP_TABLE` | Dropping tables
@@ -36,3 +37,4 @@ You can ignore check through the `--exclude-migration-test` option and specifyin
|`REVERSIBLE_DATA_MIGRATION` | RunPython data migration is not reversible (missing reverse code) |`REVERSIBLE_DATA_MIGRATION` | RunPython data migration is not reversible (missing reverse code)
|`NAMING_CONVENTION_RUNPYTHON_ARGS` | By convention, RunPython names two arguments: apps, schema_editor |`NAMING_CONVENTION_RUNPYTHON_ARGS` | By convention, RunPython names two arguments: apps, schema_editor
|`DATA_MIGRATION_MODEL_IMPORT` | Missing apps.get_model() calls for model |`DATA_MIGRATION_MODEL_IMPORT` | Missing apps.get_model() calls for model
|`DATA_MIGRATION_MODEL_VARIABLE_NAME` | The model variable name is different from the model class itself
+106
View File
@@ -133,3 +133,109 @@ class DataMigrationModelImportTestCase(unittest.TestCase):
issues = MigrationLinter.get_runpython_model_import_issues(forward_method) issues = MigrationLinter.get_runpython_model_import_issues(forward_method)
self.assertEqual(0, len(issues)) self.assertEqual(0, len(issues))
class DataMigrationModelVariableNamingTestCase(unittest.TestCase):
def test_same_variable_name(self):
def forward_op(apps, schema_editor):
MyModel = apps.get_model("app", "MyModel")
MyModel.objects.filter(id=1).first()
issues = MigrationLinter.get_runpython_model_variable_naming_issues(forward_op)
self.assertEqual(0, len(issues))
def test_same_variable_name_multiline(self):
def forward_op(apps, schema_editor):
MyModelVeryLongLongLongLongLong = apps.get_model(
"app", "MyModelVeryLongLongLongLongLong"
)
MyModelVeryLongLongLongLongLong.objects.filter(id=1).first()
issues = MigrationLinter.get_runpython_model_variable_naming_issues(forward_op)
self.assertEqual(0, len(issues))
def test_same_variable_name_multiline2(self):
def forward_op(apps, schema_editor):
MyModelVeryLongLongLongLongLong = apps.get_model(
"app_name_longlonglonglongapp",
"MyModelVeryLongLongLongLongLong",
)
MyModelVeryLongLongLongLongLong.objects.filter(id=1).first()
issues = MigrationLinter.get_runpython_model_variable_naming_issues(forward_op)
self.assertEqual(0, len(issues))
def test_different_variable_name(self):
def forward_op(apps, schema_editor):
some_model = apps.get_model("app", "MyModel")
some_model.objects.filter(id=1).first()
issues = MigrationLinter.get_runpython_model_variable_naming_issues(forward_op)
self.assertEqual(1, len(issues))
def test_diff_variable_name_multiline(self):
def forward_op(apps, schema_editor):
MyModelVeryLongLongLongLongLongNot = apps.get_model(
"app", "MyModelVeryLongLongLongLongLong"
)
MyModelVeryLongLongLongLongLongNot.objects.filter(id=1).first()
issues = MigrationLinter.get_runpython_model_variable_naming_issues(forward_op)
self.assertEqual(1, len(issues))
def test_diff_variable_name_multiline2(self):
def forward_op(apps, schema_editor):
MyModelVeryLongLongLongLongLongNot = apps.get_model(
"app_name_longlonglonglongapp",
"MyModelVeryLongLongLongLongLong",
)
MyModelVeryLongLongLongLongLongNot.objects.filter(id=1).first()
issues = MigrationLinter.get_runpython_model_variable_naming_issues(forward_op)
self.assertEqual(1, len(issues))
def test_same_variable_name_one_param(self):
def forward_op(apps, schema_editor):
MyModel = apps.get_model("app.MyModel")
MyModel.objects.filter(id=1).first()
issues = MigrationLinter.get_runpython_model_variable_naming_issues(forward_op)
self.assertEqual(0, len(issues))
def test_different_variable_name_one_param(self):
def forward_op(apps, schema_editor):
mymodel = apps.get_model("app.MyModel")
mymodel.objects.filter(id=1).first()
issues = MigrationLinter.get_runpython_model_variable_naming_issues(forward_op)
self.assertEqual(1, len(issues))
def test_correct_variable_name_one_param_multiline(self):
def forward_op(apps, schema_editor):
AVeryLongModelName = apps.get_model(
"quite_long_app_name.AVeryLongModelName"
)
AVeryLongModelName.objects.filter(id=1).first()
issues = MigrationLinter.get_runpython_model_variable_naming_issues(forward_op)
self.assertEqual(0, len(issues))
def test_different_variable_name_one_param_multiline(self):
def forward_op(apps, schema_editor):
m = apps.get_model(
"quite_long_app_name_name_name.AVeryLongModelNameNameName"
)
m.objects.filter(id=1).first()
issues = MigrationLinter.get_runpython_model_variable_naming_issues(forward_op)
self.assertEqual(1, len(issues))