Fixed #29560 -- Added --force-color management command option.
This commit is contained in:
parent
de8eb07c7a
commit
5195b99e2c
|
@ -95,7 +95,7 @@ class DjangoHelpFormatter(HelpFormatter):
|
||||||
"""
|
"""
|
||||||
show_last = {
|
show_last = {
|
||||||
'--version', '--verbosity', '--traceback', '--settings', '--pythonpath',
|
'--version', '--verbosity', '--traceback', '--settings', '--pythonpath',
|
||||||
'--no-color',
|
'--no-color', '--force_color',
|
||||||
}
|
}
|
||||||
|
|
||||||
def _reordered_actions(self, actions):
|
def _reordered_actions(self, actions):
|
||||||
|
@ -227,13 +227,15 @@ class BaseCommand:
|
||||||
# Command-specific options not defined by the argument parser.
|
# Command-specific options not defined by the argument parser.
|
||||||
stealth_options = ()
|
stealth_options = ()
|
||||||
|
|
||||||
def __init__(self, stdout=None, stderr=None, no_color=False):
|
def __init__(self, stdout=None, stderr=None, no_color=False, force_color=False):
|
||||||
self.stdout = OutputWrapper(stdout or sys.stdout)
|
self.stdout = OutputWrapper(stdout or sys.stdout)
|
||||||
self.stderr = OutputWrapper(stderr or sys.stderr)
|
self.stderr = OutputWrapper(stderr or sys.stderr)
|
||||||
|
if no_color and force_color:
|
||||||
|
raise CommandError("'no_color' and 'force_color' can't be used together.")
|
||||||
if no_color:
|
if no_color:
|
||||||
self.style = no_style()
|
self.style = no_style()
|
||||||
else:
|
else:
|
||||||
self.style = color_style()
|
self.style = color_style(force_color)
|
||||||
self.stderr.style_func = self.style.ERROR
|
self.stderr.style_func = self.style.ERROR
|
||||||
|
|
||||||
def get_version(self):
|
def get_version(self):
|
||||||
|
@ -280,6 +282,10 @@ class BaseCommand:
|
||||||
'--no-color', action='store_true',
|
'--no-color', action='store_true',
|
||||||
help="Don't colorize the command output.",
|
help="Don't colorize the command output.",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--force-color', action='store_true',
|
||||||
|
help='Force colorization of the command output.',
|
||||||
|
)
|
||||||
self.add_arguments(parser)
|
self.add_arguments(parser)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
@ -339,7 +345,11 @@ class BaseCommand:
|
||||||
controlled by the ``requires_system_checks`` attribute, except if
|
controlled by the ``requires_system_checks`` attribute, except if
|
||||||
force-skipped).
|
force-skipped).
|
||||||
"""
|
"""
|
||||||
if options['no_color']:
|
if options['force_color'] and options['no_color']:
|
||||||
|
raise CommandError("The --no-color and --force-color options can't be used together.")
|
||||||
|
if options['force_color']:
|
||||||
|
self.style = color_style(force_color=True)
|
||||||
|
elif options['no_color']:
|
||||||
self.style = no_style()
|
self.style = no_style()
|
||||||
self.stderr.style_func = None
|
self.stderr.style_func = None
|
||||||
if options.get('stdout'):
|
if options.get('stdout'):
|
||||||
|
|
|
@ -64,10 +64,10 @@ def no_style():
|
||||||
return make_style('nocolor')
|
return make_style('nocolor')
|
||||||
|
|
||||||
|
|
||||||
def color_style():
|
def color_style(force_color=False):
|
||||||
"""
|
"""
|
||||||
Return a Style object from the Django color scheme.
|
Return a Style object from the Django color scheme.
|
||||||
"""
|
"""
|
||||||
if not supports_color():
|
if not force_color and not supports_color():
|
||||||
return no_style()
|
return no_style()
|
||||||
return make_style(os.environ.get('DJANGO_COLORS', ''))
|
return make_style(os.environ.get('DJANGO_COLORS', ''))
|
||||||
|
|
|
@ -1657,6 +1657,14 @@ Example usage::
|
||||||
|
|
||||||
django-admin runserver --no-color
|
django-admin runserver --no-color
|
||||||
|
|
||||||
|
.. django-admin-option:: --force-color
|
||||||
|
|
||||||
|
.. versionadded:: 2.2
|
||||||
|
|
||||||
|
Forces colorization of the command output if it would otherwise be disabled
|
||||||
|
as discussed in :ref:`syntax-coloring`. For example, you may want to pipe
|
||||||
|
colored output to another command.
|
||||||
|
|
||||||
Extra niceties
|
Extra niceties
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
@ -1668,7 +1676,7 @@ Syntax coloring
|
||||||
The ``django-admin`` / ``manage.py`` commands will use pretty
|
The ``django-admin`` / ``manage.py`` commands will use pretty
|
||||||
color-coded output if your terminal supports ANSI-colored output. It
|
color-coded output if your terminal supports ANSI-colored output. It
|
||||||
won't use the color codes if you're piping the command's output to
|
won't use the color codes if you're piping the command's output to
|
||||||
another program.
|
another program unless the :option:`--force-color` option is used.
|
||||||
|
|
||||||
Under Windows, the native console doesn't support ANSI escape sequences so by
|
Under Windows, the native console doesn't support ANSI escape sequences so by
|
||||||
default there is no color output. But you can install the `ANSICON`_
|
default there is no color output. But you can install the `ANSICON`_
|
||||||
|
|
|
@ -170,7 +170,8 @@ Internationalization
|
||||||
Management Commands
|
Management Commands
|
||||||
~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
* ...
|
* The new :option:`--force-color` option forces colorization of the command
|
||||||
|
output.
|
||||||
|
|
||||||
Migrations
|
Migrations
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
|
@ -40,7 +40,7 @@ custom_templates_dir = os.path.join(os.path.dirname(__file__), 'custom_templates
|
||||||
SYSTEM_CHECK_MSG = 'System check identified no issues'
|
SYSTEM_CHECK_MSG = 'System check identified no issues'
|
||||||
|
|
||||||
|
|
||||||
class AdminScriptTestCase(unittest.TestCase):
|
class AdminScriptTestCase(SimpleTestCase):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
|
@ -970,9 +970,9 @@ class ManageAlternateSettings(AdminScriptTestCase):
|
||||||
out, err = self.run_manage(args)
|
out, err = self.run_manage(args)
|
||||||
self.assertOutput(
|
self.assertOutput(
|
||||||
out,
|
out,
|
||||||
"EXECUTE: noargs_command options=[('no_color', False), "
|
"EXECUTE: noargs_command options=[('force_color', False), "
|
||||||
"('pythonpath', None), ('settings', 'alternate_settings'), "
|
"('no_color', False), ('pythonpath', None), ('settings', "
|
||||||
"('traceback', False), ('verbosity', 1)]"
|
"'alternate_settings'), ('traceback', False), ('verbosity', 1)]"
|
||||||
)
|
)
|
||||||
self.assertNoOutput(err)
|
self.assertNoOutput(err)
|
||||||
|
|
||||||
|
@ -982,9 +982,9 @@ class ManageAlternateSettings(AdminScriptTestCase):
|
||||||
out, err = self.run_manage(args, 'alternate_settings')
|
out, err = self.run_manage(args, 'alternate_settings')
|
||||||
self.assertOutput(
|
self.assertOutput(
|
||||||
out,
|
out,
|
||||||
"EXECUTE: noargs_command options=[('no_color', False), "
|
"EXECUTE: noargs_command options=[('force_color', False), "
|
||||||
"('pythonpath', None), ('settings', None), ('traceback', False), "
|
"('no_color', False), ('pythonpath', None), ('settings', None), "
|
||||||
"('verbosity', 1)]"
|
"('traceback', False), ('verbosity', 1)]"
|
||||||
)
|
)
|
||||||
self.assertNoOutput(err)
|
self.assertNoOutput(err)
|
||||||
|
|
||||||
|
@ -994,9 +994,9 @@ class ManageAlternateSettings(AdminScriptTestCase):
|
||||||
out, err = self.run_manage(args)
|
out, err = self.run_manage(args)
|
||||||
self.assertOutput(
|
self.assertOutput(
|
||||||
out,
|
out,
|
||||||
"EXECUTE: noargs_command options=[('no_color', True), "
|
"EXECUTE: noargs_command options=[('force_color', False), "
|
||||||
"('pythonpath', None), ('settings', 'alternate_settings'), "
|
"('no_color', True), ('pythonpath', None), ('settings', "
|
||||||
"('traceback', False), ('verbosity', 1)]"
|
"'alternate_settings'), ('traceback', False), ('verbosity', 1)]"
|
||||||
)
|
)
|
||||||
self.assertNoOutput(err)
|
self.assertNoOutput(err)
|
||||||
|
|
||||||
|
@ -1425,7 +1425,7 @@ class ManageTestserver(AdminScriptTestCase):
|
||||||
'blah.json',
|
'blah.json',
|
||||||
stdout=out, settings=None, pythonpath=None, verbosity=1,
|
stdout=out, settings=None, pythonpath=None, verbosity=1,
|
||||||
traceback=False, addrport='', no_color=False, use_ipv6=False,
|
traceback=False, addrport='', no_color=False, use_ipv6=False,
|
||||||
skip_checks=True, interactive=True,
|
skip_checks=True, interactive=True, force_color=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@mock.patch('django.db.connection.creation.create_test_db', return_value='test_db')
|
@mock.patch('django.db.connection.creation.create_test_db', return_value='test_db')
|
||||||
|
@ -1436,6 +1436,7 @@ class ManageTestserver(AdminScriptTestCase):
|
||||||
call_command('testserver', 'blah.json', stdout=out)
|
call_command('testserver', 'blah.json', stdout=out)
|
||||||
mock_runserver_handle.assert_called_with(
|
mock_runserver_handle.assert_called_with(
|
||||||
addrport='',
|
addrport='',
|
||||||
|
force_color=False,
|
||||||
insecure_serving=False,
|
insecure_serving=False,
|
||||||
no_color=False,
|
no_color=False,
|
||||||
pythonpath=None,
|
pythonpath=None,
|
||||||
|
@ -1578,6 +1579,34 @@ class CommandTypes(AdminScriptTestCase):
|
||||||
self.assertEqual(out.getvalue(), 'Hello, world!\n')
|
self.assertEqual(out.getvalue(), 'Hello, world!\n')
|
||||||
self.assertEqual(err.getvalue(), 'Hello, world!\n')
|
self.assertEqual(err.getvalue(), 'Hello, world!\n')
|
||||||
|
|
||||||
|
def test_force_color_execute(self):
|
||||||
|
out = StringIO()
|
||||||
|
err = StringIO()
|
||||||
|
with mock.patch.object(sys.stdout, 'isatty', lambda: False):
|
||||||
|
command = ColorCommand(stdout=out, stderr=err)
|
||||||
|
call_command(command, force_color=True)
|
||||||
|
self.assertEqual(out.getvalue(), '\x1b[31;1mHello, world!\n\x1b[0m')
|
||||||
|
self.assertEqual(err.getvalue(), '\x1b[31;1mHello, world!\n\x1b[0m')
|
||||||
|
|
||||||
|
def test_force_color_command_init(self):
|
||||||
|
out = StringIO()
|
||||||
|
err = StringIO()
|
||||||
|
with mock.patch.object(sys.stdout, 'isatty', lambda: False):
|
||||||
|
command = ColorCommand(stdout=out, stderr=err, force_color=True)
|
||||||
|
call_command(command)
|
||||||
|
self.assertEqual(out.getvalue(), '\x1b[31;1mHello, world!\n\x1b[0m')
|
||||||
|
self.assertEqual(err.getvalue(), '\x1b[31;1mHello, world!\n\x1b[0m')
|
||||||
|
|
||||||
|
def test_no_color_force_color_mutually_exclusive_execute(self):
|
||||||
|
msg = "The --no-color and --force-color options can't be used together."
|
||||||
|
with self.assertRaisesMessage(CommandError, msg):
|
||||||
|
call_command(BaseCommand(), no_color=True, force_color=True)
|
||||||
|
|
||||||
|
def test_no_color_force_color_mutually_exclusive_command_init(self):
|
||||||
|
msg = "'no_color' and 'force_color' can't be used together."
|
||||||
|
with self.assertRaisesMessage(CommandError, msg):
|
||||||
|
call_command(BaseCommand(no_color=True, force_color=True))
|
||||||
|
|
||||||
def test_custom_stdout(self):
|
def test_custom_stdout(self):
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
requires_system_checks = False
|
requires_system_checks = False
|
||||||
|
@ -1655,9 +1684,10 @@ class CommandTypes(AdminScriptTestCase):
|
||||||
|
|
||||||
expected_out = (
|
expected_out = (
|
||||||
"EXECUTE:BaseCommand labels=%s, "
|
"EXECUTE:BaseCommand labels=%s, "
|
||||||
"options=[('no_color', False), ('option_a', %s), ('option_b', %s), "
|
"options=[('force_color', False), ('no_color', False), "
|
||||||
"('option_c', '3'), ('pythonpath', None), ('settings', None), "
|
"('option_a', %s), ('option_b', %s), ('option_c', '3'), "
|
||||||
"('traceback', False), ('verbosity', 1)]") % (labels, option_a, option_b)
|
"('pythonpath', None), ('settings', None), ('traceback', False), "
|
||||||
|
"('verbosity', 1)]") % (labels, option_a, option_b)
|
||||||
self.assertNoOutput(err)
|
self.assertNoOutput(err)
|
||||||
self.assertOutput(out, expected_out)
|
self.assertOutput(out, expected_out)
|
||||||
|
|
||||||
|
@ -1731,9 +1761,9 @@ class CommandTypes(AdminScriptTestCase):
|
||||||
self.assertNoOutput(err)
|
self.assertNoOutput(err)
|
||||||
self.assertOutput(
|
self.assertOutput(
|
||||||
out,
|
out,
|
||||||
"EXECUTE: noargs_command options=[('no_color', False), "
|
"EXECUTE: noargs_command options=[('force_color', False), "
|
||||||
"('pythonpath', None), ('settings', None), ('traceback', False), "
|
"('no_color', False), ('pythonpath', None), ('settings', None), "
|
||||||
"('verbosity', 1)]"
|
"('traceback', False), ('verbosity', 1)]"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_noargs_with_args(self):
|
def test_noargs_with_args(self):
|
||||||
|
@ -1750,8 +1780,9 @@ class CommandTypes(AdminScriptTestCase):
|
||||||
self.assertOutput(out, "EXECUTE:AppCommand name=django.contrib.auth, options=")
|
self.assertOutput(out, "EXECUTE:AppCommand name=django.contrib.auth, options=")
|
||||||
self.assertOutput(
|
self.assertOutput(
|
||||||
out,
|
out,
|
||||||
", options=[('no_color', False), ('pythonpath', None), "
|
", options=[('force_color', False), ('no_color', False), "
|
||||||
"('settings', None), ('traceback', False), ('verbosity', 1)]"
|
"('pythonpath', None), ('settings', None), ('traceback', False), "
|
||||||
|
"('verbosity', 1)]"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_app_command_no_apps(self):
|
def test_app_command_no_apps(self):
|
||||||
|
@ -1768,14 +1799,16 @@ class CommandTypes(AdminScriptTestCase):
|
||||||
self.assertOutput(out, "EXECUTE:AppCommand name=django.contrib.auth, options=")
|
self.assertOutput(out, "EXECUTE:AppCommand name=django.contrib.auth, options=")
|
||||||
self.assertOutput(
|
self.assertOutput(
|
||||||
out,
|
out,
|
||||||
", options=[('no_color', False), ('pythonpath', None), "
|
", options=[('force_color', False), ('no_color', False), "
|
||||||
"('settings', None), ('traceback', False), ('verbosity', 1)]"
|
"('pythonpath', None), ('settings', None), ('traceback', False), "
|
||||||
|
"('verbosity', 1)]"
|
||||||
)
|
)
|
||||||
self.assertOutput(out, "EXECUTE:AppCommand name=django.contrib.contenttypes, options=")
|
self.assertOutput(out, "EXECUTE:AppCommand name=django.contrib.contenttypes, options=")
|
||||||
self.assertOutput(
|
self.assertOutput(
|
||||||
out,
|
out,
|
||||||
", options=[('no_color', False), ('pythonpath', None), "
|
", options=[('force_color', False), ('no_color', False), "
|
||||||
"('settings', None), ('traceback', False), ('verbosity', 1)]"
|
"('pythonpath', None), ('settings', None), ('traceback', False), "
|
||||||
|
"('verbosity', 1)]"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_app_command_invalid_app_label(self):
|
def test_app_command_invalid_app_label(self):
|
||||||
|
@ -1797,8 +1830,9 @@ class CommandTypes(AdminScriptTestCase):
|
||||||
self.assertNoOutput(err)
|
self.assertNoOutput(err)
|
||||||
self.assertOutput(
|
self.assertOutput(
|
||||||
out,
|
out,
|
||||||
"EXECUTE:LabelCommand label=testlabel, options=[('no_color', False), "
|
"EXECUTE:LabelCommand label=testlabel, options=[('force_color', "
|
||||||
"('pythonpath', None), ('settings', None), ('traceback', False), ('verbosity', 1)]"
|
"False), ('no_color', False), ('pythonpath', None), ('settings', "
|
||||||
|
"None), ('traceback', False), ('verbosity', 1)]"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_label_command_no_label(self):
|
def test_label_command_no_label(self):
|
||||||
|
@ -1814,13 +1848,15 @@ class CommandTypes(AdminScriptTestCase):
|
||||||
self.assertNoOutput(err)
|
self.assertNoOutput(err)
|
||||||
self.assertOutput(
|
self.assertOutput(
|
||||||
out,
|
out,
|
||||||
"EXECUTE:LabelCommand label=testlabel, options=[('no_color', False), "
|
"EXECUTE:LabelCommand label=testlabel, options=[('force_color', "
|
||||||
"('pythonpath', None), ('settings', None), ('traceback', False), ('verbosity', 1)]"
|
"False), ('no_color', False), ('pythonpath', None), "
|
||||||
|
"('settings', None), ('traceback', False), ('verbosity', 1)]"
|
||||||
)
|
)
|
||||||
self.assertOutput(
|
self.assertOutput(
|
||||||
out,
|
out,
|
||||||
"EXECUTE:LabelCommand label=anotherlabel, options=[('no_color', False), "
|
"EXECUTE:LabelCommand label=anotherlabel, options=[('force_color', "
|
||||||
"('pythonpath', None), ('settings', None), ('traceback', False), ('verbosity', 1)]"
|
"False), ('no_color', False), ('pythonpath', None), "
|
||||||
|
"('settings', None), ('traceback', False), ('verbosity', 1)]"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1894,10 +1930,11 @@ class ArgumentOrder(AdminScriptTestCase):
|
||||||
self.assertNoOutput(err)
|
self.assertNoOutput(err)
|
||||||
self.assertOutput(
|
self.assertOutput(
|
||||||
out,
|
out,
|
||||||
"EXECUTE:BaseCommand labels=('testlabel',), options=[('no_color', False), "
|
"EXECUTE:BaseCommand labels=('testlabel',), options=["
|
||||||
"('option_a', 'x'), ('option_b', %s), ('option_c', '3'), "
|
"('force_color', False), ('no_color', False), ('option_a', 'x'), "
|
||||||
"('pythonpath', None), ('settings', 'alternate_settings'), "
|
"('option_b', %s), ('option_c', '3'), ('pythonpath', None), "
|
||||||
"('traceback', False), ('verbosity', 1)]" % option_b
|
"('settings', 'alternate_settings'), ('traceback', False), "
|
||||||
|
"('verbosity', 1)]" % option_b
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -179,18 +179,18 @@ class CommandTests(SimpleTestCase):
|
||||||
def test_call_command_unrecognized_option(self):
|
def test_call_command_unrecognized_option(self):
|
||||||
msg = (
|
msg = (
|
||||||
'Unknown option(s) for dance command: unrecognized. Valid options '
|
'Unknown option(s) for dance command: unrecognized. Valid options '
|
||||||
'are: example, help, integer, no_color, opt_3, option3, '
|
'are: example, force_color, help, integer, no_color, opt_3, '
|
||||||
'pythonpath, settings, skip_checks, stderr, stdout, style, '
|
'option3, pythonpath, settings, skip_checks, stderr, stdout, '
|
||||||
'traceback, verbosity, version.'
|
'style, traceback, verbosity, version.'
|
||||||
)
|
)
|
||||||
with self.assertRaisesMessage(TypeError, msg):
|
with self.assertRaisesMessage(TypeError, msg):
|
||||||
management.call_command('dance', unrecognized=1)
|
management.call_command('dance', unrecognized=1)
|
||||||
|
|
||||||
msg = (
|
msg = (
|
||||||
'Unknown option(s) for dance command: unrecognized, unrecognized2. '
|
'Unknown option(s) for dance command: unrecognized, unrecognized2. '
|
||||||
'Valid options are: example, help, integer, no_color, opt_3, '
|
'Valid options are: example, force_color, help, integer, no_color, '
|
||||||
'option3, pythonpath, settings, skip_checks, stderr, stdout, '
|
'opt_3, option3, pythonpath, settings, skip_checks, stderr, '
|
||||||
'style, traceback, verbosity, version.'
|
'stdout, style, traceback, verbosity, version.'
|
||||||
)
|
)
|
||||||
with self.assertRaisesMessage(TypeError, msg):
|
with self.assertRaisesMessage(TypeError, msg):
|
||||||
management.call_command('dance', unrecognized=1, unrecognized2=1)
|
management.call_command('dance', unrecognized=1, unrecognized2=1)
|
||||||
|
|
Loading…
Reference in New Issue