Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/django/core/management/base.py: 27%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

262 statements  

1""" 

2Base classes for writing management commands (named commands which can 

3be executed through ``django-admin`` or ``manage.py``). 

4""" 

5 

6import argparse 

7import os 

8import sys 

9from argparse import ArgumentParser, HelpFormatter 

10from functools import partial 

11from io import TextIOBase 

12 

13import django 

14from django.core import checks 

15from django.core.exceptions import ImproperlyConfigured 

16from django.core.management.color import color_style, no_style 

17from django.db import DEFAULT_DB_ALIAS, connections 

18 

19ALL_CHECKS = "__all__" 

20 

21 

22class CommandError(Exception): 

23 """ 

24 Exception class indicating a problem while executing a management 

25 command. 

26 

27 If this exception is raised during the execution of a management 

28 command, it will be caught and turned into a nicely-printed error 

29 message to the appropriate output stream (i.e., stderr); as a 

30 result, raising this exception (with a sensible description of the 

31 error) is the preferred way to indicate that something has gone 

32 wrong in the execution of a command. 

33 """ 

34 

35 def __init__(self, *args, returncode=1, **kwargs): 

36 self.returncode = returncode 

37 super().__init__(*args, **kwargs) 

38 

39 

40class SystemCheckError(CommandError): 

41 """ 

42 The system check framework detected unrecoverable errors. 

43 """ 

44 

45 pass 

46 

47 

48class CommandParser(ArgumentParser): 

49 """ 

50 Customized ArgumentParser class to improve some error messages and prevent 

51 SystemExit in several occasions, as SystemExit is unacceptable when a 

52 command is called programmatically. 

53 """ 

54 

55 def __init__( 

56 self, *, missing_args_message=None, called_from_command_line=None, **kwargs 

57 ): 

58 self.missing_args_message = missing_args_message 

59 self.called_from_command_line = called_from_command_line 

60 super().__init__(**kwargs) 

61 

62 def parse_args(self, args=None, namespace=None): 

63 # Catch missing argument for a better error message 

64 if self.missing_args_message and not ( 

65 args or any(not arg.startswith("-") for arg in args) 

66 ): 

67 self.error(self.missing_args_message) 

68 return super().parse_args(args, namespace) 

69 

70 def error(self, message): 

71 if self.called_from_command_line: 

72 super().error(message) 

73 else: 

74 raise CommandError("Error: %s" % message) 

75 

76 def add_subparsers(self, **kwargs): 

77 parser_class = kwargs.get("parser_class", type(self)) 

78 if issubclass(parser_class, CommandParser): 

79 kwargs["parser_class"] = partial( 

80 parser_class, 

81 called_from_command_line=self.called_from_command_line, 

82 ) 

83 return super().add_subparsers(**kwargs) 

84 

85 

86def handle_default_options(options): 

87 """ 

88 Include any default options that all commands should accept here 

89 so that ManagementUtility can handle them before searching for 

90 user commands. 

91 """ 

92 if options.settings: 

93 os.environ["DJANGO_SETTINGS_MODULE"] = options.settings 

94 if options.pythonpath: 

95 sys.path.insert(0, options.pythonpath) 

96 

97 

98def no_translations(handle_func): 

99 """Decorator that forces a command to run with translations deactivated.""" 

100 

101 def wrapper(*args, **kwargs): 

102 from django.utils import translation 

103 

104 saved_locale = translation.get_language() 

105 translation.deactivate_all() 

106 try: 

107 res = handle_func(*args, **kwargs) 

108 finally: 

109 if saved_locale is not None: 

110 translation.activate(saved_locale) 

111 return res 

112 

113 return wrapper 

114 

115 

116class DjangoHelpFormatter(HelpFormatter): 

117 """ 

118 Customized formatter so that command-specific arguments appear in the 

119 --help output before arguments common to all commands. 

120 """ 

121 

122 show_last = { 

123 "--version", 

124 "--verbosity", 

125 "--traceback", 

126 "--settings", 

127 "--pythonpath", 

128 "--no-color", 

129 "--force-color", 

130 "--skip-checks", 

131 } 

