forked from django-docs-ja/django-docs-ja
/
tutorial04.txt
292 lines (225 loc) · 12.2 KB
/
tutorial04.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
=====================================
はじめての Django アプリ作成,その 4
=====================================
:revision-up-to: 4805 (release 0.96)
このチュートリアルは `チュートリアルその 3`_ の続きです.ここでは,引続き
Web 投票アプリケーションの開発を例にして,簡単なフォーム処理とコードの縮小
を焦点に解説します.
.. _Write a simple form:
簡単なフォームを書く
====================
それでは,前回のチュートリアルで作成した Poll の詳細ビュー用テンプレートを
更新して, HTML ``<form>`` エレメントを入れてみましょう::
<h1>{{ poll.question }}</h1>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
<form action="/polls/{{ poll.id }}/vote/" method="post">
{% 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>
簡単に概要説明しましょう:
* 上のテンプレートでは, 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 開発の王道です.
さあ,今度は提出されたデータを処理するための Django ビューを作成しましょう.
`チュートリアルその 3`_ で,以下のような行を polls アプリケーションの
URLconf に入れたことを思い出しましょう::
(r'^(?P<poll_id>\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 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('/polls/%s/results/' % p.id)
このコードには,これまでのチュートリアルで扱っていなかったことがいくつか
入っています:
* ``request.POST`` は辞書ライクなオブジェクトで,キー名を使って入力され
たデータにアクセスできます.この例では, ``request.POST['choice']``
で投票者の選んだ選択肢を文字列で返させています. ``request.POST`` に
入っている値は常に文字列です.
Django では, POST と同様, GET データにアクセスするための
``request.GET`` も提供しています.ただし,このコードでは,POST を経由
した呼び出しでないとデータを更新させないようにするために,
``request.POST`` を明示的に使っています.
* ``choice`` が POST データ上になければ, ``request.POST['choice']`` は
``KeyError`` を送出します.上のコードでは ``KeyError`` をチェックして,
``choice`` がない場合にはエラーメッセージ付きの Poll フォームを再表示
しています.
* choice のカウントを増やした後で, ``HttpResponse`` ではなく
``HttpResponseRedirect`` を返しています. ``HttpResponseRedirect`` は
リダイレクト先の URL 一つだけを引数にとります.できるだけ "http://"
とドメイン名は入れないようにします.そうすることで,アプリケーション
をドメイン間で移植しやすくなります.
上のコードの Python コメント文で指摘しているように, POST データの処
理に成功したときは常に ``HttpResponseRedirect`` を返すようにしてくだ
さい.これは Django 固有の話ではなく, Web 開発の王道です.
チュートリアルその 3 で触れたように, ``request`` は ``HTTPRequest`` オブジェ
クトです. ``HTTPRequest`` の詳細は
`リクエストオブジェクトとレスポンスオブジェクトのドキュメント`_ を参照して
ください.
投票者が 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})
テンプレート名が違うことだけを除き,`チュートリアルその 3` の ``detail()``
とほとんど同じですね.この冗長さは後で修正することにします.
今度は ``results.html`` テンプレートを作成します::
<h1>{{ poll.question }}</h1>
<ul>
{% for choice in poll.choice_set.all %}
<li>{{ choice.choice }} -- {{ choice.votes }} 票</li>
{% endfor %}
</ul>
さあ,ブラウザで ``/polls/1/`` を表示して,投票してみましょう.票を入れるた
びに,結果のページが更新されていることがわかるはずです.選択肢を選ばずにフォー
ムを提出すると,エラーメッセージを表示するはずです.
.. _`リクエストオブジェクトとレスポンスオブジェクトのドキュメント`:
../request_response/
.. _request and response documentation:
../request_response/
.. _Use generic views: Less code is better:
汎用ビューを使う: コードが少ないのはいいことだ
==============================================
`チュートリアルその 3`_ の ``detail()`` と ``results()`` という二つのビュー
はバカバカしいくらいに単純で,先程も述べたように冗長です.(これまた
`チュートリアルその 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<poll_id>\d+)/$', 'detail'),
(r'^(?P<poll_id>\d+)/results/$', 'results'),
(r'^(?P<poll_id>\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<object_id>\d+)/$',
'django.views.generic.list_detail.object_detail', info_dict),
(r'^(?P<object_id>\d+)/results/$',
'django.views.generic.list_detail.object_detail',
dict(info_dict, template_name='polls/results.html')),
(r'^(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'),
)
この例では二つの汎用ビュー, ``object_list`` と ``object_detail`` を使って
います.これらのビューはそれぞれ,「オブジェクトのリストを表示する」および
「あるタイプのオブジェクトの詳細ページを表示する」という二つの概念を抽象化
しています.
* 各汎用ビューは自分がどのデータに対して動作するのか知っておく必要があ
ります.データは辞書の形式で渡されます.辞書内の ``queryset`` という
うキーが,この汎用ビューで操作するオブジェクトのリストを指しています.
* ``object_detail`` 汎用ビューには, ``"object_id"`` という名前で URL
から ID をキャプチャして渡すことになっています.そこで,汎用ビュー向
けに ``poll_id`` を ``object_id`` に書き換えてあります.
デフォルトでは, ``object_detail`` 汎用ビューは
``<app name>/<model name>_detail.html`` という名前のテンプレートを使います.
私達のアプリケーションでは,テンプレートの名前は
``"polls/poll_detail.html"`` になります.そこで, ``vote()`` の
``render_to_response()`` の行に書かれている ``polls/detail.html`` テンプレー
トを ``polls/poll_detail.html`` に変更します.
同様に, ``object_list`` 汎用ビューも ``<app name>/<model name>_list.html``
という名前のテンプレートを使うので, ``polls/index.html`` を
``polls/poll_list.html`` にしておきます.
一つの polls アプリケーションの URLconf に ``object_detail`` テンプレートを
使うエントリが複数あるので,開票結果ビューの方のテンプレート名は手動で
``template_name='polls/results.html'`` と指定してやります.そうしないと,二
つのビューが両方とも同じテンプレートを使おうとしてしまいます. ``dict()``
を使って ``info_dict`` の値を更新した新たな辞書を返させていることに注意して
下さい.
.. note:: ``all()`` の SQL 呼び出しは遅延型です
詳細 (detail) ビューを使う際,たった一つの ``Poll`` オブジェクトが必要
にすぎないにもかかわらず, ``Poll.objects.all()`` が使われていることに
ぎょっとしたかもしれませんね.でも心配はいりません.
``Poll.objects.all()`` は実際には「クエリセット (``QuerySet``)」と呼ば
れる特殊なオブジェクトで,その実行は「遅延 (lazy)」型,すなわち,実際に
必要になるまでデータベースを操作しないようになっています.データベース
クエリが実行される際,汎用ビュー ``object_detail`` はクエリの範囲を単一
のオブジェクトにまで狭めるので,結果的にクエリはデータベースからただ一
行のレコードしか選択しません.
この仕組みをもっと詳しく理解したければ,データベース API ドキュメントの
`QuerySet オブジェクトの遅延実行の説明`_ を参照してください.
.. _explains the lazy nature of QuerySet objects: ../db-api/#querysets-are-lazy
.. _QuerySet オブジェクトの遅延実行の説明: `explains the lazy nature of QuerySet objects`_
このチュートリアルの前の部分では, ``poll`` や ``latest_poll_list``
といった変数の入ったコンテキスト (context) をテンプレートに渡していました.
しかしながら,汎用ビューはコンテキストに ``object`` や ``object_list`` とい
う変数を提供するようになっているので,コンテキスト変数に合わせてテンプレー
トを変更する必要があります.テンプレートを編集して, ``latest_poll_list``
を ``object_list`` に, ``poll`` を ``object`` に変更しておいてください.
さて, ``index()``, ``detail()`` および ``results()`` ビューのコードを
``polls/views.py`` から削除できるようになりました.これらのビュー関数は汎用
ビューで置き換わったので,もう必要ありません.
``vote()`` ビューはまだ必要ですが,新たなテンプレートとコンテキスト変数に
合わせて修正せねばなりません. ``polls/detail.html`` というテンプレートの呼
び出しを ``polls/poll_detail.html`` に変え, ``poll`` ではなく ``object``
をコンテキストに渡すようにしてください.
サーバを実行して,新しく汎用ビューベースにした投票アプリケーションを使って
みましょう.
汎用ビューの詳細は `汎用ビューのドキュメント`_ を参照してください.
.. _`汎用ビューのドキュメント`: ../generic_views/
.. _Coming soon:
次回予告
===========
このチュートリアルはここでしばらく中断します.が,下のような内容で次回以降
を予定していまから,また見に来て下さいね:
* 高度なフォーム処理
* RSS フレームワークを使う
* キャッシュフレームワークを使う
* コメントフレームワークを使う
* 高度な admin 機能: パーミッション
* 高度な admin 機能: カスタム JavaScript
.. _`チュートリアルその 3`: ../tutorial03/
.. _Tutorial 3: ../tutorial3/