Fixed #16905 -- Added extensible checks (nee validation) framework

This is the result of Christopher Medrela's 2013 Summer of Code project.

Thanks also to Preston Holmes, Tim Graham, Anssi Kääriäinen, Florian
Apolloner, and Alex Gaynor for review notes along the way.

Also: Fixes #8579, fixes #3055, fixes #19844.
This commit is contained in:
Russell Keith-Magee 2014-01-20 10:45:21 +08:00
parent 6e7bd0b63b
commit d818e0c9b2
101 changed files with 7058 additions and 1958 deletions

View file

@ -227,8 +227,23 @@ All attributes can be set in your derived class and can be used in
wrapped with ``BEGIN;`` and ``COMMIT;``. Default value is
``False``.
.. attribute:: BaseCommand.requires_system_checks
.. versionadded:: 1.7
A boolean; if ``True``, the entire Django project will be checked for
potential problems prior to executing the command. If
``requires_system_checks`` is missing, the value of
``requires_model_validation`` is used. If the latter flag is missing
as well, the default value (``True``) is used. Defining both
``requires_system_checks`` and ``requires_model_validation`` will result
in an error.
.. attribute:: BaseCommand.requires_model_validation
.. deprecated:: 1.7
Replaced by ``requires_system_checks``
A boolean; if ``True``, validation of installed models will be
performed prior to executing the command. Default value is
``True``. To validate an individual application's models
@ -299,12 +314,23 @@ the :meth:`~BaseCommand.handle` method must be implemented.
The actual logic of the command. Subclasses must implement this method.
.. method:: BaseCommand.check(app_configs=None, tags=None, display_num_errors=False)
.. versionadded:: 1.7
Uses the system check framework to inspect the entire Django project for
potential problems. Serious problems are raised as a :class:`CommandError`;
warnings are output to stderr; minor notifications are output to stdout.
If ``apps`` and ``tags`` are both None, all system checks are performed.
``tags`` can be a list of check tags, like ``compatibility`` or ``models``.
.. method:: BaseCommand.validate(app=None, display_num_errors=False)
Validates the given app, raising :class:`CommandError` for any errors.
If ``app`` is None, then all installed apps are validated.
.. deprecated:: 1.7
Replaced with the :djadmin:`check` command
If ``app`` is None, then all installed apps are checked for errors.
.. _ref-basecommand-subclasses:

View file

@ -291,6 +291,7 @@ Learn about some other core functionalities of the Django framework:
* :doc:`Flatpages <ref/contrib/flatpages>`
* :doc:`Redirects <ref/contrib/redirects>`
* :doc:`Signals <topics/signals>`
* :doc:`System check framework <ref/checks>`
* :doc:`The sites framework <ref/contrib/sites>`
* :doc:`Unicode in Django <ref/unicode>`

View file

@ -234,6 +234,16 @@ these changes.
* Passing callable arguments to querysets will no longer be possible.
* ``BaseCommand.requires_model_validation`` will be removed in favor of
``requires_system_checks``. Admin validators will be replaced by admin
checks.
* ``ModelAdmin.validator`` will be removed in favor of the new ``checks``
attribute.
* ``django.db.backends.DatabaseValidation.validate_field`` will be removed in
favor of the ``check_field`` method.
2.0
---

View file

@ -140,7 +140,7 @@ You'll see the following output on the command line:
.. parsed-literal::
Validating models...
Performing system checks...
0 errors found
|today| - 15:50:53
@ -545,8 +545,8 @@ Note the following:
changes.
If you're interested, you can also run
:djadmin:`python manage.py validate <validate>`; this checks for any errors in
your models without making migrations or touching the database.
:djadmin:`python manage.py check <check>`; this checks for any problems in
your project without making migrations or touching the database.
Now, run :djadmin:`migrate` again to create those model tables in your database:

208
docs/ref/checks.txt Normal file
View file

