.. _intro-tutorial04: ===================================== はじめての Django アプリ作成、その 4 ===================================== :revision-up-to: 8961 (1.0) このチュートリアルは :ref:`チュートリアルその 3 ` の続き です。ここでは、引続き Web 投票アプリケーションの開発を例にして、簡単なフォー ム処理とコードの縮小化を中心に解説します。 .. _Write a simple form: 簡単なフォームを書く ==================== それでは、前回のチュートリアルで作成した Poll の詳細ビュー用テンプレート (``"polls/detail.html"``) を更新して、 HTML ``
`` エレメントを入れてみ ましょう: .. code-block:: html+django

{{ poll.question }}

{% if error_message %}

{{ error_message }}

{% endif %} {% for choice in poll.choice_set.all %}
{% endfor %}
簡単に説明しましょう: * 上のテンプレートでは、 Poll の選択肢ごとにラジオボタンを表示していま す。各ラジオボタンの ``value`` は Choice の ID に関連づけられています。 ラジオボタンの ``name`` はいずれも ``"choice"`` です。つまり、投票者 がラジオボタンのいずれかを選択してフォームを提出 (submit) すると、 ``choice=3`` という内容のPOST データを送信します。これは HTML フォー ムの基本ですね。 * フォームの ``action`` を ``/polls/{{ poll.id }}/vote/`` に設定し、 ``method="post"`` にしています。 (``method="get"`` ではなく) ``method="post"`` を使っている点は極めて重要です。というのも、このフォー ムの提出はサーバ側のデータの更新につながるからです。サーバ側のデータ を更新するようなフォームを作成するときは、常に ``method="post"`` を使 いましょう。これは Django 固有の話ではなく、いわば Web 開発の王道です。 * ``forloop.counter`` は、 :ttag:`for` タグのループが何度実行されたかを 表す値です。 さあ、今度は提出されたデータを処理するための Django ビューを作成しましょう。 :ref:`チュートリアルその 3 ` で、以下のような行を polls アプリケーションの URLconf に入れたことを思い出しましょう:: (r'^(?P\d+)/vote/$', 'mysite.polls.views.vote'), そういうわけで、 ``mysite/polls/views.py`` に ``vote()`` 関数を作ります:: from django.shortcuts import get_object_or_404, render_to_response from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse 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': "選択肢を選んでいません。", }) else: selected_choice.votes += 1 selected_choice.save() # ユーザが Back ボタンを押して同じフォームを提出するのを防ぐ # ため、POST データを処理できた場合には、必ず # HttpResponseRedirect を返すようにします。 return HttpResponseRedirect(reverse('mysite.polls.views.results', args=(p.id,))) このコードには、これまでのチュートリアルで扱っていなかったことがいくつか 入っています: * :attr:`request.POST ` は辞書ライクなオ ブジェクトです。このオブジェクトを使うと、キー名を使って入力されたデー タにアクセスできます。この例では、 ``request.POST['choice']`` で投票者の選んだ選択肢を文字列で返させています。 :attr:`request.POST ` に入っている値は 常に文字列です。 Django では、 POST と同様、 GET データにアクセスするための :attr:`request.GET ` も提供しています。 ただし、このコードでは、POST を経由した呼び出しでないとデータを更新さ せないようにするために、 :attr:`request.POST ` を明示的に使って います。 * ``choice`` が POST データ上になければ、 ``request.POST['choice']`` は :exc:`KeyError` を送出します。上のコードでは :exc:`KeyError` をチェッ クして、 ``choice`` がない場合にはエラーメッセージ付きの Poll フォー ムを再表示しています。 * choice のカウントを増やした後で、 :class:`~django.http.HttpResponse` ではなく :class:`~django.http.HttpResponseRedirect` を返しています。 :class:`~django.http.HttpResponseRedirect` はリダイレクト先の URL 一 つだけを引数にとります (ここでは :func:`~django.core.urlresolvers.reverse` を使って URL を生成していま すが、これについては後で説明します)。 上のコードの Python コメント文で指摘しているように、 POST データの処 理に成功したときは常に :class:`~django.http.HttpResponseRedirect` を 返してください。これは Django 固有の話ではなく、 Web 開発の王道です。 * 例では、 :class:`~django.http.HttpResponseRedirect` のコンストラクタ の中で :func:`~django.core.urlresolvers.reverse` という関数を使ってい ます。この関数を使うと、ビュー関数中での URL のハードコードを防げます。 :func:`~django.core.urlresolvers.reverse` にはビューの名前を渡し、同 時に URL パターンからビューにマップするときに取り出される変数を指定し ます。上の例では、 :func:`~django.core.urlresolvers.reverse` は :ref:`チュートリアルその 3 ` で設定した URLConfに従っ て:: '/polls/3/results/' のような URL を返します。 ``3`` は ``p.id`` の値です。リダイレクト先 の URL は ``'results'`` ビューを呼び出し、最終的なページを表示します。 (プレフィクスを含めた) ビューの完全な名前を指定せねばならないので注意 してください。 :ref:`チュートリアルその 3 ` で触れたように、 ``request`` は :class:`~django.http.HTTPRequest` オブジェクトです。 :class:`~django.http.HTTPRequest` の詳細は :ref:`リクエスト・レスポンスオブジェクトのドキュメント ` を参照してください。 投票者が Poll に投票すると、 ``vote()`` ビューは開票結果ページにリダイレク トします。開票ページを書きましょう:: def results(request, poll_id): p = get_object_or_404(Poll, pk=poll_id) return render_to_response('polls/results.html', {'poll': p}) テンプレート名が違うことだけを除き、 :ref:`チュートリアルその 3 ` の ``detail()`` とほとんど同 じですね。この冗長さは後で修正することにします。 今度は ``results.html`` テンプレートを作成します: .. code-block:: html+django

