Django、メールで更新を通知

2019-07-27 / PythonDjango

概要

Djangoで、Webサイトの更新情報を通知するシリーズの一つです。メールで、Webサイトの更新情報を通知していきます。

メール通知モデルの作成

メールで通知するには、ユーザーのメールアドレスを教えてもらう必要があります。そして、そのメールアドレスをデータベースへ保管しておく必要があります。ということは、モデルが必要です。

今回は誰でも気軽に登録できるようにするため、メールアドレスを保持する独立したモデルを作ります。会員登録機能だとかがあって、会員ユーザーにだけ通知したいといった場合は、組み込みのUserモデルにあるメールアドレスを使っても良いでしょう。

次のようなモデルを作ります。

class EmailPush(models.Model):
    """メールでのプッシュ先を表す"""
    email = models.EmailField('メールアドレス', unique=True)
    is_active = models.BooleanField('有効フラグ', default=False)

    def __str__(self):
        return self.email

unique=Trueとして、メールアドレスの重複を許さないようにしています。

is_activeフィールドは、仮登録→本登録という流れを実装するために設けました。他人のメールアドレスを使って勝手に登録されるのは、非常に困ります。なので本人確認のため、ユーザーがメールアドレスを送信すると、そのメールアドレスに本登録URLを送付し、それをクリックしてもらうと本登録にする、という処理をしていきます。

更新通知メール送信処理を実装

Djangoで、Webサイトの更新情報を通知するシリーズで前もって作ったPostモデルには、メールを送信するメソッドを定義していました。空だったその中身を、次のようにしましょう。

from django.conf import settings
from django.core.mail import EmailMessage
from django.db import models
from django.template.loader import render_to_string
...
...
    def email_push(self, request):
        """記事をメールで通知"""
        context = {
            'post': self,
        }
        subject = render_to_string('blog/notify_subject.txt', context, request)
        message = render_to_string('blog/notify_message.txt', context, request)

        from_email = settings.DEFAULT_FROM_EMAIL
        bcc = [settings.DEFAULT_FROM_EMAIL]
        for mail_push in EmailPush.objects.filter(is_active=True):
            bcc.append(mail_push.email)
        email = EmailMessage(subject, message, from_email, [], bcc)
        email.send()

送信するメールの文章は、テンプレートファイルを使って作ります。その際、その記事自身と、念のためrequestオブジェクトもテンプレートファイルへ渡しています。

登録されているメールアドレスをEmailPush.objects.filter(is_active=True)として取り出し、新着記事をメールで通知します。

メール送信処理ですが、settings.pyにて次のような設定をする必要があります。詳しくはDjangoで、メールを送信もご覧ください。

# Gメールの例
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_HOST_USER = 'あなた@gmail.com'
EMAIL_HOST_PASSWORD = '2段階認証のアプリパスワードが確実'
EMAIL_USE_TLS = True
DEFAULT_FROM_EMAIL = 'toritoritorina@gmail.com'

メール本文となるテンプレートファイル

まず、Subject部分となるテンプレートファイルを、notify_subject.txtとして作ります。

Narito Blog 新着記事のお知らせ

そして大事な、メッセージの本文をnotify_message.txtとして作ります。

最新記事のお知らせです。

{{ post.title }}
{{ request.scheme }}://{{ request.get_host }}{% url 'blog:detail' post.pk %}

メールアドレス登録処理の作成

メールの送信処理は実装できたので、ユーザーがメールアドレスを登録するための処理を作っていきましょう。

URL定義の作成

次のように変更します。メール購読申し込みしたよビュー、メール購読の本登録ビュー、メール購読の申し込み完了ビュー、の3つ足しました。

from django.urls import path
from . import views

app_name = 'blog'

urlpatterns = [
    path('', views.subscribe, name='subscribe'),  # 購読ページ

    # 足す
    path('subscribe/thanks/', views.subscribe_thanks, name='subscribe_thanks'),
    path('subscribe/register/<str:token>/', views.subscribe_register, name='subscribe_register'),
    path('subscribe/done/', views.subscribe_done, name='subscribe_done'),

    # resolve_url()等で使えるように、便宜的に定義
    path('list/', views.PostIndex.as_view(), name='list'),
    path('detail/<int:pk>/', views.PostDetail.as_view(), name='detail'),
]

ビューの作成

次のようにします。

from django.conf import settings
from django.core.mail import EmailMessage
from django.core.signing import BadSignature, SignatureExpired, loads, dumps
from django.http import HttpResponseBadRequest
from django.shortcuts import render, redirect
from django.template.loader import render_to_string
from django.views import generic
from .forms import EmailForm
from .models import Post, EmailPush


