email_notifications.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. # -*- coding: utf-8 -*-
  2. import sys
  3. import traceback
  4. from django.conf import settings
  5. from django.core.mail import send_mail
  6. from django.core.management import BaseCommand
  7. class EmailNotificationCommand(BaseCommand):
  8. """
  9. A BaseCommand subclass which adds sending email fuctionality.
  10. Subclasses will have an extra command line option ``--email-notification``
  11. and will be able to send emails by calling ``send_email_notification()``
  12. if SMTP host and port are specified in settings. The handling of the
  13. command line option is left to the management command implementation.
  14. Configuration is done in settings.EMAIL_NOTIFICATIONS dict.
  15. Configuration example::
  16. EMAIL_NOTIFICATIONS = {
  17. 'scripts.my_script': {
  18. 'subject': 'my_script subject',
  19. 'body': 'my_script body',
  20. 'from_email': 'from_email@example.com',
  21. 'recipients': ('recipient0@example.com',),
  22. 'no_admins': False,
  23. 'no_traceback': False,
  24. 'notification_level': 0,
  25. 'fail_silently': False
  26. },
  27. 'scripts.another_script': {
  28. ...
  29. },
  30. ...
  31. }
  32. Configuration explained:
  33. subject: Email subject.
  34. body: Email body.
  35. from_email: Email from address.
  36. recipients: Sequence of email recipient addresses.
  37. no_admins: When True do not include ADMINS to recipients.
  38. no_traceback: When True do not include traceback to email body.
  39. notification_level: 0: send email on fail, 1: send email always.
  40. fail_silently: Parameter passed to django's send_mail().
  41. """
  42. def add_arguments(self, parser):
  43. parser.add_argument('--email-notifications',
  44. action='store_true',
  45. default=False,
  46. dest='email_notifications',
  47. help='Send email notifications for command.')
  48. parser.add_argument('--email-exception',
  49. action='store_true',
  50. default=False,
  51. dest='email_exception',
  52. help='Send email for command exceptions.')
  53. def run_from_argv(self, argv):
  54. """Overriden in order to access the command line arguments."""
  55. self.argv_string = ' '.join(argv)
  56. super().run_from_argv(argv)
  57. def execute(self, *args, **options):
  58. """
  59. Overriden in order to send emails on unhandled exception.
  60. If an unhandled exception in ``def handle(self, *args, **options)``
  61. occurs and `--email-exception` is set or `self.email_exception` is
  62. set to True send an email to ADMINS with the traceback and then
  63. reraise the exception.
  64. """
  65. try:
  66. super().execute(*args, **options)
  67. except Exception:
  68. if options['email_exception'] or getattr(self, 'email_exception', False):
  69. self.send_email_notification(include_traceback=True)
  70. raise
  71. def send_email_notification(self, notification_id=None, include_traceback=False, verbosity=1):
  72. """
  73. Send email notifications.
  74. Reads settings from settings.EMAIL_NOTIFICATIONS dict, if available,
  75. using ``notification_id`` as a key or else provides reasonable
  76. defaults.
  77. """
  78. # Load email notification settings if available
  79. if notification_id is not None:
  80. try:
  81. email_settings = settings.EMAIL_NOTIFICATIONS.get(notification_id, {})
  82. except AttributeError:
  83. email_settings = {}
  84. else:
  85. email_settings = {}
  86. # Exit if no traceback found and not in 'notify always' mode
  87. if not include_traceback and not email_settings.get('notification_level', 0):
  88. print(self.style.ERROR("Exiting, not in 'notify always' mode."))
  89. return
  90. # Set email fields.
  91. subject = email_settings.get('subject', "Django extensions email notification.")
  92. command_name = self.__module__.split('.')[-1]
  93. body = email_settings.get(
  94. 'body',
  95. "Reporting execution of command: '%s'" % command_name
  96. )
  97. # Include traceback
  98. if include_traceback and not email_settings.get('no_traceback', False):
  99. try:
  100. exc_type, exc_value, exc_traceback = sys.exc_info()
  101. trb = ''.join(traceback.format_tb(exc_traceback))
  102. body += "\n\nTraceback:\n\n%s\n" % trb
  103. finally:
  104. del exc_traceback
  105. # Set from address
  106. from_email = email_settings.get('from_email', settings.DEFAULT_FROM_EMAIL)
  107. # Calculate recipients
  108. recipients = list(email_settings.get('recipients', []))
  109. if not email_settings.get('no_admins', False):
  110. recipients.extend(settings.ADMINS)
  111. if not recipients:
  112. if verbosity > 0:
  113. print(self.style.ERROR("No email recipients available."))
  114. return
  115. # Send email...
  116. send_mail(subject, body, from_email, recipients,
  117. fail_silently=email_settings.get('fail_silently', True))