-
Notifications
You must be signed in to change notification settings - Fork 5
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
10 changed files
with
173 additions
and
2 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.3.0" | ||
__version__ = "11.4.0" |
Empty file.
56 changes: 56 additions & 0 deletions
56
ambient_toolbox/system_checks/model_field_name_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,56 @@ | ||
from django.apps import apps | ||
from django.conf import settings | ||
from django.core import checks | ||
from django.db.models import DateField, DateTimeField | ||
|
||
|
||
def check_model_time_based_fields(*args, **kwargs): | ||
""" | ||
Checks all model time fields ('DateField', 'DateTimeField') for a "correct" ending in their name. | ||
Inspired by: https://lukeplant.me.uk/blog/posts/enforcing-conventions-in-django-projects-with-introspection/ | ||
""" | ||
|
||
project_apps = [ | ||
app.split(".")[-1] for app in settings.INSTALLED_APPS if app.startswith(settings.ROOT_URLCONF.split(".")[0]) | ||
] | ||
issue_list = [] | ||
|
||
# Allowlists | ||
allowed_datetime_field_endings = getattr(settings, "ALLOWED_MODEL_DATETIME_FIELD_ENDINGS", ["_at"]) | ||
allowed_date_field_endings = getattr(settings, "ALLOWED_MODEL_DATE_FIELD_ENDINGS", ["_date"]) | ||
|
||
str_allowed_datetime_endings = ", ".join(allowed_datetime_field_endings) | ||
str_allowed_date_endings = ", ".join(allowed_date_field_endings) | ||
|
||
# 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: | ||
# Iterate over all fields... | ||
for field in model._meta.get_fields(): | ||
# Case: DateTimeField, noqa: ERA001 | ||
if isinstance(field, DateTimeField): | ||
# Check field name ending against allowlist | ||
if not field.name.lower().endswith(tuple(allowed_datetime_field_endings)): | ||
issue_list.append( | ||
checks.Warning( | ||
f"DateTimeField '{model.__name__}.{field.name}' doesn't end with: " | ||
f"{str_allowed_datetime_endings}.", | ||
obj=field, | ||
id="ambient_toolbox.W001", | ||
) | ||
) | ||
# Case: Date field, noqa: ERA001 | ||
elif isinstance(field, DateField): | ||
# Check field name ending against allowlist | ||
if not field.name.lower().endswith(tuple(allowed_date_field_endings)): | ||
issue_list.append( | ||
checks.Warning( | ||
f"DateField '{model.__name__}.{field.name}' doesn't end with: " | ||
f"{str_allowed_date_endings}.", | ||
obj=field, | ||
id="ambient_toolbox.W002", | ||
) | ||
) | ||
|
||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# System checks | ||
|
||
## Model field naming conventions | ||
|
||
Inspired | ||
by [Luke Plants article](https://lukeplant.me.uk/blog/posts/enforcing-conventions-in-django-projects-with-introspection/), | ||
this package implements a system check to ensure that all custom DateField and DateTimeField are named in a uniform | ||
manner. | ||
|
||
By default, it requires for DateFields to end on `_date` and DateTimeFields on `_at`. | ||
|
||
It's straightforward to register this system check in your project. | ||
|
||
````python | ||
# apps/common/apps.py | ||
from ambient_toolbox.system_checks.model_field_name_conventions import check_model_time_based_fields | ||
|
||
from django.apps import AppConfig | ||
from django.core.checks import register | ||
|
||
|
||
class CommonConfig(AppConfig): | ||
name = "apps.common" | ||
verbose_name = "Common" | ||
|
||
def ready(self): | ||
register(check_model_time_based_fields) | ||
```` | ||
|
||
You can configure which field name endings are allowed by setting these variables in your global Django settings file. | ||
|
||
````python | ||
# apps/config/settings.py | ||
|
||
ALLOWED_MODEL_DATETIME_FIELD_ENDINGS = ["_at"] | ||
ALLOWED_MODEL_DATE_FIELD_ENDINGS = ["_date"] | ||
```` |
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
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
Empty file.
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,46 @@ | ||
from django.core import checks | ||
from django.test import SimpleTestCase, override_settings | ||
|
||
from ambient_toolbox.system_checks.model_field_name_conventions import check_model_time_based_fields | ||
from testapp.models import ModelNameTimeBasedFieldTest | ||
|
||
|
||
class CheckModelTimeBasedFieldsTest(SimpleTestCase): | ||
def test_check_regular(self): | ||
# Create expected warnings | ||
datetime_warning = checks.Warning( | ||
"DateTimeField 'ModelNameTimeBasedFieldTest.wrongly_named_datetime_field' doesn't end with: _at.", | ||
obj=ModelNameTimeBasedFieldTest.wrongly_named_datetime_field.field, | ||
id="ambient_toolbox.W001", | ||
) | ||
date_warning = checks.Warning( | ||
"DateField 'ModelNameTimeBasedFieldTest.wrongly_named_date_field' doesn't end with: _date.", | ||
obj=ModelNameTimeBasedFieldTest.wrongly_named_date_field.field, | ||
id="ambient_toolbox.W002", | ||
) | ||
|
||
# Call system check | ||
error_list = check_model_time_based_fields() | ||
|
||
# Assert warngins | ||
self.assertEqual(len(error_list), 2) | ||
self.assertIn(datetime_warning, error_list) | ||
self.assertIn(date_warning, error_list) | ||
|
||
@override_settings(ALLOWED_MODEL_DATETIME_FIELD_ENDINGS=["wrongly_named_datetime_field", "_at"]) | ||
def test_check_allowlist_works_datetime_field(self): | ||
# Call system check | ||
error_list = check_model_time_based_fields() | ||
|
||
# Assert warngins | ||
self.assertEqual(len(error_list), 1) | ||
self.assertEqual(error_list[0].id, "ambient_toolbox.W002") | ||
|
||
@override_settings(ALLOWED_MODEL_DATE_FIELD_ENDINGS=["wrongly_named_date_field", "_date"]) | ||
def test_check_allowlist_works_date_field(self): | ||
# Call system check | ||
error_list = check_model_time_based_fields() | ||
|
||
# Assert warngins | ||
self.assertEqual(len(error_list), 1) | ||
self.assertEqual(error_list[0].id, "ambient_toolbox.W001") |