def subscribe(request):
    """ブログの購読ページ"""
    form = EmailForm(request.POST or None)
    # メール購読を申し込みした場合
    if request.method == 'POST' and form.is_valid():
        push = form.save()
        context = {
            'token': dumps(push.pk),
        }
        subject = render_to_string('blog/register_notify_subject.txt', context, request)
        message = render_to_string('blog/register_notify_message.txt', context, request)

        from_email = settings.DEFAULT_FROM_EMAIL
        to = [push.email]
        bcc = [settings.DEFAULT_FROM_EMAIL]
        email = EmailMessage(subject, message, from_email, to, bcc)
        email.send()

        return redirect('blog:subscribe_thanks')

    context = {
        'form': form,
    }

    return render(request, 'blog/subscribe.html', context)


def subscribe_thanks(request):
    """メール購読ありがとう、確認メール送ったよページ"""
    return render(request, 'blog/subscribe_thanks.html')


def subscribe_register(request, token):
    """メール購読の確認処理"""
    try:
        user_pk = loads(token, max_age=60*60*24)  # 1日以内

    # 期限切れ
    except SignatureExpired:
        return HttpResponseBadRequest()

    # tokenが間違っている
    except BadSignature:
        return HttpResponseBadRequest()

    # tokenは問題なし
    else:
        try:
            push = EmailPush.objects.get(pk=user_pk)
        except EmailPush.DoesNotExist:
            return HttpResponseBadRequest()
        else:
            if not push.is_active:
                # まだ仮登録で、他に問題なければ本登録とする
                push.is_active = True
                push.save()
                return redirect('blog:subscribe_done')

    return HttpResponseBadRequest()


def subscribe_done(request):
    """メール購読完了"""
    return render(request, 'blog/subscribe_done.html')


class PostIndex(generic.ListView):
    model = Post


class PostDetail(generic.DetailView):
    model = Post

購読ページでメールアドレスを送信してもらうと、subscribeビューのif request.method内に入ります。そこではEmailPushis_active=Falseで作成し、本登録用のURLをメールで送信します。

本登録用のURLをクリックされると、subscribe_registerビューが呼ばれます。アクセスしてきたEmailPushの持ち主を判別し、問題がなければis_active=Trueで本登録にします。

メールアドレス登録用フォーム

メールアドレスを登録するためのフォームを作ります。

from django import forms
from .models import EmailPush


class EmailForm(forms.ModelForm):
    """Eメール通知の登録用フォーム"""

    class Meta:
        model = EmailPush
        fields = ('email',)

    def clean_email(self):
        email = self.cleaned_data['email']
        EmailPush.objects.filter(email=email, is_active=False).delete()
        return email

仮登録したまま放置して本登録URLをなくした人は、一般的にはもう一度仮登録をします。その際、「メールアドレスは既に使われています!」と表示されて登録ができないのは、ちょっと問題です。また、別の人のメールアドレスで仮登録してしまった場合も同様で、間違われた人が自分の意志で登録しようと思ったときに、「メールアドレスは既に使われています!」と表示されると困ります。

その場合に備えて、clean_email()を定義しています。仮登録状態で、入力されたものと同じメールアドレスを持つEmailPushがあったら、それは削除するという処理をしています。

テンプレートファイルの作成

まず、subscribe.htmlを次のように変更します。購読ページ内に、メールアドレスの入力フォームも置くことにしました。

    <h2>メールで新着記事を受け取る</h2>
    <form action="" method="POST">
        {{ form.as_p }}
        <button type="submit">送信</button>
        {% csrf_token %}
    </form>

次に、メール購読申し込み後のサンクスページのテンプレートファイルも作ります。subscribe_thanks.htmlです。

{% extends 'blog/base.html' %}

{% block content %}
    <p>ブログのメール購読を申し込みました。<br>入力いただいたメールアドレスに確認メールを送っています。</p>
{% endblock %}

そして、本登録用URLをクリックした後の、本登録したよ!ページのテンプレートファイルです。subscribe_done.htmlです。

{% extends 'blog/base.html' %}

{% block content %}
    <p>ブログのメール購読を完了しました。<br>更新を楽しみにしていてください。</p>
{% endblock %}

本登録メールの文章に使うテンプレートも作成しましょう。register_notify_subject.txtは、次のようにします。

Narito Blog メール購読申し込みの確認

register_notify_message.txtは、次のようにします。

メール通知の申し込みありがとうございます。
以下のURLにアクセスすると、購読の申し込みが完了します。
{{ request.scheme }}://{{ request.get_host }}{% url 'blog:subscribe_register' token %}

この記事の関連記事

Djangoで、メールを送信

2018-11-07 / PythonDjangoEmail

- Djangoでメールを送信する、基本的なやり方を説明していきます。

Django、Lineで更新を通知

2019-07-27 / PythonDjangoLine

- Djangoで、Webサイトの更新情報を通知するシリーズの一つです。Lineで、Webサイトの更新を伝えていきます。

Django、OneSignalでブラウザ通知

2019-07-27 / PythonDjango

- Djangoで、Webサイトの更新情報を通知するシリーズの一つです。OneSignalを利用してブラウザのプッシュ通知を行い、Webサイトの更新を伝えます。

コメント欄

記事にコメントする

まだコメントはありません。