DJangoメモ：はじめから（フォーム処理）


[チュートリアル４](http://docs.djangoproject.jp/en/latest/intro/tutorial04.html)。いよいよ最後のチュートリアルに入りました。
pollsという投票アプリケーションを仕上げていきます。

### フォームを使ったテンプレート ###

detailページのテンプレートを以下のように記述する。

###### detail.html ######

```django
<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 ####

```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の部分も実装する。

```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):
    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オブジェクトを代入する。

```py
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という書き方は決まり文句みたいなものっぽい。詳しくは[チュートリアル１](http://docs.djangoproject.jp/en/latest/intro/tutorial01.html)で）。
テンプレートを見直すと”choice”ラジオボタンのvalueは”{{ choice.id }}”となっていて、このidは何かというと

```
python manage.py sql polls
```

で確認できて、

>"id" integer NOT NULL PRIMARY KEY

こんな感じのデータになっている（idはmodels.pyで定義しなくても自動生成される模様）。まあ要するに何番目のchoiceかという感じ。

つまり先ほどの

```py
# def vote….
        selected_choice.votes += 1
        selected_choice.save()
```

の部分では、POSTされてきたchoiceのvalueをidとしたデータがプラス１されて保存される。
でもってテンプレートを確認するとこのidはそのままラジオボタンのvalueになっているので、結果的にラジオボタンで選択された項目のデータが更新されることになる。

ものすごく長くなって自分でも驚いているが、処理の流れとしてはおそらくそんなところだろう（違っていたら教えてください）。

- **HttpResponseRedirect**

これまでのプログラムではもっぱらHttpResponseだったが、ここではRedirectがくっついている。第一引数reverseでは、中身が

```
’/polls/[id]/results/‘
```

のように変換されて返される。リダイレクト先を指定？
argsはリダイレクト先に持っていく引数と思われる（results関数のpoll_idに入れられる？）。ここでは中身は一つだけだがカンマを付けるのを忘れずに！
POSTが成功した際にはリダイレクトさせるのがWebの鉄則らしい。

ということで、次はリダイレクト先resultsを設計していく。

```py
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 ######

```django
<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 ######

```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})という書き方で解決。

それにしても、同じようなことを何度も書いていてかっこ悪い。
流れもなんとなく分かってきたし、そろそろショートカットを使ってもいいかもしれない。

## 次回は汎用ビューの使用をやります。 ##