Webアプリでフラッシュメッセージを表示する場合、ASP.NET MVCではTempData、Railsではflashあたりを使うと思います。
Djangoでは何を使うのかを調べてみたところ、標準で用意されているmessages frameworkを使うのが良さそうでした。
The messages framework | Django documentation | Django
そこで、公式ドキュメントに従い、ModelForm + CreateViewを使って試してみたときのメモを残します。
環境
準備
myapp/models.py
from django.db import models class Item(models.Model): name = models.CharField('Name', max_length=255)
myapp/forms.py
from django import forms from .models import Item class ItemForm(forms.ModelForm): class Meta: model = Item fields = '__all__'
バリデーションエラー時にフラッシュメッセージを表示
CreateViewでバリデーションエラーとなった場合 form_invalid()
が呼ばれるため、そこに実装してみます。
Modelのname
フィールドがデフォルトだと入力必須になることを利用して、未入力時にフラッシュメッセージを出すようにします。
Models - field-options | Django documentation | Django
実装
myapp/views.py
messages.error
の第一引数に渡すrequestは、self.request
で取得できるのを使います。
django - Get request.session from a class-based generic view - Stack Overflow
... from django.contrib import messages ... class ItemErrorFlashView(CreateView): ... def form_invalid(self, form): if form.has_error('name'): # Modelのnameフィールドにエラーがある場合、フラッシュメッセージを表示 messages.error(self.request, '!!ERROR!!') return super(ItemErrorFlashView, self).form_invalid(form)
myapp/templates/myapp/item_form.html
フラッシュメッセージを表示する部分を用意します。
<h1>Item Form</h1> {% if messages %} <ul class="messages"> {% for message in messages %} <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li> {% endfor %} </ul> {% endif %} ...
動作確認
ブラウザで確認すると、フラッシュメッセージが表示されました。
... <h1>Item Form</h1> <ul class="messages"> <li class="error">!!ERROR!!</li> </ul> ...
message tagsはデフォルトのままなので、HTMLのclassがerror
となっています。
Bootstrapと組み合わせる
フラッシュメッセージの見栄えを良くするため、BootstrapのAlerts
と組み合わせてみます。
Components - Alerts · Bootstrap
Djangoのmessage tagがそのままHTMLのClassになりますが、上記で見た通りデフォルトだとerror
でありBootstrapのdanger
ではありません。
対応としては、
- message_tagを
danger
、extra_tagsをalert
にする - message_tagを
alert alert-danger
にする
などが思いつきましたが、今回は後者で試します。
実装
myproject/settings.py
Level Constantに紐づくTagの値を変更するため、以下の公式ドキュメントを参考にMESSAGE_TAGS
の設定を追加します。
Settings - MESSAGE_TAGS | Django documentation | Django
from django.contrib.messages import constants as message_constants MESSAGE_TAGS = { message_constants.ERROR: 'alert alert-danger', }
myapp/templates/myapp/item_form.html
HTMLにBootstrapを追加します。今回はCDNで提供されているものを使います。
Getting started - Bootstrap CDN · Bootstrap
<head> ... <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous"> </head>
動作確認
Bootstrapでフラッシュメッセージが表示されました。
<ul class="messages"> <li class="alert alert-danger">!!ERROR!!</li> </ul>
フラッシュメッセージの削除
何らかの理由で、一度追加してしまったフラッシュメッセージを削除する方法も探してみました。
公式ドキュメントによると、
The messages are marked to be cleared when the storage instance is iterated (and cleared when the response is processed).
The messages framework - Expiration of messages | Django documentation | Django
とあり、一度イテレータを回せば良さそうでした。
イテレータを回す方法としては、
- for文
- 内包表記
- list()
などがありますが、今回はlist()
を使うことにしました。
Django: Remove message before they are displayed - Stack Overflow
なお、公式ドキュメントでcleared when the response is processed
と書かれている通り、
post()
でフラッシュメッセージを追加form_invalid()
でフラッシュメッセージを削除
をCreateViewのみで実装・確認した場合にはフラッシュメッセージが削除されませんでした。
そのため、
- CreateViewにて、
form_valid()
でフラッシュメッセージを追加し、直後に削除 - CreateViewからDetailViewへリダイレクト
- リダイレクト先のDetailViewにて、フラッシュメッセージが削除されていることを確認
という形にしました。
また、いくつもCreateViewを用意するのは手間だったので、
POST /mysite/remove-flash/
(クエリ文字列なし)- フラッシュメッセージなし
POST /mysite/remove-flash/?a=1
(クエリ文字列あり)- フラッシュメッセージあり
という動作にしました。
クエリ文字列の有無については、以下を参考にrequest.META['QUERY_STRING']
を使うこととしました。
django - Best way to get query string from a URL in python? - Stack Overflow
実装
myapp/views.py
class ItemFlashRemoveView(CreateView): model = Item form_class = ItemForm def get_success_url(self): return reverse('my:item-detail', args=(self.object.id,)) def form_valid(self, form): messages.success(self.request, 'Success (Remove ver.)') if self.request.META['QUERY_STRING'] == '': list(messages.get_messages(self.request)) return super(ItemFlashRemoveView, self).form_valid(form)
myapp/urls.py
urlpatterns = [ url(r'^remove-flash/$', views.ItemFlashRemoveView.as_view(), name='item-remove-flash'), url(r'^(?P<pk>[0-9]+)/$', DetailView.as_view(model=models.Item), name='item-detail'), ]
myapp/templates/myapp/item_detail.html
<h1>Item Detail</h1> {% if messages %} <ul class="messages"> {% for message in messages %} <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li> {% endfor %} </ul> {% endif %} <div id="main"> <p>Name: {{ object.name }}</p> </div>
myproject/settings.py
MESSAGE_TAGS = { message_constants.SUCCESS: 'alert alert-success', message_constants.ERROR: 'alert alert-danger', }
動作確認
クエリ文字列なし
クエリ文字列あり
両者の実行時ログ
前者はPOST /mysite/remove-flash/
、後者はPOST /mysite/remove-flash/?a=1
の結果であることが確認できました。
[16/Feb/2016 18:54:21] "GET /mysite/remove-flash/ HTTP/1.1" 200 883 [16/Feb/2016 18:54:55] "POST /mysite/remove-flash/ HTTP/1.1" 302 0 [16/Feb/2016 18:54:56] "GET /mysite/23/ HTTP/1.1" 200 490 [16/Feb/2016 18:55:16] "GET /mysite/remove-flash/?a=1 HTTP/1.1" 200 883 [16/Feb/2016 18:55:20] "POST /mysite/remove-flash/?a=1 HTTP/1.1" 302 0 [16/Feb/2016 18:55:20] "GET /mysite/24/ HTTP/1.1" 200 646
フラッシュメッセージがクリアされるのを防ぐ
例えば、
- ログにフラッシュメッセージの内容を表示
- 画面にもフラッシュメッセージを表示
とする場合があるとします。
その時は
messages.success(self.request, 'Success (Reuse ver.)') msgs = messages.get_messages(self.request) for m in msgs: print(m) msgs.used = False
のように、used
にFalse
を設定すれば、一度イテレータを回してもフラッシュメッセージがクリアされません。
SuccessMessageMixinを使う
CreateViewを使う場合、SuccessMessageMixin
をミックスインすることで、処理成功時のフラッシュメッセージを簡単に設定できます。
The messages framework | Django documentation | Django
なお、UpdateViewにもSuccessMessageMixinをミックスインできますが、今のところ、DeleteViewにはミックスインできないようです。
- django - success_message in DeleteView not shown - Stack Overflow #21936 (Allow delete to provide a success message through a mixin.) – Django
- Fixed #21936 -- Refactored DeleteView to be compatibale with SuccessMessageMixin by auvipy · Pull Request #5992 · django/django
また、extra_tags
を設定する方法も見当たりませんでした。
実装はこんな感じです。
class ItemFlashMixinView(SuccessMessageMixin, CreateView): ... success_message = "%(name)s was created successfully"
%(name)s
については、%(field_name)s
という表記で、ModelであるItemのnameフィールドにある値、という意味になります。
ソースコード
GitHubに上げました。
thinkAmi-sandbox/Django_messages_framework_sample
フラッシュメッセージがクリアされるのを防ぐサンプルやSuccessMessageMixinを使うサンプルも含めています。