🐍

Django CBV × SuccessMessageMixin でメッセージ最小実装(Create / Delete 対応)

に公開

CBVでフェードアウトするメッセージ表示(今回はcreate + delete)を最小で実装する方法。
※本記事は Django 4.1.5 環境で検証したものです。他バージョンで挙動が変わる可能性があります...!

環境

pc:MacBook Pro(2019)
os:macos Sequoia
django: 4.1.5

できること

django の SuccessMessageMixin を使うと、処理成功時に「◯◯を登録しました」のようなメッセージを簡単に表示できる

  • CreateView
    • ModelFormMixinを継承している
    • フォームを介するため form.cleaned_data が存在する、そのためsuccess_message 内で %(title)s のようにフィールド名を埋め込み可能
  • DeleteView
    • ModelFormMixinを継承しないため cleaned_data が空、 %(title)s のような置換は不可
    • get_success_message() などをオーバーライドして self.object.title を参照する必要あり

※ Django 4.1.5 の時点では DeleteView に ModelFormMixin が含まれないためget_success_message() のオーバーライドが必要(他バージョンで内部仕様が変わる可能性、挙動が変わる可能性あり)

動作イメージ

サンプルコード

views.py
class CreateView(SuccessMessageMixin, generic.CreateView):
    model = [model名]
    form_class = [フォーム名]
    success_url = reverse_lazy('app名:index')
    # 内部的にcleaned_dataが渡されるため%(title)s = cleaned_data["title"]と同義
    success_message = "%(title)s をアップロードしました"
・
・  
class DeleteView(SuccessMessageMixin, generic.DeleteView):
    model = [model名]
    success_url = reverse_lazy('app名:index')
    success_message = "%(title)s"

    def get_success_message(self, cleaned_data):
       return f"{self.object.title} を削除しました。"
・
・
forms.py
# 今回はforms.pyを使っていますが使わなくても良いです
from django import forms
from .models import [model名]


class MovieCreateForm(forms.ModelForm):

    class Meta:
        model = [model名]
        fields = ('title', 'description', 'thumbnail', 'upload')
        # bootstrapのクラスをカスタマイズするため定義
        # 今回は[title]を使いメッセージ表示
        widgets = {
            'title': forms.TextInput(attrs={  # <input type="text" class="form-control"
                'class': 'form-control',
            }),
            'description': forms.Textarea(attrs={  # <textarea class="form-cotrol"
                'class': 'form-control',
            }),
            'thumbnail': forms.ClearableFileInput(attrs={  # <input type="file" class="form-control-file"
                'class': "form-control-file",
            }),
            'upload': forms.ClearableFileInput(attrs={
                'class': "form-control-file",
            }),
        }
メッセージを表示させたいテンプレートファイル
{% if messages %}
    {% for message in messages %}
        <div class="alert alert-success text-center fade show" role="alert">{{ message }}</div>
    {% endfor %}
{% endif %}
・
・
<script>
  setTimeout(function () {
    document.querySelectorAll('.alert').forEach(function (el) {
      el.classList.remove('show');
    });
  }, 10000);
</script>

継承内容確認

  • CreateView に ModelFormMixin が入っていることを確認できる
  • DeleteView には FormMixin は入っているが ModelFormMixin は無いため、cleaned_data は使えない
  • mroは Method Resolution Order(メソッド解決順序)の略
$ python manage.py shell
$ from django.views import generic
$ print(generic.CreateView.__mro__)
(<class 'django.views.generic.edit.CreateView'>, <class 'django.views.generic.detail.SingleObjectTemplateResponseMixin'>
, <class 'django.views.generic.base.TemplateResponseMixin'>, <class 'django.views.generic.edit.BaseCreateView'>, <
class 'django.views.generic.edit.ModelFormMixin'>, <class 'django.views.generic.edit.FormMixin'>, <class '
django.views.generic.detail.SingleObjectMixin'>, <class 'django.views.generic.base.ContextMixin'>, <class '
django.views.generic.edit.ProcessFormView'>, <class 'django.views.generic.base.View'>, <class 'object'>)

$ print(generic.DeleteView.__mro__)
(<class 'django.views.generic.edit.DeleteView'>, <class 'django.views.generic.detail.SingleObjectTemplateResponseMixin'>
, <class 'django.views.generic.base.TemplateResponseMixin'>, <class 'django.views.generic.edit.BaseDeleteView'>, <
class 'django.views.generic.edit.DeletionMixin'>, <class 'django.views.generic.edit.FormMixin'>, <class '
django.views.generic.detail.BaseDetailView'>, <class 'django.views.generic.detail.SingleObjectMixin'>, <class '
django.views.generic.base.ContextMixin'>, <class 'django.views.generic.base.View'>, <class 'object'>)

SuccessMessageMixinの流れ

  • SuccessMessageMixin は form_valid() をオーバーライドしている
SuccessMessageMixin
from django.contrib import messages

class SuccessMessageMixin:
    """
    Add a success message on successful form submission.
    """

    success_message = ""

    def form_valid(self, form):
        response = super().form_valid(form)
        success_message = self.get_success_message(form.cleaned_data)
        if success_message:
            messages.success(self.request, success_message)
        return response

    def get_success_message(self, cleaned_data):
        return self.success_message % cleaned_data

CreateView の場合

  • CreateView は ModelFormMixin を継承している。
  • form_valid(form) が呼ばれる処理経路に乗るので、SuccessMessageMixin.form_valid() も実行される。
  • form.cleaned_data が渡されて get_success_message(cleaned_data) が呼ばれる。
  • %(title)s のような置換が効く。

DeleteView の場合

  • DeleteView は ModelFormMixin を持たない。
  • 削除処理は post() → delete() という別ルートで進む。
  • form_valid() が呼ばれない。
  • 結果的に SuccessMessageMixin.form_valid() も呼ばれない。
  • get_success_message() は自動で発火しない。なのでオーバーライドが必要

Discussion