系统检查框架

系统检查框架是一组验证Django项目的静态检查。 它检测到常见的问题,并提供了如何解决这些问题的提示。 该框架是可扩展的,所以你可以轻松地添加自己的检查。

通过 check 命令来显示的触发检查操作。检查会在大多数命令之前被隐式触发,包括 runservermigrate 。出于性能原因,检查不会作为部署中使用的 WSGI 堆栈的一部分来运行。如果你需要在部署系统上运行系统检查,可以使用 check 来触发他们。

严重的错误将阻止 Django 命令(比如 runserver)运行。小问题将会在控制台上报告出来。如果你已经检查了警告的原因并愿意忽略它,你可以在 settings.py 文件里的 SILENCED_SYSTEM_CHECKS 设置隐藏指定的警告。

Django 引发的所有检查的完整列表在 System check reference 中可查。

编写自定义的检查

框架是可伸缩的,并且允许你编写函数来执行其他你需要的其他检查。举例:

  1. from django.core.checks import Error, register
  2. @register()
  3. def example_check(app_configs, **kwargs):
  4. errors = []
  5. # ... your check logic here
  6. if check_failed:
  7. errors.append(
  8. Error(
  9. "an error",
  10. hint="A hint.",
  11. obj=checked_object,
  12. id="myapp.E001",
  13. )
  14. )
  15. return errors

check 函数必须接受一个 app_configs 参数;这个参数是应该被检查的应用程序的列表。如果为 None,则必须在项目中的 所有 已安装的应用程序上运行检查。

检查将接收一个 databases 关键字参数。这是一个数据库别名的列表,其连接可能用于检查数据库级别的配置。如果 databasesNone,则检查必须不使用任何数据库连接。

**kwargs 参数是为了将来扩展而必需的。

消息

函数必须返回消息列表。如果检查结果没有发现问题,检查函数必须返回空列表。

警告和错误由必须是 CheckMessage 的检查方法引发。CheckMessage 的实例概括了错误或警告。它也提供适合消息的上下文和消息,和用来过滤目的的唯一标示。

这个概念与来自 message framework or the logging framework 的消息非常相似。消息标有 level ,标示消息的严重性。

也可以有快捷方式来简单创建公共级别的消息。当使用这些类你可以忽略 level 参数,因为它已经通过类名隐含。

注册和标记检查

最后,你的检查函数必须已经在系统检查注册表明确注册。检查应该在加载应用程序时加载文件中注册;比如,在 AppConfig.ready() 方法中。

register(*tags)(function)

为了标记检查,你可以根据需要来传递很多标签给 register。标记检查很有用,因为它允许你仅运行一个特定的检查组。比如,为了注册一个兼容性检查,你可以进行以下调用:

  1. from django.core.checks import register, Tags
  2. @register(Tags.compatibility)
  3. def my_check(app_configs, **kwargs):
  4. # ... perform compatibility checks and collect errors
  5. return errors

你可以注册仅与生产配置文件相关的”部署检查” :

  1. @register(Tags.security, deploy=True)
  2. def my_check(app_configs, **kwargs): ...

这些检查只在使用 check —deploy 选项时运行。

你也可以把 register 当做函数而不是装饰器,通过传递一个可调用对象(通常是函数)作为第一参数传递给 register

下面的代码和上面的代码等同:

  1. def my_check(app_configs, **kwargs): ...
  2. 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。这个字段添加 minmax 参数给 IntegerField 的构造器。你可能想添加一个检查来确保用户提供小于或等于最大值的最小值。下面的代码片段显示如何实现这个检查:

  1. from django.core import checks
  2. from django.db import models
  3. class RangedIntegerField(models.IntegerField):
  4. def __init__(self, min=None, max=None, **kwargs):
  5. super().__init__(**kwargs)
  6. self.min = min
  7. self.max = max
  8. def check(self, **kwargs):
  9. # Call the superclass
  10. errors = super().check(**kwargs)
  11. # Do some custom checks and add messages to `errors`:
  12. errors.extend(self._check_min_max_values(**kwargs))
  13. # Return all errors and warnings
  14. return errors
  15. def _check_min_max_values(self, **kwargs):
  16. if self.min is not None and self.max is not None and self.min > self.max:
  17. return [
  18. checks.Error(
  19. "min greater than max.",
  20. hint="Decrease min or increase max.",
  21. obj=self,
  22. id="myapp.E001",
  23. )
  24. ]
  25. # When no error, return an empty list
  26. return []

如果你想对模型管理器添加检查,你应该对 Manager 的子类采用相同方法。

如果你想对模型类添加检查,这个方法几乎相同,唯一的区别是这个检查是一个类方法,而不是实例方法。

  1. class MyModel(models.Model):
  2. @classmethod
  3. def check(cls, **kwargs):
  4. errors = super().check(**kwargs)
  5. # ... your own checks ...
  6. return errors

Changed in Django 5.1:

In older versions, template engines didn’t implement a check() method.

编写测试

消息具有可比性。允许你轻松编写测试:

  1. from django.core.checks import Error
  2. errors = checked_object.check()
  3. expected_errors = [
  4. Error(
  5. "an error",
  6. hint="A hint.",
  7. obj=checked_object,
  8. id="myapp.E001",
  9. )
  10. ]
  11. self.assertEqual(errors, expected_errors)

编写集成测试

由于在应用程序加载时需要注册某些检查,因此测试它们在系统检查框架内的集成可以是有用的。这可以通过使用 call_command() 函数来实现。

例如,这个测试演示了:setting:SITE_ID 设置必须是一个整数,这是来自站点框架的内置 检查:

  1. from django.core.management import call_command
  2. from django.core.management.base import SystemCheckError
  3. from django.test import SimpleTestCase, modify_settings, override_settings
  4. class SystemCheckIntegrationTest(SimpleTestCase):
  5. @override_settings(SITE_ID="non_integer")
  6. @modify_settings(INSTALLED_APPS={"prepend": "django.contrib.sites"})
  7. def test_non_integer_site_id(self):
  8. message = "(sites.E101) The SITE_ID setting must be an integer."
  9. with self.assertRaisesMessage(SystemCheckError, message):
  10. call_command("check")

考虑以下检查,如果自定义设置 ENABLE_ANALYTICS 未设置为 True,则在部署时发出警告:

  1. from django.conf import settings
  2. from django.core.checks import Warning, register
  3. @register("myapp", deploy=True)
  4. def check_enable_analytics_is_true_on_deploy(app_configs, **kwargs):
  5. errors = []
  6. if getattr(settings, "ENABLE_ANALYTICS", None) is not True:
  7. errors.append(
  8. Warning(
  9. "The ENABLE_ANALYTICS setting should be set to True in deployment.",
  10. id="myapp.W001",
  11. )
  12. )
  13. return errors

鉴于这个检查不会引发 SystemCheckError,可以通过以下方式断言 stderr 输出中存在警告消息:

  1. from io import StringIO
  2. from django.core.management import call_command
  3. from django.test import SimpleTestCase, override_settings
  4. class EnableAnalyticsDeploymentCheckTest(SimpleTestCase):
  5. @override_settings(ENABLE_ANALYTICS=None)
  6. def test_when_set_to_none(self):
  7. stderr = StringIO()
  8. call_command("check", "-t", "myapp", "--deploy", stderr=stderr)
  9. message = (
  10. "(myapp.W001) The ENABLE_ANALYTICS setting should be set "
  11. "to True in deployment."
  12. )
  13. self.assertIn(message, stderr.getvalue())