@ -0,0 +1,208 @@
======================
System check framework
======================
.. versionadded:: 1.7
.. 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, the checks are not performed if
:setting:`DEBUG` is set to ``False``.
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.
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 register
@register()
def example_check(app_configs, **kwargs):
errors = []
# ... your check logic here
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 ``**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.
.. class:: CheckMessage(level, msg, hint, obj=None, id=None)
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.
Constructor arguments are:
``level``
The severity of the message. Use one of the
predefined values: ``DEBUG``, ``INFO``, ``WARNING``, ``ERROR``,
``CRITICAL``. If the level is greater or equal to ``ERROR``, then Django
will prevent management commands from executing. Messages with
level lower than ``ERROR`` (i.e. warnings) are reported to the console,
but can be silenced.
``msg``
A short (less than 80 characters) string describing the problem. The string
should *not* contain newlines.
``hint``
A single-line string providing a hint for fixing the problem. If no hint
can be provided, or the hint is self-evident from the error message, a
value of ``None`` can be used::
Error('error message') # Will not work.
Error('error message', None) # Good
Error('error message', hint=None) # Better
``obj``
Optional. An object providing context for the message (for example, the
model where the problem was discovered). The object should be a model, field,
or manager or any other object that defines ``__str__`` method (on
Python 2 you need to define ``__unicode__`` method). The method is used while
reporting all messages and its result precedes the message.
``id``
Optional string. A unique identifier for the issue. Identifiers should
follow the pattern ``applabel.X001``, where ``X`` is one of the letters
``CEWID``, indicating the message severity (``C`` for criticals,
``E`` for errors and so). The number can be allocated by the application,
but should be unique within that application.
There are also shortcuts to make creating messages with common levels easier.
When using these methods you can omit the ``level`` argument because it is
implied by the class name.
.. class:: Debug(msg, hint, obj=None, id=None)
.. class:: Info(msg, hint, obj=None, id=None)
.. class:: Warning(msg, hint, obj=None, id=None)
.. class:: Error(msg, hint, obj=None, id=None)
.. class:: Critical(msg, hint, obj=None, id=None)
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=None,
obj=checked_object,
id='myapp.E001',
)
]
self.assertEqual(errors, expected_errors)
Registering and labeling checks
-------------------------------
Lastly, your check function must be registered explicitly with system check
registry.
.. 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
@register('compatibility')
def my_check(app_configs, **kwargs):
# ... perform compatibility checks and collect errors
return errors
.. _field-checking:
Field, Model, and Manager checks
--------------------------------
In some cases, you won't need to register your check function -- you can
piggyback on an existing registration.
Fields, models, and model managers all implement a ``check()`` method that is
already registered with the check framework. If you want to add extra checks,
you can extend the implemenation on the base class, perform any extra
checks you need, and append any messages to those generated by the base class.
It's recommended the you delegate each check to a 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(RangedIntegerField, self).__init__(**kwargs)
self.min = min
self.max = max
def check(self, **kwargs):
# Call the superclass
errors = super(RangedIntegerField, self).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 greated 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 sublass 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(MyModel, cls).check(**kwargs)
# ... your own checks ...
return errors

View file

@ -95,18 +95,36 @@ documentation for the :djadminopt:`--verbosity` option.
Available commands
==================
check
-----
check <appname appname ...>
---------------------------
.. django-admin:: check
.. versionadded:: 1.6
.. versionchanged:: 1.7
Performs a series of checks to verify a given setup (settings/application code)
is compatible with the current version of Django.
Uses the :doc:`system check framework </ref/checks>` to inspect
the entire Django project for common problems.
Upon finding things that are incompatible or require notifying the user, it
issues a series of warnings.
The system check framework will confirm that there aren't any problems with
your installed models or your admin registrations. It will also provide warnings
of common compatibility problems introduced by upgrading Django to a new version.
Custom checks may be introduced by other libraries and applications.
By default, all apps will be checkd. You can check a subset of apps by providing
a list of app labels as arguments::
python manage.py auth admin myapp
If you do not specify any app, all apps will be checked.
.. django-admin-option:: --tag <tagname>
The :doc:`system check framework </ref/checks>` performs many different
types of checks. These check types are categorized with tags. You can use these tags
to restrict the checks performed to just those in a particular category. For example,
to perform only security and compatibility checks, you would run::
python manage.py --tag security -tag compatibility
compilemessages
---------------
@ -810,9 +828,9 @@ reduction.
``pyinotify`` support was added.
When you start the server, and each time you change Python code while the
server is running, the server will validate all of your installed models. (See
the :djadmin:`validate` command below.) If the validator finds errors, it will
print them to standard output, but it won't stop the server.
server is running, the server will check your entire Django project for errors (see
the :djadmin:`check` command). If any errors are found, they will be printed
to standard output, but it won't stop the server.
You can run as many servers as you want, as long as they're on separate ports.
Just execute ``django-admin.py runserver`` more than once.
@ -1310,6 +1328,9 @@ validate
.. django-admin:: validate
.. deprecated:: 1.7
Replaced by the :djadmin:`check` command.
Validates all installed models (according to the :setting:`INSTALLED_APPS`
setting) and prints validation errors to standard output.

View file

