123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- ======================
- System check framework
- ======================
- .. module:: django.core.checks
- The system check framework is a set of static checks for validating Django
- projects. It detects common problems and provides hints for how to fix them.
- The framework is extensible so you can easily add your own checks.
- Checks can be triggered explicitly via the :djadmin:`check` command. Checks are
- triggered implicitly before most commands, including :djadmin:`runserver` and
- :djadmin:`migrate`. For performance reasons, checks are not run as part of the
- WSGI stack that is used in deployment. If you need to run system checks on your
- deployment server, trigger them explicitly using :djadmin:`check`.
- Serious errors will prevent Django commands (such as :djadmin:`runserver`) from
- running at all. Minor problems are reported to the console. If you have inspected
- the cause of a warning and are happy to ignore it, you can hide specific warnings
- using the :setting:`SILENCED_SYSTEM_CHECKS` setting in your project settings file.
- A full list of all checks that can be raised by Django can be found in the
- :doc:`System check reference </ref/checks>`.
- Writing your own checks
- =======================
- The framework is flexible and allows you to write functions that perform
- any other kind of check you may require. The following is an example stub
- check function::
- 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
- 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.
- Messages
- --------
- The function must return a list of messages. If no problems are found as a result
- of the check, the check function must return an empty list.
- The warnings and errors raised by the check method must be instances of
- :class:`~django.core.checks.CheckMessage`. An instance of
- :class:`~django.core.checks.CheckMessage` encapsulates a single reportable
- error or warning. It also provides context and hints applicable to the
- message, and a unique identifier that is used for filtering purposes.
- The concept is very similar to messages from the :doc:`message framework
- </ref/contrib/messages>` or the :doc:`logging framework </topics/logging>`.
- Messages are tagged with a ``level`` indicating the severity of the message.
- There are also shortcuts to make creating messages with common levels easier.
- When using these classes you can omit the ``level`` argument because it is
- implied by the class name.
- * :class:`Debug`
- * :class:`Info`
- * :class:`Warning`
- * :class:`Error`
- * :class:`Critical`
- .. _registering-labeling-checks:
- Registering and labeling checks
- -------------------------------
- Lastly, your check function must be registered explicitly with system check
- registry. Checks should be registered in a file that's loaded when your
- application is loaded; for example, in the :meth:`AppConfig.ready()
- <django.apps.AppConfig.ready>` method.
- .. function:: register(*tags)(function)
- You can pass as many tags to ``register`` as you want in order to label your
- check. Tagging checks is useful since it allows you to run only a certain
- group of checks. For example, to register a compatibility check, you would
- make the following call::
- from django.core.checks import register, Tags
- @register(Tags.compatibility)
- def my_check(app_configs, **kwargs):
- # ... perform compatibility checks and collect errors
- return errors
- You can register "deployment checks" that are only relevant to a production
- settings file like this::
- @register(Tags.security, deploy=True)
- def my_check(app_configs, **kwargs): ...
- These checks will only be run if the :option:`check --deploy` option is used.
- You can also use ``register`` as a function rather than a decorator by
- passing a callable object (usually a function) as the first argument
- to ``register``.
- The code below is equivalent to the code above::
- def my_check(app_configs, **kwargs): ...
- register(my_check, Tags.security, deploy=True)
- .. _field-checking:
- Field, model, manager, template engine, and database checks
- -----------------------------------------------------------
- In some cases, you won't need to register your check function -- you can
- piggyback on an existing registration.
- 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.
- Consider an example where you are implementing a custom field named
- ``RangedIntegerField``. This field adds ``min`` and ``max`` arguments to the
- constructor of ``IntegerField``. You may want to add a check to ensure that users
- provide a min value that is less than or equal to the max value. The following
- code snippet shows how you can implement this check::
- 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 []
- If you wanted to add checks to a model manager, you would take the same
- approach on your subclass of :class:`~django.db.models.Manager`.
- If you want to add a check to a model class, the approach is *almost* the same:
- the only difference is that the check is a classmethod, not an instance method::
- class MyModel(models.Model):
- @classmethod
- def check(cls, **kwargs):
- errors = super().check(**kwargs)
- # ... your own checks ...
- return errors
- .. versionchanged:: 5.1
- In older versions, template engines didn't implement a ``check()`` method.
- Writing tests
- -------------
- Messages are comparable. That allows you to easily write tests::
- 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)
- 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 :func:`~django.core.management.call_command`
- function.
- For example, this test demonstrates that the :setting:`SITE_ID` setting must be
- an integer, a built-in :ref:`check from the sites framework
- <sites-system-checks>`::
- 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")
- Consider the following check which issues a warning on deployment if a custom
- setting named ``ENABLE_ANALYTICS`` is not set to ``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
- Given that this check will not raise a ``SystemCheckError``, the presence of
- the warning message in the ``stderr`` output can be asserted like so::
- 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())
|