系统检查框架

系统检查框架是一套用于验证 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 的实例封装单个可报告的错误或警告。它还提供适用于消息的上下文和提示,以及用于过滤目的的唯一标识符。

此概念与 消息框架日志框架 中的消息非常相似。消息用 level 标记,指示消息的严重程度。

还有一些快捷方式可以更轻松地创建具有常见级别的消息。使用这些类时,您可以省略 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 的自定义字段。此字段将 minmax 参数添加到 IntegerField 的构造函数中。您可能需要添加一个检查以确保用户提供的最小值小于或等于最大值。以下代码片段显示了如何实现此检查

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
Django 5.1 中的更改

在较旧的版本中,模板引擎没有实现 check() 方法。

编写测试

消息是可以比较的。这使您可以轻松编写测试

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())
返回顶部