mirror of
https://github.com/django/django.git
synced 2025-10-26 15:16:09 +00:00
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:
@@ -1,3 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
"""
|
||||
Base classes for writing management commands (named commands which can
|
||||
be executed through ``django-admin.py`` or ``manage.py``).
|
||||
@@ -12,9 +15,10 @@ import warnings
|
||||
from optparse import make_option, OptionParser
|
||||
|
||||
import django
|
||||
from django.core import checks
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.management.color import color_style, no_style
|
||||
from django.utils.encoding import force_str
|
||||
from django.utils.six import StringIO
|
||||
|
||||
|
||||
class CommandError(Exception):
|
||||
@@ -136,7 +140,20 @@ class BaseCommand(object):
|
||||
wrapped with ``BEGIN;`` and ``COMMIT;``. Default value is
|
||||
``False``.
|
||||
|
||||
``requires_system_checks``
|
||||
A boolean; if ``True``, entire Django project will be checked for errors
|
||||
prior to executing the command. Default value is ``True``.
|
||||
To validate an individual application's models
|
||||
rather than all applications' models, call
|
||||
``self.check(app_configs)`` from ``handle()``, where ``app_configs``
|
||||
is the list of application's configuration provided by the
|
||||
app registry.
|
||||
|
||||
``requires_model_validation``
|
||||
DEPRECATED - This value will only be used if requires_system_checks
|
||||
has not been provided. Defining both ``requires_system_checks`` and
|
||||
``requires_model_validation`` will result in an error.
|
||||
|
||||
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
|
||||
@@ -181,13 +198,40 @@ class BaseCommand(object):
|
||||
|
||||
# Configuration shortcuts that alter various logic.
|
||||
can_import_settings = True
|
||||
requires_model_validation = True
|
||||
output_transaction = False # Whether to wrap the output in a "BEGIN; COMMIT;"
|
||||
leave_locale_alone = False
|
||||
|
||||
# Uncomment the following line of code after deprecation plan for
|
||||
# requires_model_validation comes to completion:
|
||||
#
|
||||
# requires_system_checks = True
|
||||
|
||||
def __init__(self):
|
||||
self.style = color_style()
|
||||
|
||||
# `requires_model_validation` is deprecated in favour of
|
||||
# `requires_system_checks`. If both options are present, an error is
|
||||
# raised. Otherwise the present option is used. If none of them is
|
||||
# defined, the default value (True) is used.
|
||||
has_old_option = hasattr(self, 'requires_model_validation')
|
||||
has_new_option = hasattr(self, 'requires_system_checks')
|
||||
|
||||
if has_old_option:
|
||||
warnings.warn(
|
||||
'"requires_model_validation" is deprecated '
|
||||
'in favour of "requires_system_checks".',
|
||||
PendingDeprecationWarning)
|
||||
if has_old_option and has_new_option:
|
||||
raise ImproperlyConfigured(
|
||||
'Command %s defines both "requires_model_validation" '
|
||||
'and "requires_system_checks", which is illegal. Use only '
|
||||
'"requires_system_checks".' % self.__class__.__name__)
|
||||
|
||||
self.requires_system_checks = (
|
||||
self.requires_system_checks if has_new_option else
|
||||
self.requires_model_validation if has_old_option else
|
||||
True)
|
||||
|
||||
def get_version(self):
|
||||
"""
|
||||
Return the Django version, which should be correct for all
|
||||
@@ -253,8 +297,8 @@ class BaseCommand(object):
|
||||
|
||||
def execute(self, *args, **options):
|
||||
"""
|
||||
Try to execute this command, performing model validation if
|
||||
needed (as controlled by the attribute
|
||||
Try to execute this command, performing system checks if needed (as
|
||||
controlled by attributes ``self.requires_system_checks`` and
|
||||
``self.requires_model_validation``, except if force-skipped).
|
||||
"""
|
||||
self.stdout = OutputWrapper(options.get('stdout', sys.stdout))
|
||||
@@ -286,8 +330,10 @@ class BaseCommand(object):
|
||||
translation.activate('en-us')
|
||||
|
||||
try:
|
||||
if self.requires_model_validation and not options.get('skip_validation'):
|
||||
self.validate()
|
||||
if (self.requires_system_checks and
|
||||
not options.get('skip_validation') and # This will be removed at the end of deprecation proccess for `skip_validation`.
|
||||
not options.get('skip_checks')):
|
||||
self.check()
|
||||
output = self.handle(*args, **options)
|
||||
if output:
|
||||
if self.output_transaction:
|
||||
@@ -305,21 +351,67 @@ class BaseCommand(object):
|
||||
translation.activate(saved_locale)
|
||||
|
||||
def validate(self, app_config=None, display_num_errors=False):
|
||||
"""
|
||||
Validates the given app, raising CommandError for any errors.
|
||||
""" Deprecated. Delegates to ``check``."""
|
||||
|
||||
If app_config is None, then this will validate all installed apps.
|
||||
if app_config is None:
|
||||
app_configs = None
|
||||
else:
|
||||
app_configs = [app_config]
|
||||
|
||||
return self.check(app_configs=app_configs, display_num_errors=display_num_errors)
|
||||
|
||||
def check(self, app_configs=None, tags=None, display_num_errors=False):
|
||||
"""
|
||||
Uses the system check framework to validate entire Django project.
|
||||
Raises CommandError for any serious message (error or critical errors).
|
||||
If there are only light messages (like warnings), they are printed to
|
||||
stderr and no exception is raised.
|
||||
|
||||
"""
|
||||
from django.core.management.validation import get_validation_errors
|
||||
s = StringIO()
|
||||
num_errors = get_validation_errors(s, app_config)
|
||||
if num_errors:
|
||||
s.seek(0)
|
||||
error_text = s.read()
|
||||
raise CommandError("One or more models did not validate:\n%s" % error_text)
|
||||
all_issues = checks.run_checks(app_configs=app_configs, tags=tags)
|
||||
|
||||
msg = ""
|
||||
if all_issues:
|
||||
debugs = [e for e in all_issues if e.level < checks.INFO and not e.is_silenced()]
|
||||
infos = [e for e in all_issues if checks.INFO <= e.level < checks.WARNING and not e.is_silenced()]
|
||||
warnings = [e for e in all_issues if checks.WARNING <= e.level < checks.ERROR and not e.is_silenced()]
|
||||
errors = [e for e in all_issues if checks.ERROR <= e.level < checks.CRITICAL]
|
||||
criticals = [e for e in all_issues if checks.CRITICAL <= e.level]
|
||||
sorted_issues = [
|
||||
(criticals, 'CRITICALS'),
|
||||
(errors, 'ERRORS'),
|
||||
(warnings, 'WARNINGS'),
|
||||
(infos, 'INFOS'),
|
||||
(debugs, 'DEBUGS'),
|
||||
]
|
||||
|
||||
for issues, group_name in sorted_issues:
|
||||
if issues:
|
||||
formatted = (
|
||||
color_style().ERROR(force_str(e))
|
||||
if e.is_serious()
|
||||
else color_style().WARNING(force_str(e))
|
||||
for e in issues)
|
||||
formatted = "\n".join(sorted(formatted))
|
||||
msg += '\n%s:\n%s\n' % (group_name, formatted)
|
||||
|
||||
msg = "System check identified some issues:\n%s" % msg
|
||||
|
||||
if display_num_errors:
|
||||
self.stdout.write("%s error%s found" % (num_errors, '' if num_errors == 1 else 's'))
|
||||
if msg:
|
||||
msg += '\n'
|
||||
msg += "System check identified %s." % (
|
||||
"no issues" if len(all_issues) == 0 else
|
||||
"1 issue" if len(all_issues) == 1 else
|
||||
"%s issues" % len(all_issues)
|
||||
)
|
||||
|
||||
if any(e.is_serious() and not e.is_silenced() for e in all_issues):
|
||||
raise CommandError(msg)
|
||||
elif msg and all_issues:
|
||||
self.stderr.write(msg)
|
||||
elif msg:
|
||||
self.stdout.write(msg)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user