Kaydet (Commit) 2b09e4c8 authored tarafından Chandrakant Kumar's avatar Chandrakant Kumar Kaydeden (comit) Tim Graham

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

üst 92e28649
...@@ -20,6 +20,7 @@ class NotRunningInTTYException(Exception): ...@@ -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): ...@@ -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: ...@@ -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: ...@@ -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): ...@@ -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 ...@@ -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:: ...@@ -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): ...@@ -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): ...@@ -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):
""" """
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment