base.py 21 KB

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