base.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. """
  4. Base classes for writing management commands (named commands which can
  5. be executed through ``django-admin`` or ``manage.py``).
  6. """
  7. import os
  8. import sys
  9. import warnings
  10. from argparse import ArgumentParser
  11. from optparse import OptionParser
  12. import django
  13. from django.core import checks
  14. from django.core.exceptions import ImproperlyConfigured
  15. from django.core.management.color import color_style, no_style
  16. from django.utils.deprecation import RemovedInDjango19Warning, RemovedInDjango20Warning
  17. from django.utils.encoding import force_str
  18. class CommandError(Exception):
  19. """
  20. Exception class indicating a problem while executing a management
  21. command.
  22. If this exception is raised during the execution of a management
  23. command, it will be caught and turned into a nicely-printed error
  24. message to the appropriate output stream (i.e., stderr); as a
  25. result, raising this exception (with a sensible description of the
  26. error) is the preferred way to indicate that something has gone
  27. wrong in the execution of a command.
  28. """
  29. pass
  30. class CommandParser(ArgumentParser):
  31. """
  32. Customized ArgumentParser class to improve some error messages and prevent
  33. SystemExit in several occasions, as SystemExit is unacceptable when a
  34. command is called programmatically.
  35. """
  36. def __init__(self, cmd, **kwargs):
  37. self.cmd = cmd
  38. super(CommandParser, self).__init__(**kwargs)
  39. def parse_args(self, args=None, namespace=None):
  40. # Catch missing argument for a better error message
  41. if (hasattr(self.cmd, 'missing_args_message') and
  42. not (args or any([not arg.startswith('-') for arg in args]))):
  43. self.error(self.cmd.missing_args_message)
  44. return super(CommandParser, self).parse_args(args, namespace)
  45. def error(self, message):
  46. if self.cmd._called_from_command_line:
  47. super(CommandParser, self).error(message)
  48. else:
  49. raise CommandError("Error: %s" % message)
  50. def handle_default_options(options):
  51. """
  52. Include any default options that all commands should accept here
  53. so that ManagementUtility can handle them before searching for
  54. user commands.
  55. """
  56. if options.settings:
  57. os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
  58. if options.pythonpath:
  59. sys.path.insert(0, options.pythonpath)
  60. class OutputWrapper(object):
  61. """
  62. Wrapper around stdout/stderr
  63. """
  64. def __init__(self, out, style_func=None, ending='\n'):
  65. self._out = out
  66. self.style_func = None
  67. if hasattr(out, 'isatty') and out.isatty():
  68. self.style_func = style_func
  69. self.ending = ending
  70. def __getattr__(self, name):
  71. return getattr(self._out, name)
  72. def write(self, msg, style_func=None, ending=None):
  73. ending = self.ending if ending is None else ending
  74. if ending and not msg.endswith(ending):
  75. msg += ending
  76. style_func = [f for f in (style_func, self.style_func, lambda x:x)
  77. if f is not None][0]
  78. self._out.write(force_str(style_func(msg)))
  79. class BaseCommand(object):
  80. """
  81. The base class from which all management commands ultimately
  82. derive.
  83. Use this class if you want access to all of the mechanisms which
  84. parse the command-line arguments and work out what code to call in
  85. response; if you don't need to change any of that behavior,
  86. consider using one of the subclasses defined in this file.
  87. If you are interested in overriding/customizing various aspects of
  88. the command-parsing and -execution behavior, the normal flow works
  89. as follows:
  90. 1. ``django-admin`` or ``manage.py`` loads the command class
  91. and calls its ``run_from_argv()`` method.
  92. 2. The ``run_from_argv()`` method calls ``create_parser()`` to get
  93. an ``ArgumentParser`` for the arguments, parses them, performs
  94. any environment changes requested by options like
  95. ``pythonpath``, and then calls the ``execute()`` method,
  96. passing the parsed arguments.
  97. 3. The ``execute()`` method attempts to carry out the command by
  98. calling the ``handle()`` method with the parsed arguments; any
  99. output produced by ``handle()`` will be printed to standard
  100. output and, if the command is intended to produce a block of
  101. SQL statements, will be wrapped in ``BEGIN`` and ``COMMIT``.
  102. 4. If ``handle()`` or ``execute()`` raised any exception (e.g.
  103. ``CommandError``), ``run_from_argv()`` will instead print an error
  104. message to ``stderr``.
  105. Thus, the ``handle()`` method is typically the starting point for
  106. subclasses; many built-in commands and command types either place
  107. all of their logic in ``handle()``, or perform some additional
  108. parsing work in ``handle()`` and then delegate from it to more
  109. specialized methods as needed.
  110. Several attributes affect behavior at various steps along the way:
  111. ``args``
  112. A string listing the arguments accepted by the command,
  113. suitable for use in help messages; e.g., a command which takes
  114. a list of application names might set this to '<app_label
  115. app_label ...>'.
  116. ``can_import_settings``
  117. A boolean indicating whether the command needs to be able to
  118. import Django settings; if ``True``, ``execute()`` will verify
  119. that this is possible before proceeding. Default value is
  120. ``True``.
  121. ``help``
  122. A short description of the command, which will be printed in
  123. help messages.
  124. ``option_list``
  125. This is the list of ``optparse`` options which will be fed
  126. into the command's ``OptionParser`` for parsing arguments.
  127. Deprecated and will be removed in Django 2.0.
  128. ``output_transaction``
  129. A boolean indicating whether the command outputs SQL
  130. statements; if ``True``, the output will automatically be
  131. wrapped with ``BEGIN;`` and ``COMMIT;``. Default value is
  132. ``False``.
  133. ``requires_system_checks``
  134. A boolean; if ``True``, entire Django project will be checked for errors
  135. prior to executing the command. Default value is ``True``.
  136. To validate an individual application's models
  137. rather than all applications' models, call
  138. ``self.check(app_configs)`` from ``handle()``, where ``app_configs``
  139. is the list of application's configuration provided by the
  140. app registry.
  141. ``requires_model_validation``
  142. DEPRECATED - This value will only be used if requires_system_checks
  143. has not been provided. Defining both ``requires_system_checks`` and
  144. ``requires_model_validation`` will result in an error.
  145. A boolean; if ``True``, validation of installed models will be
  146. performed prior to executing the command. Default value is
  147. ``True``. To validate an individual application's models
  148. rather than all applications' models, call
  149. ``self.validate(app_config)`` from ``handle()``, where ``app_config``
  150. is the application's configuration provided by the app registry.
  151. ``leave_locale_alone``
  152. A boolean indicating whether the locale set in settings should be
  153. preserved during the execution of the command instead of being
  154. forcibly set to 'en-us'.
  155. Default value is ``False``.
  156. Make sure you know what you are doing if you decide to change the value
  157. of this option in your custom command if it creates database content
  158. that is locale-sensitive and such content shouldn't contain any
  159. translations (like it happens e.g. with django.contrim.auth
  160. permissions) as making the locale differ from the de facto default
  161. 'en-us' might cause unintended effects.
  162. This option can't be False when the can_import_settings option is set
  163. to False too because attempting to set the locale needs access to
  164. settings. This condition will generate a CommandError.
  165. """
  166. # Metadata about this command.
  167. option_list = ()
  168. help = ''
  169. args = ''
  170. # Configuration shortcuts that alter various logic.
  171. _called_from_command_line = False
  172. can_import_settings = True
  173. output_transaction = False # Whether to wrap the output in a "BEGIN; COMMIT;"
  174. leave_locale_alone = False
  175. # Uncomment the following line of code after deprecation plan for
  176. # requires_model_validation comes to completion:
  177. #
  178. # requires_system_checks = True
  179. def __init__(self):
  180. self.style = color_style()
  181. # `requires_model_validation` is deprecated in favor of
  182. # `requires_system_checks`. If both options are present, an error is
  183. # raised. Otherwise the present option is used. If none of them is
  184. # defined, the default value (True) is used.
  185. has_old_option = hasattr(self, 'requires_model_validation')
  186. has_new_option = hasattr(self, 'requires_system_checks')
  187. if has_old_option:
  188. warnings.warn(
  189. '"requires_model_validation" is deprecated '
  190. 'in favor of "requires_system_checks".',
  191. RemovedInDjango19Warning)
  192. if has_old_option and has_new_option:
  193. raise ImproperlyConfigured(
  194. 'Command %s defines both "requires_model_validation" '
  195. 'and "requires_system_checks", which is illegal. Use only '
  196. '"requires_system_checks".' % self.__class__.__name__)
  197. self.requires_system_checks = (
  198. self.requires_system_checks if has_new_option else
  199. self.requires_model_validation if has_old_option else
  200. True)
  201. @property
  202. def use_argparse(self):
  203. return not bool(self.option_list)
  204. def get_version(self):
  205. """
  206. Return the Django version, which should be correct for all
  207. built-in Django commands. User-supplied commands should
  208. override this method.
  209. """
  210. return django.get_version()
  211. def usage(self, subcommand):
  212. """
  213. Return a brief description of how to use this command, by
  214. default from the attribute ``self.help``.
  215. """
  216. usage = '%%prog %s [options] %s' % (subcommand, self.args)
  217. if self.help:
  218. return '%s\n\n%s' % (usage, self.help)
  219. else:
  220. return usage
  221. def create_parser(self, prog_name, subcommand):
  222. """
  223. Create and return the ``ArgumentParser`` which will be used to
  224. parse the arguments to this command.
  225. """
  226. if not self.use_argparse:
  227. # Backwards compatibility: use deprecated optparse module
  228. warnings.warn("OptionParser usage for Django management commands "
  229. "is deprecated, use ArgumentParser instead",
  230. RemovedInDjango20Warning)
  231. parser = OptionParser(prog=prog_name,
  232. usage=self.usage(subcommand),
  233. version=self.get_version())
  234. parser.add_option('-v', '--verbosity', action='store', dest='verbosity', default='1',
  235. type='choice', choices=['0', '1', '2', '3'],
  236. help='Verbosity level; 0=minimal output, 1=normal output, 2=verbose output, 3=very verbose output')
  237. parser.add_option('--settings',
  238. help='The Python path to a settings module, e.g. "myproject.settings.main". If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.')
  239. parser.add_option('--pythonpath',
  240. help='A directory to add to the Python path, e.g. "/home/djangoprojects/myproject".'),
  241. parser.add_option('--traceback', action='store_true',
  242. help='Raise on exception')
  243. parser.add_option('--no-color', action='store_true', dest='no_color', default=False,
  244. help="Don't colorize the command output.")
  245. for opt in self.option_list:
  246. parser.add_option(opt)
  247. else:
  248. parser = CommandParser(self, prog="%s %s" % (os.path.basename(prog_name), subcommand),
  249. description=self.help or None)
  250. parser.add_argument('--version', action='version', version=self.get_version())
  251. parser.add_argument('-v', '--verbosity', action='store', dest='verbosity', default='1',
  252. type=int, choices=[0, 1, 2, 3],
  253. help='Verbosity level; 0=minimal output, 1=normal output, 2=verbose output, 3=very verbose output')
  254. parser.add_argument('--settings',
  255. help='The Python path to a settings module, e.g. "myproject.settings.main". If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.')
  256. parser.add_argument('--pythonpath',
  257. help='A directory to add to the Python path, e.g. "/home/djangoprojects/myproject".')
  258. parser.add_argument('--traceback', action='store_true',
  259. help='Raise on exception')
  260. parser.add_argument('--no-color', action='store_true', dest='no_color', default=False,
  261. help="Don't colorize the command output.")
  262. if self.args:
  263. # Keep compatibility and always accept positional arguments, like optparse when args is set
  264. parser.add_argument('args', nargs='*')
  265. self.add_arguments(parser)
  266. return parser
  267. def add_arguments(self, parser):
  268. """
  269. Entry point for subclassed commands to add custom arguments.
  270. """
  271. pass
  272. def print_help(self, prog_name, subcommand):
  273. """
  274. Print the help message for this command, derived from
  275. ``self.usage()``.
  276. """
  277. parser = self.create_parser(prog_name, subcommand)
  278. parser.print_help()
  279. def run_from_argv(self, argv):
  280. """
  281. Set up any environment changes requested (e.g., Python path
  282. and Django settings), then run this command. If the
  283. command raises a ``CommandError``, intercept it and print it sensibly
  284. to stderr. If the ``--traceback`` option is present or the raised
  285. ``Exception`` is not ``CommandError``, raise it.
  286. """
  287. self._called_from_command_line = True
  288. parser = self.create_parser(argv[0], argv[1])
  289. if self.use_argparse:
  290. options = parser.parse_args(argv[2:])
  291. cmd_options = vars(options)
  292. # Move positional args out of options to mimic legacy optparse
  293. if 'args' in options:
  294. args = options.args
  295. del cmd_options['args']
  296. else:
  297. args = ()
  298. else:
  299. options, args = parser.parse_args(argv[2:])
  300. cmd_options = vars(options)
  301. handle_default_options(options)
  302. try:
  303. self.execute(*args, **cmd_options)
  304. except Exception as e:
  305. if options.traceback or not isinstance(e, CommandError):
  306. raise
  307. # self.stderr is not guaranteed to be set here
  308. stderr = getattr(self, 'stderr', OutputWrapper(sys.stderr, self.style.ERROR))
  309. stderr.write('%s: %s' % (e.__class__.__name__, e))
  310. sys.exit(1)
  311. def execute(self, *args, **options):
  312. """
  313. Try to execute this command, performing system checks if needed (as
  314. controlled by attributes ``self.requires_system_checks`` and
  315. ``self.requires_model_validation``, except if force-skipped).
  316. """
  317. self.stdout = OutputWrapper(options.get('stdout', sys.stdout))
  318. if options.get('no_color'):
  319. self.style = no_style()
  320. self.stderr = OutputWrapper(options.get('stderr', sys.stderr))
  321. os.environ["DJANGO_COLORS"] = "nocolor"
  322. else:
  323. self.stderr = OutputWrapper(options.get('stderr', sys.stderr), self.style.ERROR)
  324. if self.can_import_settings:
  325. from django.conf import settings # NOQA
  326. saved_locale = None
  327. if not self.leave_locale_alone:
  328. # Only mess with locales if we can assume we have a working
  329. # settings file, because django.utils.translation requires settings
  330. # (The final saying about whether the i18n machinery is active will be
  331. # found in the value of the USE_I18N setting)
  332. if not self.can_import_settings:
  333. raise CommandError("Incompatible values of 'leave_locale_alone' "
  334. "(%s) and 'can_import_settings' (%s) command "
  335. "options." % (self.leave_locale_alone,
  336. self.can_import_settings))
  337. # Switch to US English, because django-admin creates database
  338. # content like permissions, and those shouldn't contain any
  339. # translations.
  340. from django.utils import translation
  341. saved_locale = translation.get_language()
  342. translation.activate('en-us')
  343. try:
  344. if (self.requires_system_checks and
  345. not options.get('skip_validation') and # This will be removed at the end of deprecation process for `skip_validation`.
  346. not options.get('skip_checks')):
  347. self.check()
  348. output = self.handle(*args, **options)
  349. if output:
  350. if self.output_transaction:
  351. # This needs to be imported here, because it relies on
  352. # settings.
  353. from django.db import connections, DEFAULT_DB_ALIAS
  354. connection = connections[options.get('database', DEFAULT_DB_ALIAS)]
  355. if connection.ops.start_transaction_sql():
  356. self.stdout.write(self.style.SQL_KEYWORD(connection.ops.start_transaction_sql()))
  357. self.stdout.write(output)
  358. if self.output_transaction:
  359. self.stdout.write('\n' + self.style.SQL_KEYWORD(connection.ops.end_transaction_sql()))
  360. finally:
  361. if saved_locale is not None:
  362. translation.activate(saved_locale)
  363. def validate(self, app_config=None, display_num_errors=False):
  364. """ Deprecated. Delegates to ``check``."""
  365. if app_config is None:
  366. app_configs = None
  367. else:
  368. app_configs = [app_config]
  369. return self.check(app_configs=app_configs, display_num_errors=display_num_errors)
  370. def check(self, app_configs=None, tags=None, display_num_errors=False):
  371. """
  372. Uses the system check framework to validate entire Django project.
  373. Raises CommandError for any serious message (error or critical errors).
  374. If there are only light messages (like warnings), they are printed to
  375. stderr and no exception is raised.
  376. """
  377. all_issues = checks.run_checks(app_configs=app_configs, tags=tags)
  378. msg = ""
  379. visible_issue_count = 0 # excludes silenced warnings
  380. if all_issues:
  381. debugs = [e for e in all_issues if e.level < checks.INFO and not e.is_silenced()]
  382. infos = [e for e in all_issues if checks.INFO <= e.level < checks.WARNING and not e.is_silenced()]
  383. warnings = [e for e in all_issues if checks.WARNING <= e.level < checks.ERROR and not e.is_silenced()]
  384. errors = [e for e in all_issues if checks.ERROR <= e.level < checks.CRITICAL]
  385. criticals = [e for e in all_issues if checks.CRITICAL <= e.level]
  386. sorted_issues = [
  387. (criticals, 'CRITICALS'),
  388. (errors, 'ERRORS'),
  389. (warnings, 'WARNINGS'),
  390. (infos, 'INFOS'),
  391. (debugs, 'DEBUGS'),
  392. ]
  393. for issues, group_name in sorted_issues:
  394. if issues:
  395. visible_issue_count += len(issues)
  396. formatted = (
  397. color_style().ERROR(force_str(e))
  398. if e.is_serious()
  399. else color_style().WARNING(force_str(e))
  400. for e in issues)
  401. formatted = "\n".join(sorted(formatted))
  402. msg += '\n%s:\n%s\n' % (group_name, formatted)
  403. if msg:
  404. msg = "System check identified some issues:\n%s" % msg
  405. if display_num_errors:
  406. if msg:
  407. msg += '\n'
  408. msg += "System check identified %s (%s silenced)." % (
  409. "no issues" if visible_issue_count == 0 else
  410. "1 issue" if visible_issue_count == 1 else
  411. "%s issues" % visible_issue_count,
  412. len(all_issues) - visible_issue_count,
  413. )
  414. if any(e.is_serious() and not e.is_silenced() for e in all_issues):
  415. raise CommandError(msg)
  416. elif msg and visible_issue_count:
  417. self.stderr.write(msg)
  418. elif msg:
  419. self.stdout.write(msg)
  420. def handle(self, *args, **options):
  421. """
  422. The actual logic of the command. Subclasses must implement
  423. this method.
  424. """
  425. raise NotImplementedError('subclasses of BaseCommand must provide a handle() method')
  426. class AppCommand(BaseCommand):
  427. """
  428. A management command which takes one or more installed application labels
  429. as arguments, and does something with each of them.
  430. Rather than implementing ``handle()``, subclasses must implement
  431. ``handle_app_config()``, which will be called once for each application.
  432. """
  433. missing_args_message = "Enter at least one application label."
  434. def add_arguments(self, parser):
  435. parser.add_argument('args', metavar='app_label', nargs='+',
  436. help='One or more application label.')
  437. def handle(self, *app_labels, **options):
  438. from django.apps import apps
  439. try:
  440. app_configs = [apps.get_app_config(app_label) for app_label in app_labels]
  441. except (LookupError, ImportError) as e:
  442. raise CommandError("%s. Are you sure your INSTALLED_APPS setting is correct?" % e)
  443. output = []
  444. for app_config in app_configs:
  445. app_output = self.handle_app_config(app_config, **options)
  446. if app_output:
  447. output.append(app_output)
  448. return '\n'.join(output)
  449. def handle_app_config(self, app_config, **options):
  450. """
  451. Perform the command's actions for app_config, an AppConfig instance
  452. corresponding to an application label given on the command line.
  453. """
  454. try:
  455. # During the deprecation path, keep delegating to handle_app if
  456. # handle_app_config isn't implemented in a subclass.
  457. handle_app = self.handle_app
  458. except AttributeError:
  459. # Keep only this exception when the deprecation completes.
  460. raise NotImplementedError(
  461. "Subclasses of AppCommand must provide"
  462. "a handle_app_config() method.")
  463. else:
  464. warnings.warn(
  465. "AppCommand.handle_app() is superseded by "
  466. "AppCommand.handle_app_config().",
  467. RemovedInDjango19Warning, stacklevel=2)
  468. if app_config.models_module is None:
  469. raise CommandError(
  470. "AppCommand cannot handle app '%s' in legacy mode "
  471. "because it doesn't have a models module."
  472. % app_config.label)
  473. return handle_app(app_config.models_module, **options)
  474. class LabelCommand(BaseCommand):
  475. """
  476. A management command which takes one or more arbitrary arguments
  477. (labels) on the command line, and does something with each of
  478. them.
  479. Rather than implementing ``handle()``, subclasses must implement
  480. ``handle_label()``, which will be called once for each label.
  481. If the arguments should be names of installed applications, use
  482. ``AppCommand`` instead.
  483. """
  484. label = 'label'
  485. missing_args_message = "Enter at least one %s." % label
  486. def add_arguments(self, parser):
  487. parser.add_argument('args', metavar=self.label, nargs='+')
  488. def handle(self, *labels, **options):
  489. output = []
  490. for label in labels:
  491. label_output = self.handle_label(label, **options)
  492. if label_output:
  493. output.append(label_output)
  494. return '\n'.join(output)
  495. def handle_label(self, label, **options):
  496. """
  497. Perform the command's actions for ``label``, which will be the
  498. string as given on the command line.
  499. """
  500. raise NotImplementedError('subclasses of LabelCommand must provide a handle_label() method')
  501. class NoArgsCommand(BaseCommand):
  502. """
  503. A command which takes no arguments on the command line.
  504. Rather than implementing ``handle()``, subclasses must implement
  505. ``handle_noargs()``; ``handle()`` itself is overridden to ensure
  506. no arguments are passed to the command.
  507. Attempting to pass arguments will raise ``CommandError``.
  508. """
  509. args = ''
  510. def __init__(self):
  511. warnings.warn(
  512. "NoArgsCommand class is deprecated and will be removed in Django 2.0. "
  513. "Use BaseCommand instead, which takes no arguments by default.",
  514. RemovedInDjango20Warning
  515. )
  516. super(NoArgsCommand, self).__init__()
  517. def handle(self, *args, **options):
  518. if args:
  519. raise CommandError("Command doesn't accept any arguments")
  520. return self.handle_noargs(**options)
  521. def handle_noargs(self, **options):
  522. """
  523. Perform this command's actions.
  524. """
  525. raise NotImplementedError('subclasses of NoArgsCommand must provide a handle_noargs() method')