132 

133 def _reordered_actions(self, actions): 

134 return sorted( 

135 actions, key=lambda a: set(a.option_strings) & self.show_last != set() 

136 ) 

137 

138 def add_usage(self, usage, actions, *args, **kwargs): 

139 super().add_usage(usage, self._reordered_actions(actions), *args, **kwargs) 

140 

141 def add_arguments(self, actions): 

142 super().add_arguments(self._reordered_actions(actions)) 

143 

144 

145class OutputWrapper: 

146 """ 

147 Wrapper around stdout/stderr 

148 """ 

149 

150 @property 

151 def style_func(self): 

152 return self._style_func 

153 

154 @style_func.setter 

155 def style_func(self, style_func): 

156 if style_func and self.isatty(): 

157 self._style_func = style_func 

158 else: 

159 self._style_func = lambda x: x 

160 

161 def __init__(self, out, ending="\n"): 

162 self._out = out 

163 self.style_func = None 

164 self.ending = ending 

165 

166 def __getattr__(self, name): 

167 return getattr(self._out, name) 

168 

169 def flush(self): 

170 if hasattr(self._out, "flush"): 

171 self._out.flush() 

172 

173 def isatty(self): 

174 return hasattr(self._out, "isatty") and self._out.isatty() 

175 

176 def write(self, msg="", style_func=None, ending=None): 

177 ending = self.ending if ending is None else ending 

178 if ending and not msg.endswith(ending): 

179 msg += ending 

180 style_func = style_func or self.style_func 

181 self._out.write(style_func(msg)) 

182 

183 

184TextIOBase.register(OutputWrapper) 

185 

186 

187class BaseCommand: 

188 """ 

189 The base class from which all management commands ultimately 

190 derive. 

191 

192 Use this class if you want access to all of the mechanisms which 

193 parse the command-line arguments and work out what code to call in 

194 response; if you don't need to change any of that behavior, 

195 consider using one of the subclasses defined in this file. 

196 

197 If you are interested in overriding/customizing various aspects of 

198 the command-parsing and -execution behavior, the normal flow works 

199 as follows: 

200 

201 1. ``django-admin`` or ``manage.py`` loads the command class 

202 and calls its ``run_from_argv()`` method. 

203 

204 2. The ``run_from_argv()`` method calls ``create_parser()`` to get 

205 an ``ArgumentParser`` for the arguments, parses them, performs 

206 any environment changes requested by options like 

207 ``pythonpath``, and then calls the ``execute()`` method, 

208 passing the parsed arguments. 

209 

210 3. The ``execute()`` method attempts to carry out the command by 

211 calling the ``handle()`` method with the parsed arguments; any 

212 output produced by ``handle()`` will be printed to standard 

213 output and, if the command is intended to produce a block of 

214 SQL statements, will be wrapped in ``BEGIN`` and ``COMMIT``. 

215 

216 4. If ``handle()`` or ``execute()`` raised any exception (e.g. 

217 ``CommandError``), ``run_from_argv()`` will instead print an error 

218 message to ``stderr``. 

219 

220 Thus, the ``handle()`` method is typically the starting point for 

221 subclasses; many built-in commands and command types either place 

222 all of their logic in ``handle()``, or perform some additional 

223 parsing work in ``handle()`` and then delegate from it to more 

224 specialized methods as needed. 

225 

226 Several attributes affect behavior at various steps along the way: 

227 

228 ``help`` 

229 A short description of the command, which will be printed in 

230 help messages. 

231 

232 ``output_transaction`` 

233 A boolean indicating whether the command outputs SQL 

234 statements; if ``True``, the output will automatically be 

235 wrapped with ``BEGIN;`` and ``COMMIT;``. Default value is 

236 ``False``. 

237 

238 ``requires_migrations_checks`` 

239 A boolean; if ``True``, the command prints a warning if the set of 

240 migrations on disk don't match the migrations in the database. 

241 

242 ``requires_system_checks`` 

243 A list or tuple of tags, e.g. [Tags.staticfiles, Tags.models]. System 

244 checks registered in the chosen tags will be checked for errors prior 

245 to executing the command. The value '__all__' can be used to specify 

246 that all system checks should be performed. Default value is '__all__'. 

247 

248 To validate an individual application's models 

249 rather than all applications' models, call 

250 ``self.check(app_configs)`` from ``handle()``, where ``app_configs`` 

251 is the list of application's configuration provided by the 

252 app registry. 

253 

254 ``stealth_options`` 

255 A tuple of any options the command uses which aren't defined by the 

256 argument parser. 

257 """ 