@ -6,6 +6,7 @@ API Reference
:maxdepth: 1
applications
checks
class-based-views/index
clickjacking
contrib/index

View file

@ -1766,6 +1766,22 @@ The backend used for signing cookies and other data.
See also the :doc:`/topics/signing` documentation.
.. setting:: SILENCED_SYSTEM_CHECKS
SILENCED_SYSTEM_CHECKS
----------------------
.. versionadded:: 1.7
Default: ``[]``
A list of identifers of messages generated by the system check framework
(i.e. ``["models.W001"]``) that you wish to permanently acknowledge and ignore.
Silenced warnings will no longer be output to the console; silenced errors
will still be printed, but will not prevent management commands from running.
See also the :doc:`/ref/checks` documentation.
.. setting:: TEMPLATE_CONTEXT_PROCESSORS
TEMPLATE_CONTEXT_PROCESSORS
@ -2737,6 +2753,7 @@ Error reporting
* :setting:`IGNORABLE_404_URLS`
* :setting:`MANAGERS`
* :setting:`SEND_BROKEN_LINK_EMAILS`
* :setting:`SILENCED_SYSTEM_CHECKS`
File uploads
------------

View file

@ -140,6 +140,17 @@ Using a custom manager when traversing reverse relations
It is now possible to :ref:`specify a custom manager
<using-custom-reverse-manager>` when traversing a reverse relationship.
New system check framework
~~~~~~~~~~~~~~~~~~~~~~~~~~
We've added a new :doc:`System check framework </ref/checks>` for
detecting common problems (like invalid models) and providing hints for
resolving those problems. The framework is extensible so you can add your
own checks for your own apps and libraries.
To perform system checks, you use the :djadmin:`check` management command.
This command replaces the older :djadmin:`validate` management command.
New ``Prefetch`` object for advanced ``prefetch_related`` operations.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -993,7 +1004,7 @@ considered a bug and has been addressed.
``syncdb``
~~~~~~~~~~
The ``syncdb`` command has been deprecated in favour of the new ``migrate``
The :djadmin:`syncdb` command has been deprecated in favor of the new :djadmin:`migrate`
command. ``migrate`` takes the same arguments as ``syncdb`` used to plus a few
more, so it's safe to just change the name you're calling and nothing else.
@ -1103,3 +1114,32 @@ remove the setting from your configuration at your convenience.
``SplitDateTimeWidget`` support in :class:`~django.forms.DateTimeField` is
deprecated, use ``SplitDateTimeWidget`` with
:class:`~django.forms.SplitDateTimeField` instead.
``validate``
~~~~~~~~~~~~
:djadmin:`validate` command is deprecated in favor of :djadmin:`check` command.
``django.core.management.BaseCommand``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``requires_model_validation`` is deprecated in favor of a new
``requires_system_checks`` flag. If the latter flag is missing, then the
value of the former flag is used. Defining both ``requires_system_checks`` and
``requires_model_validation`` results in an error.
The ``check()`` method has replaced the old ``validate()`` method.
``ModelAdmin.validator``
~~~~~~~~~~~~~~~~~~~~~~~~
``ModelAdmin.validator`` is deprecated in favor of new ``checks`` attribute.
``django.db.backends.DatabaseValidation.validate_field``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This method is deprecated in favor of a new ``check_field`` method.
The functionality required by ``check_field()`` is the same as that provided
by ``validate_field()``, but the output format is different. Third-party database
backends needing this functionality should modify their backends to provide an
implementation of ``check_field()``.

View file

@ -960,7 +960,7 @@ The reverse name of the ``common.ChildA.m2m`` field will be
reverse name of the ``rare.ChildB.m2m`` field will be ``rare_childb_related``.
It is up to you how you use the ``'%(class)s'`` and ``'%(app_label)s`` portion
to construct your related name, but if you forget to use it, Django will raise
errors when you validate your models (or run :djadmin:`migrate`).
errors when you perform system checks (or run :djadmin:`migrate`).
If you don't specify a :attr:`~django.db.models.ForeignKey.related_name`
attribute for a field in an abstract base class, the default reverse name will
@ -1053,7 +1053,7 @@ are putting those types of relations on a subclass of another model,
you **must** specify the
:attr:`~django.db.models.ForeignKey.related_name` attribute on each
such field. If you forget, Django will raise an error when you run
:djadmin:`validate` or :djadmin:`migrate`.
:djadmin:`check` or :djadmin:`migrate`.
For example, using the above ``Place`` class again, let's create another
subclass with a :class:`~django.db.models.ManyToManyField`::