「はじめての Django アプリ作成」チュートリアルをやったメモ その4
はじめての Django アプリ作成、その 4 — Django v1.0 documentation http://djangoproject.jp/doc/ja/1.0/intro/tutorial04.html
Django | Writing your first Django app, part 4 | Django documentation http://docs.djangoproject.com/en/1.2/intro/tutorial04/
前回の続きでチュートリアルを追ってみた個人的メモです。
環境:
- MacOS X 10.6.4
- Python 2.6.5
- Django 1.2.3
簡単なフォームを書く
詳細ビューのテンプレート polls/detail.html
に投票フォームを追加する。
# {{ poll.question}} {% if error_message %}
<p>**{{ error_message }}**</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="Vote" />
</form>
forloop.counter
とは、for
ループが何回実行されたかを表す。- 日本語版のチュートリアルには無いけど、
csrf_token
が必要。
csrf_token
settings.py で...:
MIDDLEWARE_CLASSES = (
#
'django.middleware.csrf.CsrfViewMiddleware',
)
CsrfViewMiddleware
を有効にしていると、CSRF チェックをしてくれる。
POST 処理を行う場合、render_to_response()
を使用していると、Context
だけではなく、RequestContext
も渡さないといけない。
detail()
関数を以下のように書き換える
質問に対する選択肢が表示されるページ。
# polls/views.py
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))
こんな感じになる。
質問詳細ページ
vote()
関数を作成する
選択肢を選んで投票した時に呼び出される関数。
# polls/views.py
from django.shortcuts import render_to_response, get_object_or_404
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.template import RequestContext
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' : "選択肢を選んでいません。",
}, context_instance=RequestContext(request))
else:
selected_choice.votes += 1
selected_choice.save()
# ユーザーが戻るボタンを押して同じフォームを送信するのを防ぐため
# POST データを処理できた場合には必ず HttpResponseRedirect を返す
return HttpResponseRedirect(reverse('mysite.polls.views.results', args=(p.id,)))
try
部分:
request.POST['choice']
で、POST で送信された choice というキーのデータを参照できる。
GET データにアクセスするための request.GET
もある。
except
部分:
選択肢が選ばれていなかった時に詳細ページを呼び出す。
detail()
関数でやったのと同じように、RequestContext
を渡すようにする。
選択肢が選択されずに投票した場合のエラー表示
else
部分:
choice のカウントを増やした後で、HttpResponseRedirect を返している。
引数はリダイレクト先の URL を指定する。
POST データの処理に成功した時は常に HttpResponseRedirect を返すこと!
HttpResponseRedirect 内で使用されている reverse() とは、ビューの名前を渡し、パラメーターを指定する。 例の場合だと、URLconf に従って '/polls/1/results/'のような URL を返す。
CakePHP だとこんな書き方かな:
$this->redirect(array('controller' => 'polls', 'action' => 'results', $id));
results() 関数を作成する 投票処理が行われた後に表示される開票結果のページ。
# polls/views.py
def results(request, poll_id):
p = get_object_or_404(Poll, pk=poll_id)
return render_to_response('polls/results.html', {'poll': p})
polls/mytemplates/polls/results.html:
# {{ poll.question }} {% for choice in poll.choice_set.all %} * {{ choice.choice
}} -- {{ choice.votes }} vote{{ choice.votes|pluralize }} {% endfor %}
vote{{ choice.votes|pluralize }}
という書き方をすると、choice.votes が 2 つ以上の場合は votes と複数形にしてくれる。
開票結果(results)ページ
汎用ビューを使う
detail()/results()/index() は URL を介して渡されたパラメーターに従ってデータベースからデータを取り出し、テンプレートをロードしてレンダリングしたテンプレートを返すという、よくあるパターン。 これらを簡略化するために、Django では、**汎用ビュー(generic views)**というシステムが提供されている。
汎用ビュー(generic views) よくあるパターンを抽象化して、コードを書かずにアプリケーションを書けるようにしたもの。
polls アプリケーションを汎用ビューシステムに変換する
# 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'),
)
これを以下のように変更する。
# polls/urls.py
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
: オブジェクトの詳細をページを表示する という概念を抽象化している。
- 各汎用ビューは、自分がどのデータに対して動作するのか知っておく必要がある。
info_dict という辞書内の
queryset
というキーが、この汎用ビューで操作するオブジェクトのリストを指している。 - 汎用ビューには
object_id
という名前で URL から ID が渡ってくる。 poll_id を object_id に書き換えている。 - 結果を表示するビューに
poll_results
という名前をつけている。 object_detail()
汎用ビューは<app_name>/<model_name>_detail.html
という名前のテンプレートを使う。 polls/detail.html をpoll_detail.html
というファイル名に変更する。object_list()
汎用ビューは<app_name>/<model_name>_list.html
という名前のテンプレートを使う。 polls/index.html をpoll_list.html
というファイル名に変更する。- results については、
dict(info_dict, template_name='polls/results.html')
と指定している。 これは object_detail が複数あるので、手動でテンプレートを指定しないと同じテンプレートを使用しようとするため。 dict() を使用して、新しい辞書形式で指定しているのもポイント。
テンプレートを変更
poll_list.html
{% if object_list %} {% for object in object_list %} * [{{ object.question
}}](/polls/{{ object.id }}/ "{{ object.question }}") {% endfor %} {% else %}
<p>No polls are available.</p>
{% endif %}
poll_detail.html
# {{ object.question}} {% if error_message %}
<p>**{{ error_message }}**</p>
{% endif %}
<form action="/polls/{{ object.id }}/vote/" method="post">
{% csrf_token %} {% for choice in object.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="Vote" />
</form>
results.html
# {{ object.question }} {% for choice in object.choice_set.all %} * {{
choice.choice }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }} {%
endfor %}
index()/detail()/results() 関数を削除する
vote() 関数の render_to_response() 内の poll コンテキスト変数を object に変更する
# polls/views.py
return render_to_response('polls/detail.html', {
'object': p,
'error_message' : "選択肢を選んでいません。",
}, context_instance=RequestContext(request))
HttpResponseRedirect 内のリダイレクト先を poll_results に変更する
# polls/views.py
return HttpResponseRedirect(reverse('poll_results', args=(p.id,)))