258 

259 # Metadata about this command. 

260 help = "" 

261 

262 # Configuration shortcuts that alter various logic. 

263 _called_from_command_line = False 

264 output_transaction = False # Whether to wrap the output in a "BEGIN; COMMIT;" 

265 requires_migrations_checks = False 

266 requires_system_checks = "__all__" 

267 # Arguments, common to all commands, which aren't defined by the argument 

268 # parser. 

269 base_stealth_options = ("stderr", "stdout") 

270 # Command-specific options not defined by the argument parser. 

271 stealth_options = () 

272 suppressed_base_arguments = set() 

273 

274 def __init__(self, stdout=None, stderr=None, no_color=False, force_color=False): 

275 self.stdout = OutputWrapper(stdout or sys.stdout) 

276 self.stderr = OutputWrapper(stderr or sys.stderr) 

277 if no_color and force_color: 

278 raise CommandError("'no_color' and 'force_color' can't be used together.") 

279 if no_color: 

280 self.style = no_style() 

281 else: 

282 self.style = color_style(force_color) 

283 self.stderr.style_func = self.style.ERROR 

284 if ( 

285 not isinstance(self.requires_system_checks, (list, tuple)) 

286 and self.requires_system_checks != ALL_CHECKS 

287 ): 

288 raise TypeError("requires_system_checks must be a list or tuple.") 

289 

290 def get_version(self): 

291 """ 

292 Return the Django version, which should be correct for all built-in 

293 Django commands. User-supplied commands can override this method to 

294 return their own version. 

295 """ 

296 return django.get_version() 

297 

298 def create_parser(self, prog_name, subcommand, **kwargs): 

299 """ 

300 Create and return the ``ArgumentParser`` which will be used to 

301 parse the arguments to this command. 

302 """ 

303 kwargs.setdefault("formatter_class", DjangoHelpFormatter) 

304 parser = CommandParser( 

305 prog="%s %s" % (os.path.basename(prog_name), subcommand), 

306 description=self.help or None, 

307 missing_args_message=getattr(self, "missing_args_message", None), 

308 called_from_command_line=getattr(self, "_called_from_command_line", None), 

309 **kwargs, 

310 ) 

311 self.add_base_argument( 

312 parser, 

313 "--version", 

314 action="version", 

315 version=self.get_version(), 

316 help="Show program's version number and exit.", 

317 ) 

318 self.add_base_argument( 

319 parser, 

320 "-v", 

321 "--verbosity", 

322 default=1, 

323 type=int, 

324 choices=[0, 1, 2, 3], 

325 help=( 

326 "Verbosity level; 0=minimal output, 1=normal output, 2=verbose output, " 

327 "3=very verbose output" 

328 ), 

329 ) 

330 self.add_base_argument( 

331 parser, 

332 "--settings", 

333 help=( 

334 "The Python path to a settings module, e.g. " 

335 '"myproject.settings.main". If this isn\'t provided, the ' 

336 "DJANGO_SETTINGS_MODULE environment variable will be used." 

337 ), 

338 ) 

339 self.add_base_argument( 

340 parser, 

341 "--pythonpath", 

342 help=( 

343 "A directory to add to the Python path, e.g. " 

344 '"/home/djangoprojects/myproject".' 

345 ), 

346 ) 

347 self.add_base_argument( 

348 parser, 

349 "--traceback", 

350 action="store_true", 

351 help="Display a full stack trace on CommandError exceptions.", 

352 ) 

353 self.add_base_argument( 

354 parser, 

355 "--no-color", 

356 action="store_true", 

357 help="Don't colorize the command output.", 

358 ) 

359 self.add_base_argument( 

360 parser, 

361 "--force-color", 

362 action="store_true", 

363 help="Force colorization of the command output.", 

364 ) 

365 if self.requires_system_checks: 

366 parser.add_argument( 

367 "--skip-checks", 

368 action="store_true", 

369 help="Skip system checks.", 

370 ) 

371 self.add_arguments(parser) 

372 return parser 

373 

374 def add_arguments(self, parser): 

375 """ 

376 Entry point for subclassed commands to add custom arguments. 

377 """ 

378 pass 

379 

380 def add_base_argument(self, parser, *args, **kwargs): 

381 """ 

382 Call the parser's add_argument() method, suppressing the help text 

383 according to BaseCommand.suppressed_base_arguments. 

384 """ 

385 for arg in args: 

386 if arg in self.suppressed_base_arguments: 

387 kwargs["help"] = argparse.SUPPRESS 

388 break 

389 parser.add_argument(*args, **kwargs) 

390 

391 def print_help(self, prog_name, subcommand): 

392 """ 

393 Print the help message for this command, derived from 

394 ``self.usage()``. 

395 """ 

396 parser = self.create_parser(prog_name, subcommand) 

397 parser.print_help() 

398 

399 def run_from_argv(self, argv): 

400 """ 

401 Set up any environment changes requested (e.g., Python path 

402 and Django settings), then run this command. If the 

403 command raises a ``CommandError``, intercept it and print it sensibly 

404 to stderr. If the ``--traceback`` option is present or the raised 

405 ``Exception`` is not ``CommandError``, raise it. 

406 """ 

407 self._called_from_command_line = True 

408 parser = self.create_parser(argv[0], argv[1]) 

409 

410 options = parser.parse_args(argv[2:]) 

411 cmd_options = vars(options) 

412 # Move positional args out of options to mimic legacy optparse 

413 args = cmd_options.pop("args", ()) 

414 handle_default_options(options) 

415 try: 

416 self.execute(*args, **cmd_options) 

417 except CommandError as e: 

418 if options.traceback: 

419 raise 

420 

421 # SystemCheckError takes care of its own formatting. 

422 if isinstance(e, SystemCheckError): 

423 self.stderr.write(str(e), lambda x: x) 

424 else: 

425 self.stderr.write("%s: %s" % (e.__class__.__name__, e)) 

426 sys.exit(e.returncode) 

427 finally: 

428 try: 

429 connections.close_all() 

430 except ImproperlyConfigured: 

431 # Ignore if connections aren't setup at this point (e.g. no 

432 # configured settings). 

433 pass 

434 

435 def execute(self, *args, **options): 

436 """ 

437 Try to execute this command, performing system checks if needed (as 

438 controlled by the ``requires_system_checks`` attribute, except if 

439 force-skipped). 

440 """ 

441 if options["force_color"] and options["no_color"]: 

442 raise CommandError( 

443 "The --no-color and --force-color options can't be used together." 

444 ) 

445 if options["force_color"]: 

446 self.style = color_style(force_color=True) 

447 elif options["no_color"]: 

448 self.style = no_style() 

449 self.stderr.style_func = None 

450 if options.get("stdout"): 

451 self.stdout = OutputWrapper(options["stdout"]) 

452 if options.get("stderr"): 

453 self.stderr = OutputWrapper(options["stderr"]) 

454 

455 if self.requires_system_checks and not options["skip_checks"]: 

456 check_kwargs = self.get_check_kwargs(options) 

457 self.check(**check_kwargs) 

458 if self.requires_migrations_checks: 

459 self.check_migrations() 

460 output = self.handle(*args, **options) 

461 if output: 

462 if self.output_transaction: 

463 connection = connections[options.get("database", DEFAULT_DB_ALIAS)] 

464 output = "%s\n%s\n%s" % ( 

465 self.style.SQL_KEYWORD(connection.ops.start_transaction_sql()), 

466 output, 

467 self.style.SQL_KEYWORD(connection.ops.end_transaction_sql()), 

468 ) 

469 self.stdout.write(output) 

470 return output 

471 

472 def get_check_kwargs(self, options): 

473 if self.requires_system_checks == ALL_CHECKS: 

474 return {} 

475 return {"tags": self.requires_system_checks} 

476 

477 def check( 

478 self, 

479 app_configs=None, 

480 tags=None, 

481 display_num_errors=False, 

482 include_deployment_checks=False, 

483 fail_level=checks.ERROR, 

484 databases=None, 

485 ): 

486 """ 

487 Use the system check framework to validate entire Django project. 

488 Raise CommandError for any serious message (error or critical errors). 

489 If there are only light messages (like warnings), print them to stderr 

490 and don't raise an exception. 

491 """ 

492 all_issues = checks.run_checks( 

493 app_configs=app_configs, 

494 tags=tags, 

495 include_deployment_checks=include_deployment_checks, 

496 databases=databases, 

497 ) 

498 

499 header, body, footer = "", "", "" 

500 visible_issue_count = 0 # excludes silenced warnings 

501 

502 if all_issues: 

503 debugs = [ 

504 e for e in all_issues if e.level < checks.INFO and not e.is_silenced() 

505 ] 

506 infos = [ 

507 e 

508 for e in all_issues 

509 if checks.INFO <= e.level < checks.WARNING and not e.is_silenced() 

510 ] 

511 warnings = [ 

512 e 

513 for e in all_issues 

514 if checks.WARNING <= e.level < checks.ERROR and not e.is_silenced() 

515 ] 

516 errors = [ 

517 e 

518 for e in all_issues 

519 if checks.ERROR <= e.level < checks.CRITICAL and not e.is_silenced() 

520 ] 

521 criticals = [ 

522 e 

523 for e in all_issues 

524 if checks.CRITICAL <= e.level and not e.is_silenced() 

525 ] 

526 sorted_issues = [ 

527 (criticals, "CRITICALS"), 

528 (errors, "ERRORS"), 

529 (warnings, "WARNINGS"), 

530 (infos, "INFOS"), 

531 (debugs, "DEBUGS"), 

532 ] 

533 

534 for issues, group_name in sorted_issues: 

535 if issues: 

536 visible_issue_count += len(issues) 

537 formatted = ( 

538 ( 

539 self.style.ERROR(str(e)) 

540 if e.is_serious() 

541 else self.style.WARNING(str(e)) 

542 ) 

543 for e in issues 

544 ) 

545 formatted = "\n".join(sorted(formatted)) 

546 body += "\n%s:\n%s\n" % (group_name, formatted) 

547 

548 if visible_issue_count: 

549 header = "System check identified some issues:\n" 

550 

551 if display_num_errors: 

552 if visible_issue_count: 

553 footer += "\n" 

554 footer += "System check identified %s (%s silenced)." % ( 

555 ( 

556 "no issues" 

557 if visible_issue_count == 0 

558 else ( 

559 "1 issue" 

560 if visible_issue_count == 1 

561 else "%s issues" % visible_issue_count 

562 ) 

563 ), 

564 len(all_issues) - visible_issue_count, 

565 ) 

566 

567 if any(e.is_serious(fail_level) and not e.is_silenced() for e in all_issues): 

568 msg = self.style.ERROR("SystemCheckError: %s" % header) + body + footer 

569 raise SystemCheckError(msg) 

570 else: 

571 msg = header + body + footer 

572 

573 if msg: 

574 if visible_issue_count: 

575 self.stderr.write(msg, lambda x: x) 

576 else: 

577 self.stdout.write(msg) 

578 

579 def check_migrations(self): 

580 """ 

581 Print a warning if the set of migrations on disk don't match the 

582 migrations in the database. 

583 """ 

584 from django.db.migrations.executor import MigrationExecutor 

585 

586 try: 

587 executor = MigrationExecutor(connections[DEFAULT_DB_ALIAS]) 

