Fixed #27787 -- Made call_command() validate the options it receives.

This commit is contained in:
Chandrakant Kumar 2017-01-28 16:02:33 +05:30 committed by Tim Graham
parent 92e286498a
commit 2b09e4c88e
8 changed files with 57 additions and 3 deletions

View File

@ -20,6 +20,7 @@ class NotRunningInTTYException(Exception):
class Command(BaseCommand):
help = 'Used to create a superuser.'
requires_migrations_checks = True
stealth_options = ('stdin',)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

View File

@ -118,6 +118,20 @@ def call_command(command_name, *args, **options):
arg_options = {opt_mapping.get(key, key): value for key, value in options.items()}
defaults = parser.parse_args(args=[force_text(a) for a in args])
defaults = dict(defaults._get_kwargs(), **arg_options)
# Raise an error if any unknown options were passed.
stealth_options = set(command.base_stealth_options + command.stealth_options)
dest_parameters = {action.dest for action in parser._actions}
valid_options = dest_parameters | stealth_options | set(opt_mapping)
unknown_options = set(options) - valid_options
if unknown_options:
raise TypeError(
"Unknown option(s) for %s command: %s. "
"Valid options are: %s." % (
command_name,
', '.join(sorted(unknown_options)),
', '.join(sorted(valid_options)),
)
)
# Move positional args out of options to mimic legacy optparse
args = defaults.pop('args', ())
if 'skip_checks' not in options:

View File

@ -183,6 +183,10 @@ class BaseCommand:
that is locale-sensitive and such content shouldn't contain any
translations (like it happens e.g. with django.contrib.auth
permissions) as activating any locale might cause unintended effects.
``stealth_options``
A tuple of any options the command uses which aren't defined by the
argument parser.
"""
# Metadata about this command.
help = ''
@ -193,6 +197,11 @@ class BaseCommand:
leave_locale_alone = False
requires_migrations_checks = False
requires_system_checks = True
# Arguments, common to all commands, which aren't defined by the argument
# parser.
base_stealth_options = ('skip_checks', 'stderr', 'stdout')
# Command-specific options not defined by the argument parser.
stealth_options = ()
def __init__(self, stdout=None, stderr=None, no_color=False):
self.stdout = OutputWrapper(stdout or sys.stdout)

View File

@ -12,6 +12,7 @@ class Command(BaseCommand):
'Removes ALL DATA from the database, including data added during '
'migrations. Does not achieve a "fresh install" state.'
)
stealth_options = ('reset_sequences', 'allow_cascade', 'inhibit_post_migrate')
def add_arguments(self, parser):
parser.add_argument(

View File

@ -9,9 +9,8 @@ from django.db.models.constants import LOOKUP_SEP
class Command(BaseCommand):
help = "Introspects the database tables in the given database and outputs a Django model module."
requires_system_checks = False
stealth_options = ('table_name_filter', )
db_module = 'django.db'
def add_arguments(self, parser):

View File

@ -384,6 +384,18 @@ raises an exception and should be replaced with::
forms.IntegerField(max_value=25, min_value=10)
``call_command()`` validates the options it receives
----------------------------------------------------
``call_command()`` now validates that the argument parser of the command being
called defines all of the options passed to ``call_command()``.
For custom management commands that use options not created using
``parser.add_argument()``, add a ``stealth_options`` attribute on the command::
class MyCommand(BaseCommand):
stealth_options = ('option_name', ...)
Miscellaneous
-------------

View File

@ -320,7 +320,6 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
call_command(
"createsuperuser",
interactive=False,
username="joe@somewhere.org",
stdout=new_io,
stderr=new_io,
)

View File

@ -175,6 +175,25 @@ class CommandTests(SimpleTestCase):
finally:
dance.Command.requires_migrations_checks = requires_migrations_checks
def test_call_command_unrecognized_option(self):
msg = (
'Unknown option(s) for dance command: unrecognized. Valid options '
'are: example, help, integer, no_color, opt_3, option3, '
'pythonpath, settings, skip_checks, stderr, stdout, style, '
'traceback, verbosity, version.'
)
with self.assertRaisesMessage(TypeError, msg):
management.call_command('dance', unrecognized=1)
msg = (
'Unknown option(s) for dance command: unrecognized, unrecognized2. '
'Valid options are: example, help, integer, no_color, opt_3, '
'option3, pythonpath, settings, skip_checks, stderr, stdout, '
'style, traceback, verbosity, version.'
)
with self.assertRaisesMessage(TypeError, msg):
management.call_command('dance', unrecognized=1, unrecognized2=1)
class CommandRunTests(AdminScriptTestCase):
"""