mirror of https://github.com/django/django.git
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):
|
class Command(BaseCommand):
|
||||||
help = 'Used to create a superuser.'
|
help = 'Used to create a superuser.'
|
||||||
requires_migrations_checks = True
|
requires_migrations_checks = True
|
||||||
|
stealth_options = ('stdin',)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*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()}
|
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 = parser.parse_args(args=[force_text(a) for a in args])
|
||||||
defaults = dict(defaults._get_kwargs(), **arg_options)
|
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
|
# Move positional args out of options to mimic legacy optparse
|
||||||
args = defaults.pop('args', ())
|
args = defaults.pop('args', ())
|
||||||
if 'skip_checks' not in options:
|
if 'skip_checks' not in options:
|
||||||
|
|
|
@ -183,6 +183,10 @@ class BaseCommand:
|
||||||
that is locale-sensitive and such content shouldn't contain any
|
that is locale-sensitive and such content shouldn't contain any
|
||||||
translations (like it happens e.g. with django.contrib.auth
|
translations (like it happens e.g. with django.contrib.auth
|
||||||
permissions) as activating any locale might cause unintended effects.
|
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.
|
# Metadata about this command.
|
||||||
help = ''
|
help = ''
|
||||||
|
@ -193,6 +197,11 @@ class BaseCommand:
|
||||||
leave_locale_alone = False
|
leave_locale_alone = False
|
||||||
requires_migrations_checks = False
|
requires_migrations_checks = False
|
||||||
requires_system_checks = True
|
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):
|
def __init__(self, stdout=None, stderr=None, no_color=False):
|
||||||
self.stdout = OutputWrapper(stdout or sys.stdout)
|
self.stdout = OutputWrapper(stdout or sys.stdout)
|
||||||
|
|
|
@ -12,6 +12,7 @@ class Command(BaseCommand):
|
||||||
'Removes ALL DATA from the database, including data added during '
|
'Removes ALL DATA from the database, including data added during '
|
||||||
'migrations. Does not achieve a "fresh install" state.'
|
'migrations. Does not achieve a "fresh install" state.'
|
||||||
)
|
)
|
||||||
|
stealth_options = ('reset_sequences', 'allow_cascade', 'inhibit_post_migrate')
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
|
|
|
@ -9,9 +9,8 @@ from django.db.models.constants import LOOKUP_SEP
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = "Introspects the database tables in the given database and outputs a Django model module."
|
help = "Introspects the database tables in the given database and outputs a Django model module."
|
||||||
|
|
||||||
requires_system_checks = False
|
requires_system_checks = False
|
||||||
|
stealth_options = ('table_name_filter', )
|
||||||
db_module = 'django.db'
|
db_module = 'django.db'
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
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)
|
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
|
Miscellaneous
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
|
|
@ -320,7 +320,6 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
|
||||||
call_command(
|
call_command(
|
||||||
"createsuperuser",
|
"createsuperuser",
|
||||||
interactive=False,
|
interactive=False,
|
||||||
username="joe@somewhere.org",
|
|
||||||
stdout=new_io,
|
stdout=new_io,
|
||||||
stderr=new_io,
|
stderr=new_io,
|
||||||
)
|
)
|
||||||
|
|
|
@ -175,6 +175,25 @@ class CommandTests(SimpleTestCase):
|
||||||
finally:
|
finally:
|
||||||
dance.Command.requires_migrations_checks = requires_migrations_checks
|
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):
|
class CommandRunTests(AdminScriptTestCase):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue