Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/core/management/base.py: 55%
252 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1"""
2Base classes for writing management commands (named commands which can
3be executed through ``django-admin`` or ``manage.py``).
4"""
5import argparse
6import os
7import sys
8import warnings
9from argparse import ArgumentParser, HelpFormatter
10from io import TextIOBase
12import django
13from django.core import checks
14from django.core.exceptions import ImproperlyConfigured
15from django.core.management.color import color_style, no_style
16from django.db import DEFAULT_DB_ALIAS, connections
17from django.utils.deprecation import RemovedInDjango41Warning
19ALL_CHECKS = "__all__"
22class CommandError(Exception):
23 """
24 Exception class indicating a problem while executing a management
25 command.
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 """
35 def __init__(self, *args, returncode=1, **kwargs):
36 self.returncode = returncode
37 super().__init__(*args, **kwargs)
40class SystemCheckError(CommandError):
41 """
42 The system check framework detected unrecoverable errors.
43 """
45 pass
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 """
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)
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 ( 64 ↛ exit, 64 ↛ 672 missed branches: 1) line 64 didn't jump to the function exit, 2) line 64 didn't jump to line 67, because the condition on line 64 was never true
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)
70 def error(self, message):
71 if self.called_from_command_line:
72 super().error(message)
73 else:
74 raise CommandError("Error: %s" % message)
77def handle_default_options(options):
78 """
79 Include any default options that all commands should accept here
80 so that ManagementUtility can handle them before searching for
81 user commands.
82 """
83 if options.settings: 83 ↛ 84line 83 didn't jump to line 84, because the condition on line 83 was never true
84 os.environ["DJANGO_SETTINGS_MODULE"] = options.settings
85 if options.pythonpath: 85 ↛ 86line 85 didn't jump to line 86, because the condition on line 85 was never true
86 sys.path.insert(0, options.pythonpath)
89def no_translations(handle_func):
90 """Decorator that forces a command to run with translations deactivated."""
92 def wrapped(*args, **kwargs):
93 from django.utils import translation
95 saved_locale = translation.get_language()
96 translation.deactivate_all()
97 try:
98 res = handle_func(*args, **kwargs)
99 finally:
100 if saved_locale is not None: 100 ↛ 102line 100 didn't jump to line 102, because the condition on line 100 was never false
101 translation.activate(saved_locale)
102 return res
104 return wrapped
107class DjangoHelpFormatter(HelpFormatter):
108 """
109 Customized formatter so that command-specific arguments appear in the
110 --help output before arguments common to all commands.
111 """
113 show_last = {
114 "--version",
115 "--verbosity",
116 "--traceback",
117 "--settings",
118 "--pythonpath",
119 "--no-color",
120 "--force-color",
121 "--skip-checks",
122 }
124 def _reordered_actions(self, actions):
125 return sorted(
126 actions, key=lambda a: set(a.option_strings) & self.show_last != set()
127 )
129 def add_usage(self, usage, actions, *args, **kwargs):
130 super().add_usage(usage, self._reordered_actions(actions), *args, **kwargs)
132 def add_arguments(self, actions):
133 super().add_arguments(self._reordered_actions(actions))
136class OutputWrapper(TextIOBase):
137 """
138 Wrapper around stdout/stderr
139 """
141 @property
142 def style_func(self):
143 return self._style_func
145 @style_func.setter
146 def style_func(self, style_func):
147 if style_func and self.isatty():
148 self._style_func = style_func
149 else:
150 self._style_func = lambda x: x
152 def __init__(self, out, ending="\n"):
153 self._out = out
154 self.style_func = None
155 self.ending = ending
157 def __getattr__(self, name):
158 return getattr(self._out, name)
160 def flush(self):
161 if hasattr(self._out, "flush"): 161 ↛ exitline 161 didn't return from function 'flush', because the condition on line 161 was never false
162 self._out.flush()
164 def isatty(self):
165 return hasattr(self._out, "isatty") and self._out.isatty()
167 def write(self, msg="", style_func=None, ending=None):
168 ending = self.ending if ending is None else ending
169 if ending and not msg.endswith(ending): 169 ↛ 171line 169 didn't jump to line 171, because the condition on line 169 was never false
170 msg += ending
171 style_func = style_func or self.style_func
172 self._out.write(style_func(msg))
175class BaseCommand:
176 """
177 The base class from which all management commands ultimately
178 derive.
180 Use this class if you want access to all of the mechanisms which
181 parse the command-line arguments and work out what code to call in
182 response; if you don't need to change any of that behavior,
183 consider using one of the subclasses defined in this file.
185 If you are interested in overriding/customizing various aspects of
186 the command-parsing and -execution behavior, the normal flow works
187 as follows:
189 1. ``django-admin`` or ``manage.py`` loads the command class
190 and calls its ``run_from_argv()`` method.
192 2. The ``run_from_argv()`` method calls ``create_parser()`` to get
193 an ``ArgumentParser`` for the arguments, parses them, performs
194 any environment changes requested by options like
195 ``pythonpath``, and then calls the ``execute()`` method,
196 passing the parsed arguments.
198 3. The ``execute()`` method attempts to carry out the command by
199 calling the ``handle()`` method with the parsed arguments; any
200 output produced by ``handle()`` will be printed to standard
201 output and, if the command is intended to produce a block of
202 SQL statements, will be wrapped in ``BEGIN`` and ``COMMIT``.
204 4. If ``handle()`` or ``execute()`` raised any exception (e.g.
205 ``CommandError``), ``run_from_argv()`` will instead print an error
206 message to ``stderr``.
208 Thus, the ``handle()`` method is typically the starting point for
209 subclasses; many built-in commands and command types either place
210 all of their logic in ``handle()``, or perform some additional
211 parsing work in ``handle()`` and then delegate from it to more
212 specialized methods as needed.
214 Several attributes affect behavior at various steps along the way:
216 ``help``
217 A short description of the command, which will be printed in
218 help messages.
220 ``output_transaction``
221 A boolean indicating whether the command outputs SQL
222 statements; if ``True``, the output will automatically be
223 wrapped with ``BEGIN;`` and ``COMMIT;``. Default value is
224 ``False``.
226 ``requires_migrations_checks``
227 A boolean; if ``True``, the command prints a warning if the set of
228 migrations on disk don't match the migrations in the database.
230 ``requires_system_checks``
231 A list or tuple of tags, e.g. [Tags.staticfiles, Tags.models]. System
232 checks registered in the chosen tags will be checked for errors prior
233 to executing the command. The value '__all__' can be used to specify
234 that all system checks should be performed. Default value is '__all__'.
236 To validate an individual application's models
237 rather than all applications' models, call
238 ``self.check(app_configs)`` from ``handle()``, where ``app_configs``
239 is the list of application's configuration provided by the
240 app registry.
242 ``stealth_options``
243 A tuple of any options the command uses which aren't defined by the
244 argument parser.
245 """
247 # Metadata about this command.
248 help = ""
250 # Configuration shortcuts that alter various logic.
251 _called_from_command_line = False
252 output_transaction = False # Whether to wrap the output in a "BEGIN; COMMIT;"
253 requires_migrations_checks = False
254 requires_system_checks = "__all__"
255 # Arguments, common to all commands, which aren't defined by the argument
256 # parser.
257 base_stealth_options = ("stderr", "stdout")
258 # Command-specific options not defined by the argument parser.
259 stealth_options = ()
260 suppressed_base_arguments = set()
262 def __init__(self, stdout=None, stderr=None, no_color=False, force_color=False):
263 self.stdout = OutputWrapper(stdout or sys.stdout)
264 self.stderr = OutputWrapper(stderr or sys.stderr)
265 if no_color and force_color: 265 ↛ 266line 265 didn't jump to line 266, because the condition on line 265 was never true
266 raise CommandError("'no_color' and 'force_color' can't be used together.")
267 if no_color: 267 ↛ 268line 267 didn't jump to line 268, because the condition on line 267 was never true
268 self.style = no_style()
269 else:
270 self.style = color_style(force_color)
271 self.stderr.style_func = self.style.ERROR
272 if self.requires_system_checks in [False, True]: 272 ↛ 273line 272 didn't jump to line 273, because the condition on line 272 was never true
273 warnings.warn(
274 "Using a boolean value for requires_system_checks is "
275 "deprecated. Use '__all__' instead of True, and [] (an empty "
276 "list) instead of False.",
277 RemovedInDjango41Warning,
278 )
279 self.requires_system_checks = (
280 ALL_CHECKS if self.requires_system_checks else []
281 )
282 if ( 282 ↛ 286line 282 didn't jump to line 286
283 not isinstance(self.requires_system_checks, (list, tuple))
284 and self.requires_system_checks != ALL_CHECKS
285 ):
286 raise TypeError("requires_system_checks must be a list or tuple.")
288 def get_version(self):
289 """
290 Return the Django version, which should be correct for all built-in
291 Django commands. User-supplied commands can override this method to
292 return their own version.
293 """
294 return django.get_version()
296 def create_parser(self, prog_name, subcommand, **kwargs):
297 """
298 Create and return the ``ArgumentParser`` which will be used to
299 parse the arguments to this command.
300 """
301 parser = CommandParser(
302 prog="%s %s" % (os.path.basename(prog_name), subcommand),
303 description=self.help or None,
304 formatter_class=DjangoHelpFormatter,
305 missing_args_message=getattr(self, "missing_args_message", None),
306 called_from_command_line=getattr(self, "_called_from_command_line", None),
307 **kwargs,
308 )
309 self.add_base_argument(
310 parser,
311 "--version",
312 action="version",
313 version=self.get_version(),
314 help="Show program's version number and exit.",
315 )
316 self.add_base_argument(
317 parser,
318 "-v",
319 "--verbosity",
320 default=1,
321 type=int,
322 choices=[0, 1, 2, 3],
323 help=(
324 "Verbosity level; 0=minimal output, 1=normal output, 2=verbose output, "
325 "3=very verbose output"
326 ),
327 )
328 self.add_base_argument(
329 parser,
330 "--settings",
331 help=(
332 "The Python path to a settings module, e.g. "
333 '"myproject.settings.main". If this isn\'t provided, the '
334 "DJANGO_SETTINGS_MODULE environment variable will be used."
335 ),
336 )
337 self.add_base_argument(
338 parser,
339 "--pythonpath",
340 help=(
341 "A directory to add to the Python path, e.g. "
342 '"/home/djangoprojects/myproject".'
343 ),
344 )
345 self.add_base_argument(
346 parser,
347 "--traceback",
348 action="store_true",
349 help="Raise on CommandError exceptions.",
350 )
351 self.add_base_argument(
352 parser,
353 "--no-color",
354 action="store_true",
355 help="Don't colorize the command output.",
356 )
357 self.add_base_argument(
358 parser,
359 "--force-color",
360 action="store_true",
361 help="Force colorization of the command output.",
362 )
363 if self.requires_system_checks: 363 ↛ 364line 363 didn't jump to line 364, because the condition on line 363 was never true
364 parser.add_argument(
365 "--skip-checks",
366 action="store_true",
367 help="Skip system checks.",
368 )
369 self.add_arguments(parser)
370 return parser
372 def add_arguments(self, parser):
373 """
374 Entry point for subclassed commands to add custom arguments.
375 """
376 pass
378 def add_base_argument(self, parser, *args, **kwargs):
379 """
380 Call the parser's add_argument() method, suppressing the help text
381 according to BaseCommand.suppressed_base_arguments.
382 """
383 for arg in args:
384 if arg in self.suppressed_base_arguments: 384 ↛ 385line 384 didn't jump to line 385, because the condition on line 384 was never true
385 kwargs["help"] = argparse.SUPPRESS
386 break
387 parser.add_argument(*args, **kwargs)
389 def print_help(self, prog_name, subcommand):
390 """
391 Print the help message for this command, derived from
392 ``self.usage()``.
393 """
394 parser = self.create_parser(prog_name, subcommand)
395 parser.print_help()
397 def run_from_argv(self, argv):
398 """
399 Set up any environment changes requested (e.g., Python path
400 and Django settings), then run this command. If the
401 command raises a ``CommandError``, intercept it and print it sensibly
402 to stderr. If the ``--traceback`` option is present or the raised
403 ``Exception`` is not ``CommandError``, raise it.
404 """
405 self._called_from_command_line = True
406 parser = self.create_parser(argv[0], argv[1])
408 options = parser.parse_args(argv[2:])
409 cmd_options = vars(options)
410 # Move positional args out of options to mimic legacy optparse
411 args = cmd_options.pop("args", ())
412 handle_default_options(options)
413 try:
414 self.execute(*args, **cmd_options)
415 except CommandError as e:
416 if options.traceback:
417 raise
419 # SystemCheckError takes care of its own formatting.
420 if isinstance(e, SystemCheckError):
421 self.stderr.write(str(e), lambda x: x)
422 else:
423 self.stderr.write("%s: %s" % (e.__class__.__name__, e))
424 sys.exit(e.returncode)
425 finally:
426 try:
427 connections.close_all()
428 except ImproperlyConfigured:
429 # Ignore if connections aren't setup at this point (e.g. no
430 # configured settings).
431 pass
433 def execute(self, *args, **options):
434 """
435 Try to execute this command, performing system checks if needed (as
436 controlled by the ``requires_system_checks`` attribute, except if
437 force-skipped).
438 """
439 if options["force_color"] and options["no_color"]: 439 ↛ 440line 439 didn't jump to line 440, because the condition on line 439 was never true
440 raise CommandError(
441 "The --no-color and --force-color options can't be used together."
442 )
443 if options["force_color"]: 443 ↛ 444line 443 didn't jump to line 444, because the condition on line 443 was never true
444 self.style = color_style(force_color=True)
445 elif options["no_color"]: 445 ↛ 446line 445 didn't jump to line 446, because the condition on line 445 was never true
446 self.style = no_style()
447 self.stderr.style_func = None
448 if options.get("stdout"): 448 ↛ 449line 448 didn't jump to line 449, because the condition on line 448 was never true
449 self.stdout = OutputWrapper(options["stdout"])
450 if options.get("stderr"): 450 ↛ 451line 450 didn't jump to line 451, because the condition on line 450 was never true
451 self.stderr = OutputWrapper(options["stderr"])
453 if self.requires_system_checks and not options["skip_checks"]: 453 ↛ 454line 453 didn't jump to line 454, because the condition on line 453 was never true
454 if self.requires_system_checks == ALL_CHECKS:
455 self.check()
456 else:
457 self.check(tags=self.requires_system_checks)
458 if self.requires_migrations_checks: 458 ↛ 459line 458 didn't jump to line 459, because the condition on line 458 was never true
459 self.check_migrations()
460 output = self.handle(*args, **options)
461 if output: 461 ↛ 462line 461 didn't jump to line 462, because the condition on line 461 was never true
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
472 def check(
473 self,
474 app_configs=None,
475 tags=None,
476 display_num_errors=False,
477 include_deployment_checks=False,
478 fail_level=checks.ERROR,
479 databases=None,
480 ):
481 """
482 Use the system check framework to validate entire Django project.
483 Raise CommandError for any serious message (error or critical errors).
484 If there are only light messages (like warnings), print them to stderr
485 and don't raise an exception.
486 """
487 all_issues = checks.run_checks(
488 app_configs=app_configs,
489 tags=tags,
490 include_deployment_checks=include_deployment_checks,
491 databases=databases,
492 )
494 header, body, footer = "", "", ""
495 visible_issue_count = 0 # excludes silenced warnings
497 if all_issues: 497 ↛ 498line 497 didn't jump to line 498, because the condition on line 497 was never true
498 debugs = [
499 e for e in all_issues if e.level < checks.INFO and not e.is_silenced()
500 ]
501 infos = [
502 e
503 for e in all_issues
504 if checks.INFO <= e.level < checks.WARNING and not e.is_silenced()
505 ]
506 warnings = [
507 e
508 for e in all_issues
509 if checks.WARNING <= e.level < checks.ERROR and not e.is_silenced()
510 ]
511 errors = [
512 e
513 for e in all_issues
514 if checks.ERROR <= e.level < checks.CRITICAL and not e.is_silenced()
515 ]
516 criticals = [
517 e
518 for e in all_issues
519 if checks.CRITICAL <= e.level and not e.is_silenced()
520 ]
521 sorted_issues = [
522 (criticals, "CRITICALS"),
523 (errors, "ERRORS"),
524 (warnings, "WARNINGS"),
525 (infos, "INFOS"),
526 (debugs, "DEBUGS"),
527 ]
529 for issues, group_name in sorted_issues:
530 if issues:
531 visible_issue_count += len(issues)
532 formatted = (
533 self.style.ERROR(str(e))
534 if e.is_serious()
535 else self.style.WARNING(str(e))
536 for e in issues
537 )
538 formatted = "\n".join(sorted(formatted))
539 body += "\n%s:\n%s\n" % (group_name, formatted)
541 if visible_issue_count: 541 ↛ 542line 541 didn't jump to line 542, because the condition on line 541 was never true
542 header = "System check identified some issues:\n"
544 if display_num_errors: 544 ↛ 556line 544 didn't jump to line 556, because the condition on line 544 was never false
545 if visible_issue_count: 545 ↛ 546line 545 didn't jump to line 546, because the condition on line 545 was never true
546 footer += "\n"
547 footer += "System check identified %s (%s silenced)." % (
548 "no issues"
549 if visible_issue_count == 0
550 else "1 issue"
551 if visible_issue_count == 1
552 else "%s issues" % visible_issue_count,
553 len(all_issues) - visible_issue_count,
554 )
556 if any(e.is_serious(fail_level) and not e.is_silenced() for e in all_issues): 556 ↛ 557line 556 didn't jump to line 557, because the condition on line 556 was never true
557 msg = self.style.ERROR("SystemCheckError: %s" % header) + body + footer
558 raise SystemCheckError(msg)
559 else:
560 msg = header + body + footer
562 if msg: 562 ↛ exitline 562 didn't return from function 'check', because the condition on line 562 was never false
563 if visible_issue_count: 563 ↛ 564line 563 didn't jump to line 564, because the condition on line 563 was never true
564 self.stderr.write(msg, lambda x: x)
565 else:
566 self.stdout.write(msg)
568 def check_migrations(self):
569 """
570 Print a warning if the set of migrations on disk don't match the
571 migrations in the database.
572 """
573 from django.db.migrations.executor import MigrationExecutor
575 try:
576 executor = MigrationExecutor(connections[DEFAULT_DB_ALIAS])
577 except ImproperlyConfigured:
578 # No databases are configured (or the dummy one)
579 return
581 plan = executor.migration_plan(executor.loader.graph.leaf_nodes())
582 if plan:
583 apps_waiting_migration = sorted(
584 {migration.app_label for migration, backwards in plan}
585 )
586 self.stdout.write(
587 self.style.NOTICE(
588 "\nYou have %(unapplied_migration_count)s unapplied migration(s). "
589 "Your project may not work properly until you apply the "
590 "migrations for app(s): %(apps_waiting_migration)s."
591 % {
592 "unapplied_migration_count": len(plan),
593 "apps_waiting_migration": ", ".join(apps_waiting_migration),
594 }
595 )
596 )
597 self.stdout.write(
598 self.style.NOTICE("Run 'python manage.py migrate' to apply them.")
599 )
601 def handle(self, *args, **options):
602 """
603 The actual logic of the command. Subclasses must implement
604 this method.
605 """
606 raise NotImplementedError(
607 "subclasses of BaseCommand must provide a handle() method"
608 )
611class AppCommand(BaseCommand):
612 """
613 A management command which takes one or more installed application labels
614 as arguments, and does something with each of them.
616 Rather than implementing ``handle()``, subclasses must implement
617 ``handle_app_config()``, which will be called once for each application.
618 """
620 missing_args_message = "Enter at least one application label."
622 def add_arguments(self, parser):
623 parser.add_argument(
624 "args",
625 metavar="app_label",
626 nargs="+",
627 help="One or more application label.",
628 )
630 def handle(self, *app_labels, **options):
631 from django.apps import apps
633 try:
634 app_configs = [apps.get_app_config(app_label) for app_label in app_labels]
635 except (LookupError, ImportError) as e:
636 raise CommandError(
637 "%s. Are you sure your INSTALLED_APPS setting is correct?" % e
638 )
639 output = []
640 for app_config in app_configs:
641 app_output = self.handle_app_config(app_config, **options)
642 if app_output:
643 output.append(app_output)
644 return "\n".join(output)
646 def handle_app_config(self, app_config, **options):
647 """
648 Perform the command's actions for app_config, an AppConfig instance
649 corresponding to an application label given on the command line.
650 """
651 raise NotImplementedError(
652 "Subclasses of AppCommand must provide a handle_app_config() method."
653 )
656class LabelCommand(BaseCommand):
657 """
658 A management command which takes one or more arbitrary arguments
659 (labels) on the command line, and does something with each of
660 them.
662 Rather than implementing ``handle()``, subclasses must implement
663 ``handle_label()``, which will be called once for each label.
665 If the arguments should be names of installed applications, use
666 ``AppCommand`` instead.
667 """
669 label = "label"
670 missing_args_message = "Enter at least one %s." % label
672 def add_arguments(self, parser):
673 parser.add_argument("args", metavar=self.label, nargs="+")
675 def handle(self, *labels, **options):
676 output = []
677 for label in labels:
678 label_output = self.handle_label(label, **options)
679 if label_output:
680 output.append(label_output)
681 return "\n".join(output)
683 def handle_label(self, label, **options):
684 """
685 Perform the command's actions for ``label``, which will be the
686 string as given on the command line.
687 """
688 raise NotImplementedError(
689 "subclasses of LabelCommand must provide a handle_label() method"
690 )