システムチェックフレームワーク¶
システムチェックフレームワークは、Django プロジェクトを検証するための静的チェックのセットです。一般的な問題を検出し、それらを修正するヒントを提供します。このフレームワークは拡張性があり、独自のチェックを簡単に追加できます。
チェックは、check
コマンドを介して明示的にトリガーできます。チェックは、runserver
や migrate
を含むほとんどのコマンドの前に暗黙的にトリガーされます。パフォーマンスのため、チェックはデプロイメントで使用される WSGI スタックの一部としては実行されません。デプロイメントサーバーでシステムチェックを実行する必要がある場合は、 check
を使用して明示的にトリガーしてください。
重大なエラーは、Djangoのコマンド (たとえば runserver
) の実行を完全に停止します。軽微な問題はコンソールに報告されます。警告の原因を調査し、無視しても問題ない場合は、プロジェクトの設定ファイルである SILENCED_SYSTEM_CHECKS
設定を使用して特定の警告を非表示にできます。
Djangoが発生させる可能性のあるすべてのチェックの完全なリストは、 システムチェックのリファレンス にあります。
独自のチェックを書く¶
フレームワークは柔軟で、必要に応じて任意の種類のチェックを実行する関数を書くことができます。以下は、例としてのスタブチェック関数です。
from django.core.checks import Error, register
@register()
def example_check(app_configs, **kwargs):
errors = []
# ... your check logic here
if check_failed:
errors.append(
Error(
"an error",
hint="A hint.",
obj=checked_object,
id="myapp.E001",
)
)
return errors
チェック関数は、 必ず app_configs
引数を受け入れる必要があります。この引数は、調査されるべきアプリケーションのリストです。 None
の場合、チェックはプロジェクト内の すべての インストールされたアプリで実行される必要があります。
チェックは databases
キーワード引数を受け取ります。これは、データベースレベルの構成を調査するために使用される可能性のあるデータベースエイリアスのリストです。 databases
が None
の場合、チェックはどのデータベース接続も使用してはいけません。
**kwargs
引数は将来の拡張のために必要です。
メッセージ¶
関数は、メッセージのリストを返さなければなりません。チェックの結果、問題が見つからない場合、チェック関数は空のリストを返さなければなりません。
チェックメソッドで発生させる警告やエラーは、必ず CheckMessage
のインスタンスでなければなりません。CheckMessage
のインスタンスは、1つの報告可能なエラーや警告をカプセル化します。また、メッセージに適用可能なコンテキストやヒント、フィルタリングの目的で使用される一意の識別子も提供します。
この概念は、 メッセージフレームワーク や ログフレームワーク のメッセージと非常に似ています。メッセージには、メッセージの深刻さを示す「レベル」が付けられます。
一般的なレベルのメッセージを簡単に作成するためのショートカットもあります。これらのクラスを使う場合、引数 level
はクラス名によって省略できます。
チェックの登録とラベリング¶
最後に、チェック関数はシステムのチェックレジストリに明示的に登録する必要があります。チェックは、アプリケーションが読み込まれるときに読み込まれるファイル内に登録される必要があります。たとえば、 AppConfig.ready()
メソッド内などです。
-
register
(*tags)(function)¶
register
には、チェックにラベルを付けるために必要な数のタグを渡すことができます。チェックにタグをつけると、特定のチェックグループだけを実行することができるので便利です。例えば、互換性チェックを登録するには、以下のように呼び出します:
from django.core.checks import register, Tags
@register(Tags.compatibility)
def my_check(app_configs, **kwargs):
# ... perform compatibility checks and collect errors
return errors
以下のように、本番用の設定ファイルにのみ関係する「デプロイメントチェック」を登録できます:
@register(Tags.security, deploy=True)
def my_check(app_configs, **kwargs): ...
これらのチェックが行われるのは、check --deploy
オプションが使用されたときだけです。
また、呼び出し可能オブジェクト(通常は関数)を register
の第一引数に渡すことで、デコレータではなく関数として register
を使用することもできます。
下記のコードは上記のコードと等価です:
def my_check(app_configs, **kwargs): ...
register(my_check, Tags.security, deploy=True)
Field, model, manager, template engine, and database checks¶
場合によっては、チェック機能を登録する必要はありません。既存の登録に「便乗させる」ことができます。
Fields, models, model managers, template engines, and database backends all
implement a check()
method that is already registered with the check
framework. If you want to add extra checks, you can extend the implementation
on the base class, perform any extra checks you need, and append any messages
to those generated by the base class. It's recommended that you delegate each
check to separate methods.
RangedIntegerField
というカスタムフィールドを実装する例を考えてみましょう。このフィールドは IntegerField
のコンストラクタに min
と max
引数を追加します。ユーザの min 値が max 値以下であることを確認するチェックを追加できます。次のコードスニペットは、このチェックを実装する方法を示しています:
from django.core import checks
from django.db import models
class RangedIntegerField(models.IntegerField):
def __init__(self, min=None, max=None, **kwargs):
super().__init__(**kwargs)
self.min = min
self.max = max
def check(self, **kwargs):
# Call the superclass
errors = super().check(**kwargs)
# Do some custom checks and add messages to `errors`:
errors.extend(self._check_min_max_values(**kwargs))
# Return all errors and warnings
return errors
def _check_min_max_values(self, **kwargs):
if self.min is not None and self.max is not None and self.min > self.max:
return [
checks.Error(
"min greater than max.",
hint="Decrease min or increase max.",
obj=self,
id="myapp.E001",
)
]
# When no error, return an empty list
return []
モデルマネージャにチェックを追加したい場合は、 Manager
のサブクラスで同じアプローチを取ります。
モデルクラスにチェックを追加したい場合、アプローチは ほとんど 同じです。唯一の違いは、チェックがインスタンスメソッドではなくクラスメソッドであるということです:
class MyModel(models.Model):
@classmethod
def check(cls, **kwargs):
errors = super().check(**kwargs)
# ... your own checks ...
return errors
In older versions, template engines didn't implement a check()
method.
テストを書く¶
メッセージは比較可能なので、次のように簡単にテストを書くことができます。
from django.core.checks import Error
errors = checked_object.check()
expected_errors = [
Error(
"an error",
hint="A hint.",
obj=checked_object,
id="myapp.E001",
)
]
self.assertEqual(errors, expected_errors)
インテグレーションテストを書く¶
アプリケーションの読み込み時には特定のチェックを登録する必要があるため、システムチェックフレームワーク内での統合をテストするのが便利です。これは call_command()
関数を使うことで実現できます。
例えば、このテストは SITE_ID
の設定が整数でなければならないことを示しています。これは組み込みの サイト フレームワークからのチェック のものです:
from django.core.management import call_command
from django.core.management.base import SystemCheckError
from django.test import SimpleTestCase, modify_settings, override_settings
class SystemCheckIntegrationTest(SimpleTestCase):
@override_settings(SITE_ID="non_integer")
@modify_settings(INSTALLED_APPS={"prepend": "django.contrib.sites"})
def test_non_integer_site_id(self):
message = "(sites.E101) The SITE_ID setting must be an integer."
with self.assertRaisesMessage(SystemCheckError, message):
call_command("check")
ENABLE_ANALYTICS
というカスタム設定が True
に設定されていない場合に、デプロイ時に警告を発行する以下のチェックを考えてみましょう:
from django.conf import settings
from django.core.checks import Warning, register
@register("myapp", deploy=True)
def check_enable_analytics_is_true_on_deploy(app_configs, **kwargs):
errors = []
if getattr(settings, "ENABLE_ANALYTICS", None) is not True:
errors.append(
Warning(
"The ENABLE_ANALYTICS setting should be set to True in deployment.",
id="myapp.W001",
)
)
return errors
このチェックが SystemCheckError
を発生させないことを考えると、以下のように、 stderr
の出力に警告メッセージがあることをアサートできます:
from io import StringIO
from django.core.management import call_command
from django.test import SimpleTestCase, override_settings
class EnableAnalyticsDeploymentCheckTest(SimpleTestCase):
@override_settings(ENABLE_ANALYTICS=None)
def test_when_set_to_none(self):
stderr = StringIO()
call_command("check", "-t", "myapp", "--deploy", stderr=stderr)
message = (
"(myapp.W001) The ENABLE_ANALYTICS setting should be set "
"to True in deployment."
)
self.assertIn(message, stderr.getvalue())