$30 off During Our Annual Pro Sale. View Details »

Djangoでのメール送信 - 設定からテストまで/djangocongress-jp-2019-talk

Djangoでのメール送信 - 設定からテストまで/djangocongress-jp-2019-talk

DjangoCongress JP 2019の発表資料です

thinkAmi

May 18, 2019
Tweet

More Decks by thinkAmi

Other Decks in Programming

Transcript

  1. Django
    でのメール送信
    ~ 設定からテストまで ~
    DjangoCongress JP 2019 talk
    2019/5/18
    @thinkAmi

    View Slide

  2. お前誰よ / Who are you ?
    @thinkAmi
    Python & Django
    Blog
    :メモ的な思考的な (http://thinkami.hatenablog.com/)
    (
    株)
    日本システム技研
    PyCon JP 2016-18 Silver Sponsor
    ギークラボ長野
    Python Boot Camp in
    長野
    みんなのPython
    勉強会 in
    長野

    View Slide

  3. 質問

    View Slide

  4. Django
    でメール送信の経験がある方

    View Slide

  5. 簡単!
    Django
    でメール送信

    View Slide

  6. 設定ファイル
    # settings.py
    EMAIL_HOST = 'smtp.example.com' #
    送信メールサーバ
    EMAIL_PORT = '587' #
    送信メールポート
    EMAIL_HOST_USER = 'test_user' #
    送信ユーザ
    EMAIL_HOST_PASSWORD = 'Passw0rd' #
    送信パスワード

    View Slide

  7. send_mail()
    関数の実行
    send_mail(subject='my subject', #
    件名
    message='My Body', #
    本文
    from_email='[email protected]', #
    送信者
    recipient_list=['[email protected]']) #
    受信者(To)
    のリスト

    View Slide

  8. 受信メール

    View Slide

  9. メールの送信
    send_mail(subject='my subject', #
    件名
    message='My Body', #
    本文
    from_email='[email protected]', #
    送信者
    recipient_list=['[email protected]']) #
    受信者(To)
    のリスト

    View Slide

  10. 話すこと
    Django
    のメール送信の流れ
    EmailMessage
    EmailBackend
    メール送信とunittest
    ショートカット関数
    エラー通知メール
    エラー通知の仕組み
    内容のマスク

    View Slide

  11. 話さないこと
    仕組み以外の話
    メールのRFC
    メール配信サービスの設定
    SendGrid
    Amazon SES

    View Slide

  12. 注意
    このトークを聴いても、Django
    アプリは作れません
    スライドは公開
    スライド中のソースコードは、一部省略
    GitHub
    に全体を公開済
    https://github.com/thinkAmi/DjangoCongress_JP_2019_talk

    View Slide

  13. 環境
    Python 3.7.3
    Django 2.2.1 LTS

    View Slide

  14. 目次
    Django
    のメール送信の流れ
    unittest
    ショートカット関数
    エラー通知メール

    View Slide

  15. Django
    のメールの仕組み
    send_mail()
    関数を元に理解する

    View Slide

  16. send_mail()
    関数は何をしているか
    # django.core.mail.__init__.py
    def send_mail(subject, message, from_email, recipient_list,
    fail_silently=False, auth_user=None, auth_password=None,
    connection=None, html_message=None):
    # EmailBackend
    オブジェクトを取得
    connection = connection or get_connection()
    # EmailMessage
    系オブジェクトを生成
    mail = EmailMultiAlternatives(
    subject, message, from_email, recipient_list, connection=connection)
    # EmailMessage
    系オブジェクトで送信
    return mail.send()

    View Slide

  17. send_mail()
    関数
    メール送信のショートカット関数
    実際
    EmailMessage
    EmailBackend

    View Slide

  18. EmailMessage
    について

    View Slide

  19. EmailMessage
    の役割
    メール属性を持つ
    EmailBackend
    を使ってメールを送信

    View Slide

  20. EmailMessage
    のメール属性
    from_email, to, cc, bcc, reply_to
    subject
    body
    attachments
    extra_headers
    connection
    EmailBackend

    View Slide

  21. EmailMessage
    とsend_mail()
    の違い
    できること
    どちらでも
    EmailMessage
    のみ

    View Slide

  22. どちらでもできる例
    メールアドレスに表示名を付けたい
    HTML
    メールを送信したい
    メールテンプレートを使いたい

    View Slide

  23. メールアドレスの表示名とは
    from_user

    View Slide

  24. メールアドレスに表示名を付ける
    # EmailMessage
    の場合
    email = EmailMessage(
    from_email='from_user ', ...)
    # send_mail()
    の場合
    send_mail(from_email='from user ')

    View Slide

  25. 受信メール (
    ヘッダ)
    From: from_user

    View Slide

  26. HTML
    メールを送信したい
    # EmailMessage(
    のサブクラス)
    の場合
    email = EmailMultiAlternatives(
    body='My Body', ...)
    email.attach_alternative('HTML
    メール
    です', 'text/html')
    email.send()
    # send_mail()
    の場合
    send_mail(html_message='HTML
    メール
    です')

    View Slide

  27. 受信メール
    テキスト表示
    HTML
    表示

    View Slide

  28. メールでテンプレートを使いたい
    render_to_string()
    関数と組み合わせる

    View Slide

  29. テンプレートエンジンを変更
    TEMPLATES = [
    {
    # View
    は、Django
    エンジン
    'BACKEND': 'django.template.backends.django.DjangoTemplates',
    'DIRS': [os.path.join(BASE_DIR, 'templates')],
    },
    {
    #
    メールは、Jinja2
    エンジン
    'BACKEND': 'django.template.backends.jinja2.Jinja2',
    'DIRS': [os.path.join(BASE_DIR, 'template_jinja2')],
    },
    ]

    View Slide

  30. テキストメールのテンプレート
    # base.txt
    メールのベースです
    {% block mail_content %}
    {% endblock %}
    {% include './signature.txt' %}
    # content.txt
    {% extends './base.txt' %}
    {% block mail_content %}
    {{ message }}
    {% endblock %}
    # signature.txt
    from thinkAmi

    View Slide

  31. テンプレートを利用して送信
    template_body = render_to_string('mail/content.txt',
    context={'message': '
    埋め込みます'})
    # EmailMessage
    の場合
    email = EmailMessage(body=template_body, ...)
    # send_mail()
    の場合
    send_mail(message=template_body, ...)

    View Slide

  32. 受信メール

    View Slide

  33. EmailMessage
    のみできる例
    添付ファイル

    View Slide

  34. 添付ファイル
    EmailMessage
    のメソッドを使用
    関数 引数 添付ファイル名
    __init__() MIMEBase
    系, tuple (
    *)
    指定可
    attach()
    〃 〃
    attach_file()
    ファイルシステム上のパス ファイルシステムと同一
    * tuple(filename, content, mimetype)

    View Slide

  35. メソッドの使い方
    #
    静的ディレクトリにあるファイルを添付する
    static_file_dir = pathlib.Path(settings.STATICFILES_DIRS[0])
    image_file = static_file_dir.joinpath('images', 'shinanogold.png')
    # __init__
    の場合
    with image_file.open(mode='rb') as f:
    EmailMessage(attachments=[('my.png', f.read(), 'image/png')], ...).send()
    # attach_file
    の場合
    msg = EmailMessage(...)
    msg.attach_file(image_file)
    msg.send()

    View Slide

  36. __init__
    での結果

    View Slide

  37. attach_file
    での結果

    View Slide

  38. EmailBackend
    について

    View Slide

  39. EmailBackend
    とは
    メールを送信する手段
    拡張や自作も可能

    View Slide

  40. EmailBackend
    の種類
    Django
    標準
    django.core.mail.backends
    メール内容をどこに出力するかで使い分け
    SMTP
    コンソール
    ファイル
    インメモリ
    ダミー

    View Slide

  41. EmailBackend
    の指定
    settings
    の EMAIL_BACKEND
    デフォルト SMTP

    View Slide

  42. EmailBackend
    に必要な設定
    EmailBackend
    により異なる
    # SMTP
    時のsettings
    EMAIL_HOST = 'smtp.example.com' #
    送信メールサーバ
    EMAIL_PORT = '587' #
    送信メールポート
    EMAIL_HOST_USER = 'test_user' #
    送信ユーザ
    EMAIL_HOST_PASSWORD = 'Passw0rd' #
    送信パスワード

    View Slide

  43. EmailBackend
    の拡張

    console.EmailBackend
    でも、件名を日本語で見たい
    Subject: =?utf-8?b?5Lu25ZCN44Gn44GZ?=

    View Slide

  44. console.EmailBackend
    を拡張
    write_message()
    メソッドをオーバーライド
    # myapp.email_backends.py
    class ReadableSubjectEmailBackend(console.EmailBackend):
    def write_message(self, message):
    from email.header import decode_header
    subject = message.message().get('Subject')
    decoded_tuple = decode_header(subject)
    # => [('Django', None)] # MIME
    ヘッダエンコーディングなし
    # => [(b'\xe3\x82\xb8\xe3\x83\xa3\xe3\x83\xb3\xe3\x82\xb4', 'utf-8')]
    if decoded_tuple[0][1] is not None:
    readable_subject = decoded_tuple[0][0].decode(
    decoded_tuple[0][1])
    self.stream.write(f'\nSubject (
    日本語表示): {readable_subject}\n')
    super().write_message(message)

    View Slide

  45. settings
    EMAIL_BACKEND = 'myapp.email_backends.ReadableSubjectEmailBackend'

    View Slide

  46. 結果
    Subject (
    日本語表示):
    ジャンゴ
    Content-Type: text/plain; charset="utf-8"
    Subject: =?utf-8?b?44K444Oj44Oz44K0?=

    View Slide

  47. EmailBackend
    を自作
    send_messages()
    メソッドを実装したクラスを用意
    django.core.mail.backends.base.BaseEmailBackend
    を継承すると楽

    View Slide


  48. メール送信ではなく、Slack
    に投稿したい
    SlackBackend

    View Slide

  49. SlackBackend
    の実装
    メールのBody
    をSlack
    へ投稿する
    from slackclient import SlackClient
    class SlackBackend(BaseEmailBackend):
    def send_messages(self, email_messages):
    payload = email_messages[0].message().get_payload()
    client = SlackClient(settings.SLACK_OAUTH_ACCESS_TOKEN)
    client.api_call(
    'chat.postMessage',
    channel=settings.SLACK_CHANNEL,
    text=payload,
    )

    View Slide

  50. settings
    EMAIL_BACKEND = 'myapp.email_backends.SlackBackend'
    SLACK_OAUTH_ACCESS_TOKEN = 'Your OAuth Access Token'
    SLACK_CHANNEL = 'Your Channel ID'

    View Slide

  51. メールの送信処理
    EmailMessage(
    subject='Django',
    body='DjangoCongress JP 2019
    にようこそ!',
    from_email=settings.REAL_MAIL_FROM,
    to=settings.REAL_MAIL_TO).send()

    View Slide

  52. 結果

    View Slide

  53. ここまでのまとめ
    EmailMessage
    でメール情報を作る
    EmailBackend
    を使って送信する

    View Slide

  54. 目次
    Django
    のメール送信の流れ
    unittest
    ショートカット関数
    エラー通知メール

    View Slide

  55. 開発時の悩み
    メール内容の確認をしたい
    実メールサーバに送信したくない

    View Slide

  56. 実メールサーバ以外での確認
    EmailBackend
    を差し替え
    コンソールやファイルなど
    Python
    のSMTP
    サーバ DebuggingServer
    を宛先にして送信

    View Slide

  57. unittest

    メールは送信されない
    EmailBackend
    が locmem.EmailBackend
    に差し替わる

    View Slide

  58. 差し替えているところ
    settings.TEST_RUNNER
    django.test.runner.DiscoverRunner
    setup_test_environment()
    django.test.utils.setup_test_environment()
    # https://github.com/django/django/blob/2.2.1/django/test/utils.py#L103
    # EmailBackend
    の差し替え
    settings.EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
    #
    メールボックスの初期化
    mail.outbox = []

    View Slide

  59. locmem
    に差し替わると
    django.core.mail.outbox
    に、EmailMessage
    のリストが設定

    View Slide

  60. テスト対象の関数
    def my_send_mail(encoding='utf-8', has_attachment=False):
    msg = EmailMessage(
    subject='
    件名', body='
    本文',
    from_email='
    差出人 ',
    to=['
    送信先1 ', '
    送信先2 '],
    cc=['
    シーシー '],
    bcc=['
    ビーシーシー '],
    reply_to=['
    返信先 '],
    headers={'Sender': '[email protected]'})
    if has_attachment:
    #
    静的ディレクトリにあるファイルを添付する
    img = pathlib.Path(settings.STATICFILES_DIRS[0]).joinpath(
    'images', 'shinanogold.png')
    msg.attach_file(img)
    msg.send()

    View Slide

  61. mail.outbox
    の動作確認
    class TestSendMail(TestCase):
    def _callFUT(self, encoding='utf-8', has_attachment=False):
    from myapp.utils import my_send_mail
    my_send_mail(encoding=encoding, has_attachment=has_attachment)
    def test_send_multiple(self):
    #
    実行前はメールボックスに何もない
    self.assertEqual(len(mail.outbox), 0)
    # 1
    回実行すると、メールが1
    通入る
    self._callFUT()
    self.assertEqual(len(mail.outbox), 1)
    #
    もう1
    回実行すると、メールが2
    通入る
    self._callFUT()
    self.assertEqual(len(mail.outbox), 2)

    View Slide

  62. 各フィールドの検証
    def test_mail_fields(self):
    self._callFUT()
    actual = mail.outbox[0]
    self.assertEqual(actual.subject, '
    件名')
    self.assertEqual(actual.body, '
    本文')
    self.assertEqual(actual.from_email, '
    差出人 ')
    #
    宛先系はlist
    として設定される
    self.assertEqual(actual.to,
    ['
    送信先1 ',
    '
    送信先2 '],)
    #
    追加ヘッダも含まれる
    self.assertEqual(actual.extra_headers['Sender'], '[email protected]')

    View Slide

  63. 添付ファイルがある場合
    def test_attachment(self):
    self._callFUT(has_attachment=True)
    actual = mail.outbox[0]
    #
    添付ファイル自体を検証
    img = pathlib.Path(settings.STATICFILES_DIRS[0]).joinpath(
    'images', 'shinanogold.png')
    with img.open('rb') as f:
    expected_img = f.read()
    # tuple(filename, content, mimetype)
    self.assertEqual(actual.attachments[0][1], expected_img)

    View Slide

  64. ここまでのまとめ
    unittest
    では、メールは送信されない
    django.core.mail.outbox
    に、EmailMessage
    のリストが設定

    View Slide

  65. 目次
    Django
    のメール送信の流れ
    unittest
    ショートカット関数
    エラー通知メール

    View Slide

  66. ショートカット関数
    User
    モデル
    email_user()
    一括送信
    send_mass_mail()
    管理者宛送信
    mail_admins()
    mail_managers()

    View Slide

  67. User
    モデルの email_user()
    #
    ユーザー foo
    を取得
    foo_user = User.objects.get(username='foo')
    #
    ユーザー foo
    のメールアドレスへ送信
    foo_user.email_user(
    subject='Hello',
    message='Welcome!',
    from_email='[email protected]',
    connection=console.EmailBackend(),
    )

    View Slide

  68. send_mass_mail()
    1
    接続で複数のメールを送信
    複数件送信する場合、 send_mail()
    より効率が良い
    msg1 = ('shortcut subject1', 'Shortcut Body',
    '[email protected]',
    ['[email protected]', '[email protected]'])
    msg2 = ('shortcut subject2', 'Shortcut Body',
    '[email protected]',
    ['[email protected]', '[email protected]'])
    send_mass_mail((msg1, msg2), connection=console.EmailBackend())

    View Slide

  69. 注意点
    機能 EmailMessage send_mail() send_mass_mail()
    宛先 To, Cc, Bcc, Reply-To To To
    HTML
    メール
    添付ファイル

    View Slide

  70. 管理者宛メールのショートカット関数
    mail_admins()
    settings.ADMINS
    宛にメール送信
    mail_managers()
    settings.MANAGERS
    宛にメール送信

    View Slide

  71. ADMINS
    とMANAGERS
    の違い
    公式ドキュメントより
    ADMINS = site admins
    MANAGERS = site managers
    Django
    のエラー通知機能にて、両者の違いを見る

    View Slide

  72. 目次
    Django
    のメール送信の流れ
    unittest
    ショートカット関数
    エラー通知メール

    View Slide

  73. Django
    のエラー通知メール
    Django
    アプリでエラーが発生した時に、メールで通知
    サーバーエラー
    HTTP 500
    リンク切れ
    Referer
    あり & HTTP 404
    存在しないページに直接アクセスした時は通知しない

    View Slide

  74. サーバーエラー通知メールの仕組み
    # django.utils.log.py
    DEFAULT_LOGGING = {
    'filters': {
    'require_debug_false': {
    '()': 'django.utils.log.RequireDebugFalse'},
    'handlers': {
    'mail_admins': {
    'level': 'ERROR',
    'filters': ['require_debug_false'],
    'class': 'django.utils.log.AdminEmailHandler'}},
    'loggers': {
    'django': {
    'handlers': ['console', 'mail_admins']}}

    View Slide

  75. 設定の変更例
    エラー通知メールは常にコンソール出力
    LOGGING = {
    'handlers': {
    'mail_admins': {
    ...
    'email_backend': #
    追加
    'django.core.mail.backends.console.EmailBackend'}

    View Slide

  76. 自作
    AdminEmailHandler
    を継承し、 send_mail()
    をオーバーライド

    View Slide


  77. エラーレポートのHTML
    をパスワード付zip
    ファイルにして送信
    class MyAdminEmailHandler(AdminEmailHandler):
    def send_mail(self, subject, message, *args, **kwargs):
    with TemporaryDirectory() as temp_dir:
    html_file = pathlib.Path(temp_dir).joinpath('report.html')
    with html_file.open('w') as f:
    f.write(kwargs.get('html_message'))
    #
    パスワード付zip
    ファイルを添付・送信
    zip_file = pathlib.Path(temp_dir).joinpath('dst.zip')
    pyminizip.compress(str(html_file), None,
    str(zip_file), 'pass', 0)
    msg = EmailMessage(...)
    msg.attach_file(zip_file)
    msg.send()

    View Slide

  78. 結果

    View Slide

  79. リンク切れ通知メールの仕組み
    django.middleware.common.BrokenLinkEmailsMiddleware

    View Slide

  80. 通知先の違い
    サーバーエラー
    MAIL_ADMINS
    リンク切れ
    MAIL_MANAGERS

    View Slide

  81. サーバーエラーの通知設定
    #
    本番運用モードにする
    DEBUG = False
    #
    送信先の site admins
    のメールアドレスを設定
    # ('
    メールアドレスコメント', '
    メールアドレス')
    ADMINS = [('Admin1', '[email protected]')]
    #
    他、使用するEmailBackend
    の設定を行う

    View Slide

  82. サーバーエラー通知設定 (
    任意)
    #
    送信元のメールアドレス
    #
    デフォルト:root@localhost
    SERVER_EMAIL = '[email protected]'
    #
    エラー通知メールの件名のPrefix
    #
    デフォルト:[Django]
    EMAIL_SUBJECT_PREFIX = '[Hello]'

    View Slide

  83. 動作確認
    $ curl http://localhost:8000/force_500
    Server Error (500)

    View Slide

  84. 受信メール
    エラー内容やsettings
    、POST
    データなどが含まれる
    デフォルトでは、ローカル変数は含まれない
    Subject: [Hello]ERROR (EXTERNAL IP): Internal Server Error: /force_500
    From: [email protected]
    To: [email protected]
    Internal Server Error: /force_500
    ...
    Request Method: GET
    Request URL: http://localhost:8000/force_500
    ...
    YEAR_MONTH_FORMAT =3D 'F Y'

    View Slide

  85. ローカル変数もほしい時
    単純な方法:Debug=True
    の時のHTML
    を含める
    # settings
    DEFAULT_LOGGING['handlers']['mail_admins']['include_html'] = True

    View Slide

  86. 動作確認
    例外を発生するView
    def force_500(request):
    my_local_value = '
    ハロー'
    raise Exception('Error')

    View Slide

  87. ローカル変数を含むサーバーエラー通知メール
    Subject: [Hello]ERROR (EXTERNAL IP): Internal Server Error: /force_500
    From: [email protected]
    To: [email protected]
    ...


    my_local_value
    '
    ハロー'

    View Slide

  88. サーバーエラー通知メールの注意点
    サーバー情報が漏洩する可能性あり
    settings
    ローカル変数
    POST
    データ
    必要に応じて、内容をマスクする

    View Slide

  89. settings
    に対する、自動マスク機能
    以下のパターンを含む設定名は、Django
    が自動でマスク
    API
    KEY
    PASS
    SECRET
    SIGNATURE
    TOKEN

    View Slide

  90. 動作確認
    settings
    DJANGO_CONGRESS_PASSPORT = '123'
    DJANGO_CONGRESS_PASSWORD = '456'
    DJANGO_CONGRESS_PASTA = '789'

    View Slide

  91. 結果
    DJANGO_CONGRESS_PASSPORT =3D '********************'
    DJANGO_CONGRESS_PASSWORD =3D '********************'
    DJANGO_CONGRESS_PASTA =3D '789'

    View Slide

  92. ローカル変数に対するマスク
    以下の場合、デフォルトではそのまま表示
    DEFAULT_LOGGING['handlers']['mail_admins']['include_html'] = True
    @sensitive_variables
    デコレータを使う

    View Slide

  93. 一部をマスクする例
    View
    @method_decorator(sensitive_variables('region', 'year'), name='dispatch')
    class MaskedLocalVariableView(TemplateView):
    """
    一部ローカル変数をマスク """
    template_name = 'myapp/breaking.html'
    def get(self, request, *args, **kwargs):
    conference = 'DjangoCongress'
    region = 'JP' #
    マスクする
    year = '2019' #
    マスクする
    raise Exception

    View Slide

  94. 一部マスクの結果

    Local Vars
    conference
    'DjangoCongress'
    region
    '********************'
    year
    '********************'

    View Slide

  95. すべてをマスクする例
    View
    @method_decorator(sensitive_variables(), name='dispatch')
    class AllMaskedLocalVariableView(TemplateView):
    ...

    View Slide

  96. エラー通知に含まれるPOST
    データ
    デフォルトでは、そのまま表示
    Exception Type: Exception at /post_parameters
    POST:
    apple =3D 'Shinano Gold'
    grape =3D 'Shine Muscat'
    pear =3D 'Southern Suite'
    @sensitive_post_parameters
    デコレータを使う

    View Slide

  97. 一部をマスクする例
    View
    @method_decorator(sensitive_post_parameters('grape', 'pear'),
    name='dispatch')
    class MaskedPostParameterView(FormView):
    def post(self, request, *args, **kwargs):
    raise Exception

    View Slide

  98. 一部マスクの結果
    Request information:
    POST:
    apple =3D 'Shinano Gold'
    grape =3D '********************'
    pear =3D '********************'

    View Slide

  99. すべてをマスクする例
    View
    @method_decorator(sensitive_post_parameters(), name='dispatch')
    class AllMaskedPostParameterView(FormView):
    ...

    View Slide

  100. ちなみに
    ローカル変数とPOST
    データを両方マスク
    @method_decorator(sensitive_variables('year'), name='dispatch')
    @method_decorator(sensitive_post_parameters('grape'), name='dispatch')
    class DoubleMaskedView(FormView):
    ...

    View Slide

  101. マスク機能を自作
    デコレータをView
    に付けたくない
    一括でマスクしたい
    自作のマスク機能を作成することで対応可能
    SafeExceptionReporterFilter
    のサブクラスが推奨

    View Slide

  102. マスク機能
    class MyReporterFilter(SafeExceptionReporterFilter):
    def get_post_parameters(self, request):
    """ POST
    データをマスク """
    cleansed = request.POST.copy()
    cleansed['grape'] = '?' * 30
    return cleansed
    def get_traceback_frame_variables(self, request, tb_frame):
    """
    トレースバック中のローカル変数をマスク """
    cleansed = {}
    for name, value in tb_frame.f_locals.items():
    if name == 'year':
    value = '?' * 30
    else:
    value = self.cleanse_special_types(request, value)
    cleansed[name] = value
    return cleansed.items()

    View Slide

  103. 自作マスク機能を使用
    settings
    DEFAULT_EXCEPTION_REPORTER_FILTER = 'myapp.reporter_filter.MyReporterFilter'

    View Slide

  104. マスク結果
    Request information:
    POST:
    apple =3D 'Shinano Gold'
    grape =3D '??????????????????????????????'
    pear =3D 'Southern Suite'
    ...

    Local Vars
    conference
    'DjangoCongress'
    region
    'JP'
    year
    '??????????????????????????????'

    View Slide

  105. リンク切れの通知設定
    #
    本番運用モードにする
    DEBUG = False
    #
    リンク切れを通知するミドルウェアを追加
    MIDDLEWARE += ['django.middleware.common.BrokenLinkEmailsMiddleware']
    #
    送信先の site managers
    のメールアドレスを設定
    # ('
    メールアドレスコメント', '
    メールアドレス')
    MANAGERS = [('Manager1', '[email protected]')]
    #
    他、使用するEmailBackend
    の設定を行う

    View Slide

  106. リンク切れの通知設定 (
    任意)
    # ADMINS
    と共用
    SERVER_EMAIL = '...'
    EMAIL_SUBJECT_PREFIX = '...'
    # HTTP404
    でもエラーレポートメールを送信したくないURL
    がある場合は、正規表現で指定
    IGNORABLE_404_URLS = [
    re.compile(r'^/ignore_404$'),
    ]

    View Slide

  107. 動作確認
    Referer
    ありで、HTTP 404
    #
    実際は1

    $ curl -H "Referer:http://localhost:8000/breaking_link"
    http://localhost:8000/force_404
    Not FoundThe requested resource was not found on this server.

    View Slide

  108. 受信メール
    Subject: [Hello]Broken INTERNAL link on localhost:8000
    From: [email protected]
    To: [email protected]
    Referrer: http://localhost:8000/breaking_link
    Requested URL: /force_404
    User agent: curl/7.54.0
    IP address: 127.0.0.1

    View Slide

  109. まとめ
    メール送信まわりは標準搭載
    unittest
    ショートカット関数
    エラー通知
    メール送信まわりの機能は、いずれも拡張・自作可
    エラー通知メールは、情報漏えいに注意
    必要に応じてマスク

    View Slide

  110. Enjoy Django email life!!

    View Slide

  111. Appendix

    View Slide

  112. エンコーディングの変更
    メールのエンコーディング
    非ASCII
    文字を送信するために使用
    ヘッダ
    件名
    Content-Type

    View Slide

  113. デフォルトの挙動
    件名に日本語をセット
    email = EmailMessage(subject='
    件名です', ...)
    受信メール
    Content-Type: text/plain; charset="utf-8"
    Content-Transfer-Encoding: 7bit
    Subject: =?utf-8?b?5Lu25ZCN44Gn44GZ?=

    View Slide

  114. メールのエンコーディング設定
    settings.DEFAULT_CHARSET
    utf-8
    HttpResponse
    オブジェクトのエンコーディング

    View Slide

  115. EmailMessage
    オブジェクトの属性
    encoding
    メールのエンコーディングを変更可能

    View Slide


  116. エンコーディングを ISO-2022-JP
    に変更
    email = EmailMessage(subject='
    件名です', ...)
    email.encoding = 'iso-2022-jp'

    View Slide

  117. 結果
    受信メール
    Content-Type: text/plain; charset="iso-2022-jp"
    Content-Transfer-Encoding: 7bit
    Subject: =?iso-2022-jp?b?GyRCN29MPiRHJDkbKEI=?=

    View Slide

  118. Thanks!!

    View Slide