-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
175 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
"""Python toolbox of Ambient Digital containing an abundance of useful tools and gadgets.""" | ||
|
||
__version__ = "11.4.0" | ||
__version__ = "11.5.0" |
41 changes: 41 additions & 0 deletions
41
ambient_toolbox/system_checks/model_relation_conventions.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
from django.apps import apps | ||
from django.conf import settings | ||
from django.core import checks | ||
from django.db.models import ForeignKey, ManyToManyField, OneToOneField | ||
|
||
|
||
def check_model_related_names_for_related_name(*args, **kwargs): | ||
""" | ||
Checks all model relation fields ('ForeignKey', 'ManyToManyField', "OneToOneField") for having a defined | ||
"related_name". Either directly via the field or otherwise via the model meta-option "default_related_name". | ||
""" | ||
|
||
project_apps = [ | ||
app.split(".")[-1] for app in settings.INSTALLED_APPS if app.startswith(settings.ROOT_URLCONF.split(".")[0]) | ||
] | ||
issue_list = [] | ||
|
||
# Iterate all registered models... | ||
for model in apps.get_models(): | ||
# Check if the model is from your project... | ||
if model._meta.app_label in project_apps: | ||
# If the model has a related name, this will be inherited to all relation fields and we're OK | ||
if model._meta.default_related_name: | ||
continue | ||
|
||
# Iterate over all fields... | ||
for field in model._meta.get_fields(): | ||
# Check relation field types... | ||
if isinstance(field, (ForeignKey, ManyToManyField, OneToOneField)): | ||
# Check if the field has a related name set... | ||
if not field._related_name: | ||
issue_list.append( | ||
checks.Warning( | ||
f"'{model.__name__}.{field.name}' doesn't have a related name set and neither does the " | ||
"model define a default related name.", | ||
obj=field, | ||
id="ambient_toolbox.W003", | ||
) | ||
) | ||
|
||
return issue_list |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
61 changes: 61 additions & 0 deletions
61
testapp/migrations/0002_modelwithoutrelatednameonfieldandmeta_and_more.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
# Generated by Django 5.0.7 on 2024-09-29 06:46 | ||
|
||
import django.db.models.deletion | ||
from django.conf import settings | ||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
("testapp", "0001_initial"), | ||
migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name="ModelWithoutRelatedNameOnFieldAndMeta", | ||
fields=[ | ||
( | ||
"id", | ||
models.AutoField( | ||
auto_created=True, | ||
primary_key=True, | ||
serialize=False, | ||
verbose_name="ID", | ||
), | ||
), | ||
( | ||
"relation_field", | ||
models.ForeignKey( | ||
on_delete=django.db.models.deletion.CASCADE, | ||
to=settings.AUTH_USER_MODEL, | ||
), | ||
), | ||
], | ||
), | ||
migrations.CreateModel( | ||
name="ModelWithoutRelatedNameOnFieldButWithMeta", | ||
fields=[ | ||
( | ||
"id", | ||
models.AutoField( | ||
auto_created=True, | ||
primary_key=True, | ||
serialize=False, | ||
verbose_name="ID", | ||
), | ||
), | ||
( | ||
"relation_field", | ||
models.ForeignKey( | ||
on_delete=django.db.models.deletion.CASCADE, | ||
to=settings.AUTH_USER_MODEL, | ||
), | ||
), | ||
], | ||
options={ | ||
"default_related_name": "model_without_related_name_on_field_but_with_metas", | ||
}, | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
from django.core import checks | ||
from django.test import SimpleTestCase | ||
|
||
from ambient_toolbox.system_checks.model_relation_conventions import check_model_related_names_for_related_name | ||
from testapp.models import ModelWithoutRelatedNameOnFieldAndMeta | ||
|
||
|
||
class CheckModelRelationConventionsTestCase(SimpleTestCase): | ||
def test_check_model_related_names_for_related_name_regular(self): | ||
# Create expected warning | ||
relation_warning = checks.Warning( | ||
"'ModelWithoutRelatedNameOnFieldAndMeta.relation_field' doesn't have a related name set and neither " | ||
"does the model define a default related name.", | ||
obj=ModelWithoutRelatedNameOnFieldAndMeta.relation_field.field, | ||
id="ambient_toolbox.W003", | ||
) | ||
|
||
# Call system check | ||
error_list = check_model_related_names_for_related_name() | ||
|
||
# Assert warngins | ||
self.assertEqual(len(error_list), 1) | ||
self.assertIn(relation_warning, error_list) |