システムチェックフレームワーク

システムチェックフレームワークは、Django プロジェクトを検証するための静的チェックのセットです。一般的な問題を検出し、それらを修正するヒントを提供します。このフレームワークは拡張性があり、独自のチェックを簡単に追加できます。

チェックは、check コマンドを介して明示的にトリガーできます。チェックは、runservermigrate を含むほとんどのコマンドの前に暗黙的にトリガーされます。パフォーマンスのため、チェックはデプロイメントで使用される 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 キーワード引数を受け取ります。これは、データベースレベルの構成を調査するために使用される可能性のあるデータベースエイリアスのリストです。 databasesNone の場合、チェックはどのデータベース接続も使用してはいけません。

**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)

フィールド、モデル、マネージャ、およびデータベースのチェック

場合によっては、チェック機能を登録する必要はありません。既存の登録に「便乗させる」ことができます。

フィールド、モデル、モデルマネージャ、データベースバックエンドはすべて check() メソッドを実装しており、チェックフレームワークに登録されています。追加でチェックを行いたい場合は、基底クラスの実装を拡張して、必要な追加チェックを行い、基底クラスが生成したメッセージに追加します。各チェックは別々のメソッドに委譲することを推奨します。

RangedIntegerField というカスタムフィールドを実装する例を考えてみましょう。このフィールドは IntegerField のコンストラクタに minmax 引数を追加します。ユーザの 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

テストを書く

メッセージは比較可能なので、次のように簡単にテストを書くことができます。

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())
Back to Top