Fixed #27787 -- Made call_command() validate the options it receives.
This commit is contained in:
parent
92e286498a
commit
2b09e4c88e
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
-------------
|
||||
|
||||
|
|
|
@ -320,7 +320,6 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
|
|||
call_command(
|
||||
"createsuperuser",
|
||||
interactive=False,
|
||||
username="joe@somewhere.org",
|
||||
stdout=new_io,
|
||||
stderr=new_io,
|
||||
)
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue