revision-up-to: | 17812 (1.4) |
---|
このチュートリアルは チュートリアルその 3 の続き です。ここでは、引続き Web 投票アプリケーションの開発を例にして、簡単なフォー ム処理とコードの縮小化を中心に解説します。
それでは、前回のチュートリアルで作成した Poll の詳細ビュー用テンプレート
("polls/detail.html"
) を更新して、 HTML <form>
エレメントを入れてみ
ましょう:
<h1>{{ poll.question }}</h1>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
<form action="/polls/{{ poll.id }}/vote/" method="post">
{% csrf_token %}
{% for choice in poll.choice_set.all %}
<input type="radio" name="choice" id="choice{{ forloop.counter }}"
value="{{ choice.id }}" />
<label for="choice{{ forloop.counter }}">{{ choice.choice }}</label><br />
{% endfor %}
<input type="submit" value="投票する" />
</form>
簡単に説明しましょう:
value
は Choice の ID に関連づけられています。
ラジオボタンの name
はいずれも "choice"
です。つまり、投票者
がラジオボタンのいずれかを選択してフォームを提出 (submit) すると、
choice=3
という内容のPOST データを送信します。これは HTML フォー
ムの基本ですね。action
を /polls/{{ poll.id }}/vote/
に設定し、
method="post"
にしています。 (method="get"
ではなく)
method="post"
を使っている点は極めて重要です。というのも、このフォー
ムの提出はサーバ側のデータの更新につながるからです。サーバ側のデータ
を更新するようなフォームを作成するときは、常に method="post"
を使
いましょう。これは Django 固有の話ではなく、いわば Web 開発の王道です。forloop.counter
は、 for
タグのループが何度実行されたかを
表す値です。{% csrf_token %}
テンプレートタグを使いましょう。{% csrf_token %}
タグは、テンプレートコンテキストから
はアクセスできないようなリクエストオブジェクトの情報を必要とします。このた
めに、少し detail
ビューに変更を加える必要があります。変更を加えた後は
以下のようになります:
from django.template import RequestContext
# ...
def detail(request, poll_id):
p = get_object_or_404(Poll, pk=poll_id)
return render_to_response('polls/detail.html', {'poll': p},
context_instance=RequestContext(request))
これがどのように動くかについての詳細は、 RequestContext で詳しく説明し ています。
さあ、今度は提出されたデータを処理するための Django ビューを作成しましょう。 チュートリアルその 3 で、以下のような行を polls アプリケーションの URLconf に入れたことを思い出しましょう:
(r'^(?P<poll_id>\d+)/vote/$', 'vote'),
また、 vote()
関数のダミーの実装も作成しました。今度は本物を作成しま
しょう。以下を polls/views.py
に追加してください:
from django.shortcuts import get_object_or_404, render_to_response
from django.http import HttpResponseRedirect, HttpResponse
from django.core.urlresolvers import reverse
from django.template import RequestContext
from polls.models import Choice, Poll
#...
def vote(request, poll_id):
p = get_object_or_404(Poll, pk=poll_id)
try:
selected_choice = p.choice_set.get(pk=request.POST['choice'])
except (KeyError, Choice.DoesNotExist):
# Poll 投票フォームを再表示します。
return render_to_response('polls/detail.html', {
'poll': p,
'error_message': "選択肢を選んでいません。",
}, context_instance=RequestContext(request))
else:
selected_choice.votes += 1
selected_choice.save()
# ユーザが Back ボタンを押して同じフォームを提出するのを防ぐ
# ため、POST データを処理できた場合には、必ず
# HttpResponseRedirect を返すようにします。
return HttpResponseRedirect(reverse('polls.views.results', args=(p.id,)))
このコードには、これまでのチュートリアルで扱っていなかったことがいくつか 入っています:
request.POST
は辞書ライクなオ
ブジェクトです。このオブジェクトを使うと、キー名を使って入力されたデー
タにアクセスできます。この例では、 request.POST['choice']
で投票者の選んだ選択肢を文字列で返させています。
request.POST
に入っている値は
常に文字列です。
Django では、 POST と同様、 GET データにアクセスするための
request.GET
も提供しています。
ただし、このコードでは、POST を経由した呼び出しでないとデータを更新さ
せないようにするために、
request.POST
を明示的に使って
います。
choice
が POST データ上になければ、 request.POST['choice']
は
KeyError
を送出します。上のコードでは KeyError
をチェッ
クして、 choice
がない場合にはエラーメッセージ付きの Poll フォー
ムを再表示しています。
choice のカウントを増やした後で、 HttpResponse
ではなく HttpResponseRedirect
を返しています。
HttpResponseRedirect
はリダイレクト先の URL 一
つだけを引数にとります (ここでは
reverse()
を使って URL を生成していま
すが、これについては後で説明します)。
上のコードの Python コメント文で指摘しているように、 POST データの処
理に成功したときは常に HttpResponseRedirect
を
返してください。これは Django 固有の話ではなく、 Web 開発の王道です。
例では、 HttpResponseRedirect
のコンストラクタ
の中で reverse()
という関数を使ってい
ます。この関数を使うと、ビュー関数中での URL のハードコードを防げます。
reverse()
にはビューの名前を渡し、同
時に URL パターンからビューにマップするときに取り出される変数を指定し
ます。上の例では、 reverse()
は
チュートリアルその 3 で設定した URLconfに従っ
て:
'/polls/3/results/'
のような URL を返します。 3
は p.id
の値です。リダイレクト先
の URL は 'results'
ビューを呼び出し、最終的なページを表示します。
(プレフィクスを含めた) ビューの完全な名前を指定せねばならないので注意
してください。
チュートリアルその 3 で触れたように、
request
は HTTPRequest
オブジェクトです。
HTTPRequest
の詳細は
リクエスト・レスポンスオブジェクトのドキュメント
を参照してください。
投票者が Poll に投票すると、 vote()
ビューは開票結果ページにリダイレク
トします。開票ページを書きましょう:
def results(request, poll_id):
p = get_object_or_404(Poll, pk=poll_id)
return render_to_response('polls/results.html', {'poll': p})
テンプレート名が違うことだけを除き、
チュートリアルその 3 の detail()
とほとんど同
じですね。この冗長さは後で修正することにします。
今度は results.html
テンプレートを作成します:
<h1>{{ poll.question }}</h1>
<ul>
{% for choice in poll.choice_set.all %}
<li>{{ choice.choice }} -- {{ choice.votes }} 票</li>
{% endfor %}
</ul>
<a href="/polls/{{ poll.id }}/">Vote again?</a>
さあ、ブラウザで /polls/1/
を表示して、投票してみましょう。票を入れるた
びに、結果のページが更新されていることがわかるはずです。選択肢を選ばずにフォー
ムを提出すると、エラーメッセージを表示するはずです。
チュートリアルその 3 の detail()
と
results()
という二つのビューはバカバカしいくらいに単純で、先程も述べた
ように冗長です。(これまた チュートリアルその 3 の)
Poll のリストを表示する index()
ビューも同様です。
こうしたビューは、基本的な Web 開発においてよくあるケース。すなわち、URL を 介して渡されたパラメタに従ってデータベースからデータを取り出し、テンプレー トをロードして、レンダリングしたテンプレートを返す、というケースを体現して います。これはきわめてよくあるケースなので、 Django では「汎用ビュー (generic view)」というショートカットのシステムを提供しています。
汎用ビューとは、よくあるパターンを抽象化して、 Python コードすら書かずにア プリケーションを書き上げられる状態にしたものです。
これまで作成してきた polls アプリケーションを汎用ビューシステムに変換して、 コードをばっさり捨てられるようにしましょう。変換にはほんの数ステップしかか かりません。そのステップとは:
です。詳しく見てゆきましょう。
なぜ今更コードを入れ換えるの?
一般に Django アプリケーションを書く場合は、まず自分の問題を解決するため に汎用ビューが適しているか考えた上で、最初から汎用ビューを使い、途中ま で書き上げたコードをリファクタすることはありません。ただ、このチュート リアルでは中核となるコンセプトに焦点を合わせるために、わざと「大変な」 ビューの作成に集中してもらったのです。
電卓を使う前に、算数の基本を知っておかねばならないのと同じです。
まず polls の URLconf である polls/urls.py
を開きます。チュートリアルで
のこれまでの作業から、中身は以下のようになっているはずです:
from django.conf.urls import patterns, include, url
urlpatterns = patterns('polls.views',
url(r'^$', 'index'),
url(r'^(?P<poll_id>\d+)/$', 'detail'),
url(r'^(?P<poll_id>\d+)/results/$', 'results'),
url(r'^(?P<poll_id>\d+)/vote/$', 'vote'),
)
これを以下のように変更しましょう:
from django.conf.urls import patterns, include, url
from django.views.generic import DetailView, ListView
from polls.models import Poll
urlpatterns = patterns('',
url(r'^$',
ListView.as_view(
queryset=Poll.objects.order_by('-pub_date')[:5],
context_object_name='latest_poll_list',
template_name='polls/index.html')),
url(r'^(?P<pk>\d+)/$',
DetailView.as_view(
model=Poll,
template_name='polls/detail.html')),
url(r'^(?P<pk>\d+)/results/$',
DetailView.as_view(
model=Poll,
template_name='polls/results.html'),
name='poll_results'),
url(r'^(?P<poll_id>\d+)/vote/$', 'polls.views.vote'),
)
この例では二つの汎用ビュー、
ListView
と
DetailView
を使っています。こ
れらのビューはそれぞれ、「オブジェクトのリストを表示する」および「あるタイ
プのオブジェクトの詳細ページを表示する」という二つの概念を抽象化しています。
model
パラメタによって提供されます。DetailView
汎用ビューには、
"pk"
という名前で URL から プライマリキー をキャプチャ
して渡すことになっています。そこで、汎用ビュー向けに poll_id
を
pk
に書き換えてあります。poll_results
という名前をつけてあります。
こうすると、このビューを呼び出すような URL を後で生成できます
(詳しくは「 名前付きパターン <naming-url-patterns> 」の説明を参照し
てください。)また、 django.conf.urls
モジュールの
url()
関数を使っています。上の例のように、
パターン名を指定する場合には、 url()
を使う
よう薦めます。デフォルトでは、 DetailView
汎用
ビューは <app name>/<model name>_detail.html
という名前のテンプレート
を使います。私達のアプリケーションでは、テンプレートの名前は
"polls/poll_detail.html"
です。
template_name
引数は Django に自動生成されたデフォルトのテンプレート名
ではなく、指定した名前を使うように伝えるために使われます。また、
result
リストビューにも template_name
を指定します。これは結果
(result) ビューと詳細 (detail) ビューが、お互い実は
DetailView
であるにも関わらず、
レンダリングされたときに違った外観を持っているためです。
同様に、 ListView
汎用ビューも
<app name>/<model name>_list.html
という名前のテンプレートを使うので、
template_name
を使って ListView
に既存の polls/index.html
テンプレートを使用するように伝えます。
このチュートリアルの前の部分では、 poll
や latest_poll_list
といった変数の入ったコンテキスト (context) をテンプレートに渡していました。
DetailView には、 poll
という変数が自動的に渡されます。なぜなら、今
私達は Django モデル (Poll
) を使用していて、 Django はコンテキスト
変数にふさわしい名前を決めることができるからです。一方で、 ListView では、
自動的に生成されるコンテキスト変数は poll_list
となります。これを上書
きするには、 context_object_name
オプションを与えて、
latest_poll_list
を代わりに使用すると指定します。この代替アプローチと
して、新しいデフォルトのコンテキスト変数と一致するようにテンプレートを変
えることもできます。しかし、ただ Django に使用したい変数名を伝えるほうが
簡単でしょう。
さて、 index()
, detail()
および results()
ビューのコードを
polls/views.py
から削除できるようになりました。これらのビュー関数は汎用
ビューで置き換わったので、もう必要ありません。
最後に、 URL が汎用ビューを指すように修正します。上の vote
ビューでは、
reverse()
関数を使って URL のハードコードを
防いでいます。汎用ビューに切替えたので、
reverse()
を変更して、URL が新しく追加した
汎用ビューを指すようにします。汎用ビューのビュー関数を使えれば簡単なのです
が、汎用ビューというものは一つのサイトの中で何度も使われることがあるので、
そういうわけにはいかないのです。そこで、先程指定しておいたビューの名前を使
います:
return HttpResponseRedirect(reverse('poll_results', args=(p.id,)))
サーバを実行して、新しく汎用ビューベースにした投票アプリケーションを使って みましょう。
汎用ビューの詳細は 汎用ビューのドキュメント を参照してく ださい。
Oct 26, 2017