{{ poll.question }}

    {% for choice in poll.choice_set.all %}
  • {{ choice.choice }} -- {{ choice.votes }} 票
  • {% endfor %}
さあ、ブラウザで ``/polls/1/`` を表示して、投票してみましょう。票を入れるた びに、結果のページが更新されていることがわかるはずです。選択肢を選ばずにフォー ムを提出すると、エラーメッセージを表示するはずです。 .. _Use generic views: Less code is better: 汎用ビューを使う: コードが少ないのはいいことだ ============================================== :ref:`チュートリアルその 3 ` の ``detail()`` と ``results()`` という二つのビューはバカバカしいくらいに単純で、先程も述べた ように冗長です。(これまた :ref:`チュートリアルその 3 ` の) Poll のリストを表示する ``index()`` ビューも同様です。 こうしたビューは、基本的な Web 開発においてよくあるケース。すなわち、URL を 介して渡されたパラメタに従ってデータベースからデータを取り出し、テンプレー トをロードして、レンダリングしたテンプレートを返す、というケースを体現して います。これはきわめてよくあるケースなので、 Django では「汎用ビュー (generic view)」というショートカットのシステムを提供しています。 汎用ビューとは、よくあるパターンを抽象化して、 Python コードすら書かずにア プリケーションを書き上げられる状態にしたものです。 これまで作成してきた polls アプリケーションを汎用ビューシステムに変換して、 コードをばっさり捨てられるようにしましょう。変換にはほんの数ステップしかか かりません。 .. admonition:: なぜ今更コードを入れ換えるの? 一般に Django アプリケーションを書く場合は、まず自分の問題を解決するため に汎用ビューが適しているか考えた上で、最初から汎用ビューを使い、途中ま で書き上げたコードをリファクタすることはありません。ただ、このチュート リアルでは中核となるコンセプトに焦点を合わせるために、わざと「大変な」 ビューの作成に集中してもらったのです。 電卓を使うには算数の基本を知っておく必要があるようなものです。 まず polls の URLconf である ``polls/urls.py`` を開きます。チュートリアルで のこれまでの作業から、中身は以下のようになっているはずです:: from django.conf.urls.defaults import * urlpatterns = patterns('mysite.polls.views', (r'^$', 'index'), (r'^(?P\d+)/$', 'detail'), (r'^(?P\d+)/results/$', 'results'), (r'^(?P\d+)/vote/$', 'vote'), ) これを以下のように変更しましょう:: 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\d+)/$', 'django.views.generic.list_detail.object_detail', info_dict), url(r'^(?P\d+)/results/$', 'django.views.generic.list_detail.object_detail', dict(info_dict, template_name='polls/results.html'), 'poll_results'), (r'^(?P\d+)/vote/$', 'mysite.polls.views.vote'), ) この例では二つの汎用ビュー、 :func:`~django.views.generic.list_detail.object_list` と :func:`~django.views.generic.list_detail.object_detail` を使っています。こ れらのビューはそれぞれ、「オブジェクトのリストを表示する」および「あるタイ プのオブジェクトの詳細ページを表示する」という二つの概念を抽象化しています。 * 各汎用ビューは自分がどのデータに対して動作するのか知っておく必要があ ります。データは辞書の形式で渡されます。辞書内の ``queryset`` という うキーが、この汎用ビューで操作するオブジェクトのリストを指しています。 * :func:`~django.views.generic.list_detail.object_detail` 汎用ビューに は、 ``"object_id"`` という名前で URL から ID をキャプチャして渡すこ とになっています。そこで、汎用ビュー向けに ``poll_id`` を ``object_id`` に書き換えてあります。 * 結果を表示するビューに ``poll_results`` という名前をつけてあります。 こうすると、このビューを呼び出すような URL を後で生成できます (詳しくは「 `名前付きパターン ` 」の説明を参照し てください。)また、 :func:`~django.conf.urls.defaults.url` 関数を使っ ています。上の例のように、パターン名を指定する場合には、 :func:`~django.conf.urls.defaults.url` を使うよう薦めます。 デフォルトでは、 :func:`~django.views.generic.list_detail.object_detail` 汎 用ビューは ``/_detail.html`` という名前のテンプレート を使います。私達のアプリケーションでは、テンプレートの名前は ``"polls/poll_detail.html"`` です。そこで、 ``vote()`` の :func:`~django.shortcuts.render_to_response` の行に書かれている ``polls/detail.html`` テンプレートを ``polls/poll_detail.html`` に変更しま す。 同様に、 :func:`~django.views.generic.list_detail.object_list` 汎用ビューも ``/_list.html`` という名前のテンプレートを使うので、 ``polls/index.html`` を ``polls/poll_list.html`` にしておきます。 一つの polls アプリケーションの URLconf に :func:`~django.views.generic.list_detail.object_detail` テンプレートを使う エントリが複数あるので、開票結果ビューの方のテンプレート名は手動で ``template_name='polls/results.html'`` と指定してやります。そうしないと、二 つのビューが両方とも同じテンプレートを使おうとしてしまいます。 ``dict()`` を使って新たな辞書を置き換えで返していることに注意して下さい。 .. note:: :meth:`django.db.models.QuerySet.all`` は遅延呼び出しされます 詳細 (detail) ビューを使う際、たった一つの ``Poll`` オブジェクトが必要 にすぎないにもかかわらず、 ``Poll.objects.all()`` が使われていることに ぎょっとしたかもしれませんね。でも心配はいりません。 ``Poll.objects.all()`` は実際には「クエリセット ( :class:`~django.db.models.QuerySet`)」と呼ばれる特殊なオブジェクトで、 その実行は「遅延 (lazy)」型です。すなわち、実際に必要になるまでデータベー スを操作しないのです。データベースクエリが実行される際、汎用ビュー :func:`~django.views.generic.list_detail.object_detail` はクエリの範囲 を単一のオブジェクトにまで狭めるので、結果的にクエリはデータベースから ただ一行のレコードしか選択しません。 この仕組みをもっと詳しく理解したければ、データベース API ドキュメントの `QuerySet オブジェクトの遅延実行の説明 ` を参照して ください。 このチュートリアルの前の部分では、 ``poll`` や ``latest_poll_list`` といった変数の入ったコンテキスト (context) をテンプレートに渡していました。 しかしながら、汎用ビューはコンテキストに ``object`` や ``object_list`` とい う変数を提供するようになっているので、コンテキスト変数に合わせてテンプレー トを変更する必要があります。テンプレートを編集して、 ``latest_poll_list`` を :func:`~django.views.generic.list_detail.object_list` に、 ``poll`` を ``object`` に変更しておいてください。 さて、 ``index()``, ``detail()`` および ``results()`` ビューのコードを ``polls/views.py`` から削除できるようになりました。これらのビュー関数は汎用 ビューで置き換わったので、もう必要ありません。 ``vote()`` ビューはまだ必要ですが、新たなコンテキスト変数に合わせて修正せね ばなりません。 :func:`~django.shortcuts.render_to_response`` の中の ``poll`` コンテキスト変数を ``object`` に変更してください。 最後に、 URL が汎用ビューを指すように修正します。上の ``vote`` ビューでは、 :func:`~django.core.urlresolvers.reverse` 関数を使って URL のハードコードを 防いでいます。汎用ビューに切替えたので、 :func:`~django.core.urlresolvers.reverse` を変更して、URL が新しく追加した 汎用ビューを指すようにします。汎用ビューのビュー関数を使えれば簡単なのです が、汎用ビューというものは一つのサイトの中で何度も使われることがあるので、 そういうわけにはいかないのです。そこで、先程指定しておいたビューの名前を使 います:: return HttpResponseRedirect(reverse('poll_results', args=(p.id,))) サーバを実行して、新しく汎用ビューベースにした投票アプリケーションを使って みましょう。 汎用ビューの詳細は :ref:`汎用ビューのドキュメント ` を参照してくだ さい。 .. _`汎用ビューのドキュメント`: ../generic_views/ .. _Coming soon: 次回予告 =========== このチュートリアルはここでしばらく中断します。今後は以下のような内容をカバー する予定です: * 高度なフォーム処理 * RSS フレームワークを使う * キャッシュフレームワークを使う * コメントフレームワークを使う * 高度な admin 機能: パーミッション * 高度な admin 機能: カスタム JavaScript さしあたっては、 :ref:`次のステップへ ` に進むとよいでしょ う。