LoginSignup
17

More than 5 years have passed since last update.

DJangoメモ:はじめから(フォーム処理)

Last updated at Posted at 2013-12-20

チュートリアル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})という書き方で解決。

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

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17