チュートリアル4。いよいよ最後のチュートリアルに入りました。
pollsという投票アプリケーションを仕上げていきます。
フォームを使ったテンプレート
detailページのテンプレートを以下のように記述する。
detail.html
<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>
初めて見るワードがいくつか含まれている。
- error_message
これについては名前で何となく想像がつくだろうが、このあとviews.py側で定義する値なので別に覚える必要があるものではない。
- forloop.counter
ループをまわした回数という意味の決まり文句。まあ意味のまんまなので覚えやすい。
- csrf_token
いわゆるCSRF対策。これがいちばん重要そう。自分のサイトにPOSTメソッドを送る場合は必須と考えたほうがよさげ。
で、CSRFって何だっけ?ということでググりました。
http://e-words.jp/w/CSRF.html
これを使う都合上views.pyも編集することになる。
views.py
from django.template import RequestContext # RequestContextはContextのサブクラスらしい
# ...
def detail(request, poll_id):
pobject = get_object_or_404(Poll, pk=poll_id) # pkはPrimaryKeyの略っぽい。予約語?
return render_to_response('polls/detail.html', {'poll': pobject},
context_instance=RequestContext(request)) # context_instanceが新しく追加された。たぶん予約語
ついでにvoteの部分も実装する。
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):
pobject = get_object_or_404(Poll, pk=poll_id)
try:
selected_choice = pobject.choice_set.get(pk=request.POST['choice'])
except (KeyError, Choice.DoesNotExist):
return render_to_response('polls/detail.html', {
'poll': pobject,
'error_message': "まずは選択肢を選ぼう",
}, context_instance=RequestContext(request))
else:
selected_choice.votes += 1
selected_choice.save()
return HttpResponseRedirect(reverse('polls.views.results', args=(pobject.id,)))
ここでも見たことのないワードがいくつか出てきているのでまとめておく。
- request.POST
POSTされてきたデータを受け取ったもの。request.POST[‘name’]というようにしてnameに応じたvalueを取り出せる。どこぞの言語では$_POSTという書き方をしている気がする。
- RequestContext
Contextのサブクラス。こちらには第一引数にHttpRequestオブジェクトを入れないといけない。ここではビュー関数voteの第一引数requestをそのまま入れている(ビュー関数の第一引数にはアクセスしてきたHttpRequestがたぶん自動的に代入されている)。
- context_instance
ショートカットでrender_to_responseをリターンする場合に使う名前(おそらく固定)。HttpRequestオブジェクトを代入する。
render_to_response('for/your/template',{'name':value},context_instance)
第一引数でテンプレートを指定し、第二引数にデータを入れて、第三引数のリクエストでテンプレートにデータを渡しているという感じか?
- save()
設定したデータベースを更新。ここではselected_choice.votes(models.pyで定義したデータ名)の値を一つ増やしてから上書きしている。
それだけではよく分からないのでもう少し掘り下げてみると、
このselected_choiceはPOSTされてきた’choice’の値をPrimaryKeyとしたデータをchoice_setから参照していると思われる。
で、このchoice_setというものはpobjectから取得したchoiceのセット(hoge_setという書き方は決まり文句みたいなものっぽい。詳しくはチュートリアル1で)。
テンプレートを見直すと”choice”ラジオボタンのvalueは”{{ choice.id }}”となっていて、このidは何かというと
python manage.py sql polls
で確認できて、
"id" integer NOT NULL PRIMARY KEY
こんな感じのデータになっている(idはmodels.pyで定義しなくても自動生成される模様)。まあ要するに何番目のchoiceかという感じ。
つまり先ほどの
# def vote….
selected_choice.votes += 1
selected_choice.save()
の部分では、POSTされてきたchoiceのvalueをidとしたデータがプラス1されて保存される。
でもってテンプレートを確認するとこのidはそのままラジオボタンのvalueになっているので、結果的にラジオボタンで選択された項目のデータが更新されることになる。
ものすごく長くなって自分でも驚いているが、処理の流れとしてはおそらくそんなところだろう(違っていたら教えてください)。
- HttpResponseRedirect
これまでのプログラムではもっぱらHttpResponseだったが、ここではRedirectがくっついている。第一引数reverseでは、中身が
’/polls/[id]/results/‘
のように変換されて返される。リダイレクト先を指定?
argsはリダイレクト先に持っていく引数と思われる(results関数のpoll_idに入れられる?)。ここでは中身は一つだけだがカンマを付けるのを忘れずに!
POSTが成功した際にはリダイレクトさせるのがWebの鉄則らしい。
ということで、次はリダイレクト先resultsを設計していく。
def results(request, poll_id):
pobject = get_object_or_404(Poll, pk=poll_id)
return render_to_response('polls/results.html', {'poll': pobject})
テンプレートも作る。
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 }}/">まだ投票する?</a>
以上がチュートリアルのまとめ。
ただ、これだと多いにショートカットを使ってしまっている。それでは勉強にならないので、ショートカット不使用のものに書き換えてみた。
views.py
from polls.models import Poll,Choice
from django.http import HttpResponse,HttpResponseRedirect
from django.template import Context,loader,RequestContext
from django.core.urlresolvers import reverse
from django.http import Http404
def index(req):
latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
temp = loader.get_template('polls/index.html')
contxt = Context({
'latest_poll_list':latest_poll_list
})
return HttpResponse(temp.render(contxt))
def detail(req,poll_id):
try:
pobject = Poll.objects.get(pk=poll_id)
except Poll.DoesNotExist:
raise Http404
temp = loader.get_template('polls/detail.html')
contxt = RequestContext(req,{
'poll':pobject
})
return HttpResponse(temp.render(contxt))
def results(req,poll_id):
try:
pobject = Poll.objects.get(pk=poll_id)
except Poll.DoesNotExist:
raise Http404
temp = loader.get_template('polls/results.html')
contxt = Context({
'poll':pobject
})
return HttpResponse( temp.render(contxt) )
def vote(req,poll_id):
try:
pobject = Poll.objects.get(pk=poll_id)
except Poll.DoesNotExist:
raise Http404
try:
selected_choice = pobject.choice_set.get(pk=req.POST['choice'])
except (KeyError,Choice.DoesNotExist):
temp = loader.get_template('polls/detail.html')
contxt = RequestContext(req,{
'poll':pobject,
'error_message':"You have to select choice!"
})
return HttpResponse(temp.render(contxt))
else:
selected_choice.votes += 1
selected_choice.save()
return HttpResponseRedirect( reverse('polls.views.results',args=(pobject.id,) ) )
いちおう動作も確認。
苦戦したのはdetailやvoteのrender_to_responseで第三引数として入れられているcontext_instanceをどこに入れるのかという点だった。
RequestContext(request,{‘name’:value})という書き方で解決。
それにしても、同じようなことを何度も書いていてかっこ悪い。
流れもなんとなく分かってきたし、そろそろショートカットを使ってもいいかもしれない。