revision-up-to: | 8961 (1.0) |
---|
このチュートリアルは チュートリアルその 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">
{% 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>
簡単に説明しましょう:
さあ、今度は提出されたデータを処理するための Django ビューを作成しましょう。 チュートリアルその 3 で、以下のような行を polls アプリケーションの URLconf に入れたことを思い出しましょう:
(r'^(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'),
そういうわけで、 mysite/polls/views.py に vote() 関数を作ります:
from django.shortcuts import get_object_or_404, render_to_response
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from mysite.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': "選択肢を選んでいません。",
})
else:
selected_choice.votes += 1
selected_choice.save()
# ユーザが Back ボタンを押して同じフォームを提出するのを防ぐ
# ため、POST データを処理できた場合には、必ず
# HttpResponseRedirect を返すようにします。
return HttpResponseRedirect(reverse('mysite.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>
さあ、ブラウザで /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.defaults import *
urlpatterns = patterns('mysite.polls.views',
(r'^$', 'index'),
(r'^(?P<poll_id>\d+)/$', 'detail'),
(r'^(?P<poll_id>\d+)/results/$', 'results'),
(r'^(?P<poll_id>\d+)/vote/$', 'vote'),
)
これを以下のように変更しましょう:
from django.conf.urls.defaults import *
from mysite.polls.models import Poll
info_dict = {
'queryset': Poll.objects.all()
}
urlpatterns = patterns('',
(r'^$', 'django.views.generic.list_detail.object_list', info_dict),
(r'^(?P<object_id>\d+)/$',
'django.views.generic.list_detail.object_detail', info_dict),
url(r'^(?P<object_id>\d+)/results/$',
'django.views.generic.list_detail.object_detail',
dict(info_dict, template_name='polls/results.html'), 'poll_results'),
(r'^(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'),
)
この例では二つの汎用ビュー、 object_list() と object_detail() を使っています。こ れらのビューはそれぞれ、「オブジェクトのリストを表示する」および「あるタイ プのオブジェクトの詳細ページを表示する」という二つの概念を抽象化しています。
デフォルトでは、 object_detail() 汎 用ビューは <app name>/<model name>_detail.html という名前のテンプレート を使います。私達のアプリケーションでは、テンプレートの名前は "polls/poll_detail.html" です。そこで、 vote() の render_to_response() の行に書かれている polls/detail.html テンプレートを polls/poll_detail.html に変更しま す。
同様に、 object_list() 汎用ビューも <app name>/<model name>_list.html という名前のテンプレートを使うので、 polls/index.html を polls/poll_list.html にしておきます。
一つの polls アプリケーションの URLconf に object_detail() テンプレートを使う エントリが複数あるので、開票結果ビューの方のテンプレート名は手動で template_name='polls/results.html' と指定してやります。そうしないと、二 つのビューが両方とも同じテンプレートを使おうとしてしまいます。 dict() を使って新たな辞書を置き換えで返していることに注意して下さい。
Note
django.db.models.QuerySet.all`() は遅延呼び出しされます
詳細 (detail) ビューを使う際、たった一つの Poll オブジェクトが必要 にすぎないにもかかわらず、 Poll.objects.all() が使われていることに ぎょっとしたかもしれませんね。でも心配はいりません。 Poll.objects.all() は実際には「クエリセット ( QuerySet)」と呼ばれる特殊なオブジェクトで、 その実行は「遅延 (lazy)」型です。すなわち、実際に必要になるまでデータベー スを操作しないのです。データベースクエリが実行される際、汎用ビュー object_detail() はクエリの範囲 を単一のオブジェクトにまで狭めるので、結果的にクエリはデータベースから ただ一行のレコードしか選択しません。
この仕組みをもっと詳しく理解したければ、データベース API ドキュメントの QuerySet オブジェクトの遅延実行の説明 <querysets-are-lazy> を参照して ください。
このチュートリアルの前の部分では、 poll や latest_poll_list といった変数の入ったコンテキスト (context) をテンプレートに渡していました。 しかしながら、汎用ビューはコンテキストに object や object_list とい う変数を提供するようになっているので、コンテキスト変数に合わせてテンプレー トを変更する必要があります。テンプレートを編集して、 latest_poll_list を object_list() に、 poll を object に変更しておいてください。
さて、 index(), detail() および results() ビューのコードを polls/views.py から削除できるようになりました。これらのビュー関数は汎用 ビューで置き換わったので、もう必要ありません。
vote() ビューはまだ必要ですが、新たなコンテキスト変数に合わせて修正せね ばなりません。 render_to_response`() の中の poll コンテキスト変数を object に変更してください。
最後に、 URL が汎用ビューを指すように修正します。上の vote ビューでは、 reverse() 関数を使って URL のハードコードを 防いでいます。汎用ビューに切替えたので、 reverse() を変更して、URL が新しく追加した 汎用ビューを指すようにします。汎用ビューのビュー関数を使えれば簡単なのです が、汎用ビューというものは一つのサイトの中で何度も使われることがあるので、 そういうわけにはいかないのです。そこで、先程指定しておいたビューの名前を使 います:
return HttpResponseRedirect(reverse('poll_results', args=(p.id,)))
サーバを実行して、新しく汎用ビューベースにした投票アプリケーションを使って みましょう。
汎用ビューの詳細は 汎用ビューのドキュメント を参照してくだ さい。
Aug 31, 2012