暇人じゃない

「はじめての 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/

前回の続きでチュートリアルを追ってみた個人的メモです。

「はじめての Django アプリ作成」チュートリアルをやったメモ その3
http://chocoby.jp/blog/2010/11/05/first-django-app-tutorial-memo-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>

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'),

)

ポイント

テンプレートを変更

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() 関数の rendertoresponse() 内の 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,)))