系统检查框架

系统检查框架是一组验证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

The check function must accept an app_configs argument; this argument is the list of applications that should be inspected. If None, the check must be run on all installed apps in the project.

The check will receive a databases keyword argument. This is a list of database aliases whose connections may be used to inspect database level configuration. If databases is None, the check must not use any database connections.

The **kwargs argument is required for future expansion.

消息

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

警告和错误由必须是 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):
  3. ...

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

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

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

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

字段,模型,管理器和数据库检查

在某些情况下,你不需要注册检查函数——你可以使用现有的注册。

字段,模型,模型管理器和数据库后端都实现了一个检查方法,这个方法已经被检查框架注册。如果你想添加其他检查,你可以在基类上扩展实现,完成任何你需要的检查,将任何消息添加到基类生成的消息。建议你将每个检查委派给不同方法。

考虑一个例子,你正在实现一个自定义字段 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

编写测试

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

  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)

Writing integration tests

Given the need to register certain checks when the application loads, it can be useful to test their integration within the system checks framework. This can be accomplished by using the call_command() function.

For example, this test demonstrates that the SITE_ID setting must be an integer, a built-in check from the sites framework:

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

Consider the following check which issues a warning on deployment if a custom setting named ENABLE_ANALYTICS is not set to 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

Given that this check will not raise a SystemCheckError, the presence of the warning message in the stderr output can be asserted like so:

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