mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #27787 -- Made call_command() validate the options it receives.
This commit is contained in:
		
				
					committed by
					
						 Tim Graham
						Tim Graham
					
				
			
			
				
	
			
			
			
						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): | ||||
|     """ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user