588 except ImproperlyConfigured: 

589 # No databases are configured (or the dummy one) 

590 return 

591 

592 plan = executor.migration_plan(executor.loader.graph.leaf_nodes()) 

593 if plan: 

594 apps_waiting_migration = sorted( 

595 {migration.app_label for migration, backwards in plan} 

596 ) 

597 self.stdout.write( 

598 self.style.NOTICE( 

599 "\nYou have %(unapplied_migration_count)s unapplied migration(s). " 

600 "Your project may not work properly until you apply the " 

601 "migrations for app(s): %(apps_waiting_migration)s." 

602 % { 

603 "unapplied_migration_count": len(plan), 

604 "apps_waiting_migration": ", ".join(apps_waiting_migration), 

605 } 

606 ) 

607 ) 

608 self.stdout.write( 

609 self.style.NOTICE("Run 'python manage.py migrate' to apply them.") 

610 ) 

611 

612 def handle(self, *args, **options): 

613 """ 

614 The actual logic of the command. Subclasses must implement 

615 this method. 

616 """ 

617 raise NotImplementedError( 

618 "subclasses of BaseCommand must provide a handle() method" 

619 ) 

620 

621 

622class AppCommand(BaseCommand): 

623 """ 

624 A management command which takes one or more installed application labels 

625 as arguments, and does something with each of them. 

626 

627 Rather than implementing ``handle()``, subclasses must implement 

628 ``handle_app_config()``, which will be called once for each application. 

629 """ 

630 

631 missing_args_message = "Enter at least one application label." 

632 

633 def add_arguments(self, parser): 

634 parser.add_argument( 

635 "args", 

636 metavar="app_label", 

637 nargs="+", 

638 help="One or more application label.", 

639 ) 

640 

641 def handle(self, *app_labels, **options): 

642 from django.apps import apps 

643 

644 try: 

645 app_configs = [apps.get_app_config(app_label) for app_label in app_labels] 

646 except (LookupError, ImportError) as e: 

647 raise CommandError( 

648 "%s. Are you sure your INSTALLED_APPS setting is correct?" % e 

649 ) 

650 output = [] 

651 for app_config in app_configs: 

652 app_output = self.handle_app_config(app_config, **options) 

653 if app_output: 

654 output.append(app_output) 

655 return "\n".join(output) 

656 

657 def handle_app_config(self, app_config, **options): 

658 """ 

659 Perform the command's actions for app_config, an AppConfig instance 

660 corresponding to an application label given on the command line. 

661 """ 

662 raise NotImplementedError( 

663 "Subclasses of AppCommand must provide a handle_app_config() method." 

664 ) 

665 

666 

667class LabelCommand(BaseCommand): 

668 """ 

669 A management command which takes one or more arbitrary arguments 

670 (labels) on the command line, and does something with each of 

671 them. 

672 

673 Rather than implementing ``handle()``, subclasses must implement 

674 ``handle_label()``, which will be called once for each label. 

675 

676 If the arguments should be names of installed applications, use 

677 ``AppCommand`` instead. 

678 """ 

679 

680 label = "label" 

681 missing_args_message = "Enter at least one %s." 

682 

683 def __init__(self, *args, **kwargs): 

684 super().__init__(*args, **kwargs) 

685 

686 if self.missing_args_message == LabelCommand.missing_args_message: 

687 self.missing_args_message = self.missing_args_message % self.label 

688 

689 def add_arguments(self, parser): 

690 parser.add_argument("args", metavar=self.label, nargs="+") 

691 

692 def handle(self, *labels, **options): 

693 output = [] 

694 for label in labels: 

695 label_output = self.handle_label(label, **options) 

696 if label_output: 

697 output.append(label_output) 

698 return "\n".join(output) 

699 

700 def handle_label(self, label, **options): 

701 """ 

702 Perform the command's actions for ``label``, which will be the 

703 string as given on the command line. 

704 """ 

705 raise NotImplementedError( 

706 "subclasses of LabelCommand must provide a handle_label() method" 

707 )