Browse Source

Refs #26351 -- Added check hook to support database-related checks

Thanks Tim Graham and Shai Berger for the reviews.
Claude Paroz 9 years ago
parent
commit
0d3c616fbb

+ 1 - 0
django/core/checks/__init__.py

@@ -10,6 +10,7 @@ from .registry import Tags, register, run_checks, tag_exists
 # Import these to force registration of checks
 import django.core.checks.caches  # NOQA isort:skip
 import django.core.checks.compatibility.django_1_8_0  # NOQA isort:skip
+import django.core.checks.database  # NOQA isort:skip
 import django.core.checks.model_checks  # NOQA isort:skip
 import django.core.checks.security.base  # NOQA isort:skip
 import django.core.checks.security.csrf  # NOQA isort:skip

+ 11 - 0
django/core/checks/database.py

@@ -0,0 +1,11 @@
+from django.db import connections
+
+from . import Tags, register
+
+
+@register(Tags.database)
+def check_database_backends(*args, **kwargs):
+    issues = []
+    for conn in connections.all():
+        issues.extend(conn.validation.check(**kwargs))
+    return issues

+ 6 - 0
django/core/checks/registry.py

@@ -13,6 +13,7 @@ class Tags(object):
     admin = 'admin'
     caches = 'caches'
     compatibility = 'compatibility'
+    database = 'database'
     models = 'models'
     security = 'security'
     signals = 'signals'
@@ -70,6 +71,11 @@ class CheckRegistry(object):
         if tags is not None:
             checks = [check for check in checks
                       if hasattr(check, 'tags') and set(check.tags) & set(tags)]
+        else:
+            # By default, 'database'-tagged checks are not run as they do more
+            # than mere static code analysis.
+            checks = [check for check in checks
+                      if not hasattr(check, 'tags') or Tags.database not in check.tags]
 
         for check in checks:
             new_errors = check(app_configs=app_configs)

+ 4 - 1
django/core/management/base.py

@@ -368,6 +368,9 @@ class BaseCommand(object):
                 translation.activate(saved_locale)
         return output
 
+    def _run_checks(self, **kwargs):
+        return checks.run_checks(**kwargs)
+
     def check(self, app_configs=None, tags=None, display_num_errors=False,
               include_deployment_checks=False, fail_level=checks.ERROR):
         """
@@ -376,7 +379,7 @@ class BaseCommand(object):
         If there are only light messages (like warnings), they are printed to
         stderr and no exception is raised.
         """
-        all_issues = checks.run_checks(
+        all_issues = self._run_checks(
             app_configs=app_configs,
             tags=tags,
             include_deployment_checks=include_deployment_checks,

+ 6 - 0
django/core/management/commands/migrate.py

@@ -6,6 +6,7 @@ from collections import OrderedDict
 from importlib import import_module
 
 from django.apps import apps
+from django.core.checks import Tags, run_checks
 from django.core.management.base import BaseCommand, CommandError
 from django.core.management.sql import (
     emit_post_migrate_signal, emit_pre_migrate_signal,
@@ -56,6 +57,11 @@ class Command(BaseCommand):
             help='Creates tables for apps without migrations.',
         )
 
+    def _run_checks(self, **kwargs):
+        issues = run_checks(tags=[Tags.database])
+        issues.extend(super(Command, self).check(**kwargs))
+        return issues
+
     def handle(self, *args, **options):
 
         self.verbosity = options['verbosity']

+ 4 - 1
django/db/backends/base/validation.py

@@ -1,9 +1,12 @@
 class BaseDatabaseValidation(object):
     """
-    This class encapsulates all backend-specific model validation.
+    This class encapsulates all backend-specific validation.
     """
     def __init__(self, connection):
         self.connection = connection
 
+    def check(self, **kwargs):
+        return []
+
     def check_field(self, field, **kwargs):
         return []

+ 8 - 0
docs/ref/checks.txt

@@ -84,6 +84,14 @@ Django's system checks are organized using the following tags:
 * ``templates``: Checks template related configuration.
 * ``caches``: Checks cache related configuration.
 * ``urls``: Checks URL configuration.
+* ``database``: Checks database-related configuration issues. Database checks
+  are not run by default because they do more than static code analysis as
+  regular checks do. They are only run by the :djadmin:`migrate` command or if
+  you specify the ``database`` tag when calling the :djadmin:`check` command.
+
+.. versionadded:: 1.10
+
+    The ``database`` tag was added.
 
 Some checks may be registered with multiple tags.
 

+ 12 - 7
docs/topics/checks.txt

@@ -120,17 +120,22 @@ The code below is equivalent to the code above::
 
 .. _field-checking:
 
-Field, model, and manager checks
---------------------------------
+Field, model, manager, and database 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 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.
+Fields, models, model managers, 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.
+
+.. versionchanged:: 1.10
+
+    Database backend checks were added.
 
 Consider an example where you are implementing a custom field named
 ``RangedIntegerField``. This field adds ``min`` and ``max`` arguments to the

+ 33 - 0
tests/check_framework/test_database.py

@@ -0,0 +1,33 @@
+import unittest
+
+from django.core.checks import Tags, run_checks
+from django.core.checks.registry import CheckRegistry
+from django.db import connection
+from django.test import TestCase, mock
+
+
+class DatabaseCheckTests(TestCase):
+    @property
+    def func(self):
+        from django.core.checks.database import check_database_backends
+        return check_database_backends
+
+    def test_database_checks_not_run_by_default(self):
+        """
+        `database` checks are only run when their tag is specified.
+        """
+        def f1(**kwargs):
+            return [5]
+
+        registry = CheckRegistry()
+        registry.register(Tags.database)(f1)
+        errors = registry.run_checks()
+        self.assertEqual(errors, [])
+
+        errors2 = registry.run_checks(tags=[Tags.database])
+        self.assertEqual(errors2, [5])
+
+    def test_database_checks_called(self):
+        with mock.patch('django.db.backends.base.validation.BaseDatabaseValidation.check') as mocked_check:
+            run_checks(tags=[Tags.database])
+            self.assertTrue(mocked_check.called)