revision-up-to: | 17812 (1.4) |
---|
このチュートリアルは チュートリアルその 2 の続き です。ここでは、引続きWeb 投票アプリケーションの開発を例にして、公開用のイ ンタフェース、ビュー(view) の作成を焦点に解説します。
ビューとは、 Django のアプリケーションにおいて特定の機能を提供するウェブペー ジの「型 (type)」であり、独自のテンプレートを持っています。例えばブログアプ リケーションなら、以下のようなビューがあるでしょう:
Poll アプリケーションの場合には、以下の 4 つのビューを作成します:
Django では、各ビューは簡単な Python の関数として表現されます。
ビューを書く上での最初のステップは、 URL 構造の設計です。 URL 構造を定義す るには、 URLconf と呼ばれる Python モジュールを作成します。 Django は URLconf を使ってどの URL をどの Python コードに関連づけるかを決めています。
ユーザが Django で作られたページをリクエストすると、システムは
ROOT_URLCONF
設定を探します。この設定には Python モジュールをドッ
ト表記で表した文字列が入っています。 Django は指定されたモジュールをロード
して、 urlpatterns
という名前のモジュールレベル変数を探します。
urlpatterns
は以下のような形式のタプルからなるシーケンスです:
(正規表現, Pythonのコールバック関数, [, オプションの辞書オブジェクト])
Django は先頭のタプルから順に、リクエストされた URL とタプル内の正規表現が マッチするまでテストしてゆきます。
マッチする正規表現が見つかると、Django は該当するタプルに指定されているコー
ルバック関数を呼び出します。コールバック関数には
HttpRequest
オブジェクトを第一引数として渡します。
さらに、正規表現内で「キャプチャした (captured)」値をキーワード引数として渡
します。(タプルの三番目の要素である) オプションの辞書オブジェクトが指定され
ていれば、その内容も追加のキーワード引数として渡します。
HttpRequest`
オブジェクトの詳細は
リクエストオブジェクトとレスポンスオブジェクト を参照してください。 URLconf の詳細は
URL ディスパッチャ を参照してください。
チュートリアルその 1 の冒頭で
python django-admin.py startproject mysite
を実行していれば、URLconf が
mysite/urls.py
にできているはずです。また、(settings.py
の)
ROOT_URLCONF
設定にも、自動的に値が入ります:
ROOT_URLCONF = 'mysite.urls'
さて、例題を使って練習する時が来ました。 mysite/urls.py
を編集して、
以下のような内容にしましょう:
from django.conf.urls import patterns, include, url
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
url(r'^polls/$', 'polls.views.index'),
url(r'^polls/(?P<poll_id>\d+)/$', 'polls.views.detail'),
url(r'^polls/(?P<poll_id>\d+)/results/$', 'polls.views.results'),
url(r'^polls/(?P<poll_id>\d+)/vote/$', 'polls.views.vote'),
url(r'^admin/', include(admin.site.urls)),
)
復習しておいたほうがよいですね。だれかが Web サイトのあるページ、例えば
“/polls/23” をリクエストすると、 Django は ROOT_URLCONF
に書かれている
この Pythonモジュールをロードします。Django はモジュール内の
urlpatterns
という変数を探し、その中に入っている正規表現を順に検査して
ゆきます。マッチする正規表現が見つかった (r'^polls/(?P<poll_id>\d+)/$'
ですね) ところで、 Django はこの正規表現に関連づけられている Python パッケー
ジ/モジュールである detail()
を polls/views.py
からロードし
ます。これは最後に、 Django は以下のような引数で detail()
を呼び出し
ます:
detail(request=<HttpRequest object>, poll_id='23')
poll_id='23'
の部分は、 (?P<poll_id>\d+)
からきています。パターンを
丸括弧で囲うと、パターンにマッチしたテキストを「キャプチャ」して、ビュー関
数の引数として送り込みます。 ?P<poll_id>
はマッチしたパターンを識別する
ための名前をつけています。 \d+
は数字の列 (すなわち番号) にマッチする正
規表現です。
URL パターンは正規表現なので、正規表現で実現できる限り無制限の URL を表現で
きます。また、 .php
のようなくだらない文字列を URL に追加する必要もあり
ません。ただし、病的なユーモアの持ち主のために、以下のようにすれば実現でき
ることは示しておきましょう:
(r'^polls/latest\.php$', 'polls.views.index'),
とはいえ、こんな阿呆なことはやめましょう。
これらの正規表現では GET や POST のパラメタ、またドメイン名を検索しないこと
に注意してください。例えば、 http://www.example.com/myapp/
というリクエ
ストが来ると、 URLconf は /myapp/
を探します。
http://www.example.com/myapp/?page=3
の場合にも、 URLconf は
/myapp/
を探します。
正規表現をよく理解できないなら、 Wikipedia のエントリ や re
モジュ
ールのドキュメントを参照してください。また、オライリーから出版されている本、
「詳説 正規表現」 (Jeffrey Friedl 著、 訳注: 田和 勝 訳) に秀逸な解説が
あります。
最後に、パフォーマンスについての注意です: 正規表現は URLconf モジュールをロー ドする時にコンパイルされ、極めて高速に動作します。
さてと、まだビューを作っていませんね。まだ URLconf しかありません。しかし、 まずは Django が URLconf を正しく理解できているか調べておきましょう。
Django 開発サーバを起動してください:
python manage.py runserver
Web ブラウザで “http://localhost:8000/polls/” に行ってみましょう。以下のよ うなメッセージの入った綺麗に彩られたエラーページが表示されるはずです:
ViewDoesNotExist at /polls/
Could not import polls.views.index. View does not exist in module
polls.views.
このエラーは、まだ index()
という関数を polls/views.py
に書いていな
いために発生しています。
“/polls/23/” や “/polls/23/results/” 、 “/polls/23/vote/” も試して下さい。 エラーメッセージから、 Django がどのビューを試した (そしてまだビューを書い ていないので失敗した) かがわかります。
いよいよ最初のビューを書きましょう。 polls/views.py
というファイ
ルを開いて、以下のような Python コードを書いてください:
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world. You're at the poll index.")
最も単純なビューです。ブラウザで “/polls/” にアクセスすると、テキストが表示 されるはずです。
もう少しビューを追加しましょう。これらのビューはさきほどと少し違って、引数 をとります (この引数には、URLconf の正規表現内でキャプチャされたものが渡さ れます):
def detail(request, poll_id):
return HttpResponse("You're looking at poll %s." % poll_id)
def results(request, poll_id):
return HttpResponse("You're looking at the results of poll %s." % poll_id)
def vote(request, poll_id):
return HttpResponse("You're voting on poll %s." % poll_id)
ブラウザを使って “/polls/34/” を見てください。これは detail() メソッドを 呼びだし、 URL に指定した ID を表示します。 “/polls/34/results/” と “/polls/34/vote/” を見てください。それぞれ開票結果ページと投票ページを表示 します。
各ビューは、リクエストされたページのコンテンツが入った
HttpResponse
オブジェクトを返すか、
Http404
のような例外を送出するかの 2 つの動作のうち、い
ずれかを実行せねばなりません。それ以外の処理は全てユーザにゆだねられていま
す。
ビューはデータベースからレコードを読みだしても、読み出さなくてもかまいませ ん。 Django のテンプレートシステム (あるいはサードパーティの Python テンプ レートシステム) を使ってもよいですし、使わなくてもかまいません。 PDF ファイ ルを生成しても、 XML を出力しても、 ZIP ファイルをオンザフライで生成しても かまいません。 Python ライブラリを使ってやりたいことを何でも実現できます。
Django にとって必要なのは HttpResponse
か、あるいは
例外です。
簡単のため、 チュートリアルその 1 で解説した
Django のデータベース API を使ってみましょう。 index()
ビューを、システ
ム上にある最新の 5 件の質問項目をカンマで区切り、日付順に表示させてみます:
from polls.models import Poll
from django.http import HttpResponse
def index(request):
latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
output = ', '.join([p.question for p in latest_poll_list])
return HttpResponse(output)
残念ながらこのコードには問題があります。ページのデザインがビュー中にハード コードされているのです。これでは、ページの見栄えを変えたくなるたびに Python コードをいじらねばなりません。というわけで、 Django のテンプレートシステム を使って、デザインと Python コードを分離しましょう:
from django.template import Context, loader
from polls.models import Poll
from django.http import HttpResponse
def index(request):
latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
t = loader.get_template('polls/index.html')
c = Context({
'latest_poll_list': latest_poll_list,
})
return HttpResponse(t.render(c))
このコードは “polls/index.html” とい名前のテンプレートをロードし、テンプレー トにコンテキストを渡します。コンテキストとは、テンプレート変数名を Python のオブジェクトに対応づけている辞書です。
ページをリロードしましょう。エラーが出るようになったはずです:
TemplateDoesNotExist at /polls/
polls/index.html
そうそう、まだテンプレートを作ってませんね。まず、ファイルシステム上の
Django がアクセス出来る場所にディレクトリを作成します (Django はサーバを実
行しているユーザと同じユーザ権限で動作します)。ただし、Web サーバのドキュメ
ントルートには置かないようにしましょう。セキュリティへの配慮として、テンプ
レートを公開するべきではないと思います。次に、 settings.py
の
TEMPLATE_DIRS
を編集して、どこでテンプレートを探せばよいか
Django に教えます。チュートリアルその 2 の「admin サイトのルック & フィール
をカスタマイズする」でやったのと同じ作業です。
設定がおわったら、テンプレートディレクトリに polls
というディレクトリを
作成します。このディレクトリの中に、 index.html
という名前のファイルを
作成してください。 loader.get_template('polls/index.html')
というコード
は、ファイルシステム上の “[template_directory]/polls/index.html” に対応する
ことに注意して下さい。
テンプレートには以下のようなコードを書きます:
{% if latest_poll_list %}
<ul>
{% for poll in latest_poll_list %}
<li><a href="/polls/{{ poll.id }}/">{{ poll.question }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %}
ブラウザでページをロードすると、チュートリアル 1 で作った “What’s up” とい う投票項目の入ったブレットリストを表示します。リンクは詳細ページを指します。
テンプレートをロードしてコンテキストに値を入れ、テンプレートをレンダリング
した結果を HttpResponse
オブジェクトで返す、というイ
ディオムは非常によく使われます。 Django はこのイディオムのためのショートカッ
トを提供しています。ショートカットを使って index()
ビューを書き換えてみ
ましょう:
from django.shortcuts import render_to_response
from polls.models import Poll
def index(request):
latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
return render_to_response('polls/index.html',
{'latest_poll_list': latest_poll_list})
この作業によって、 loader
や
Context
、 HttpResponse
を
import する必要はなくなりました。
render_to_response()
関数は第一引数にテンプレートの
名前をとり、オプションの第二引数に辞書をとります。
render_to_response()
はテンプレートを指定のコンテキ
ストでレンダリングし、 HttpResponse
オブジェクトにし
て返します。
さて、今度は Poll の詳細ページを片付けましょう。このページは Poll の質問文 (question フィールド) を表示します。ビューは以下のようになります:
from django.http import Http404
# ...
def detail(request, poll_id):
try:
p = Poll.objects.get(pk=poll_id)
except Poll.DoesNotExist:
raise Http404
return render_to_response('polls/detail.html', {'poll': p})
新しい概念がでて来ました。このビューはリクエストした ID を持つ Poll が存在
しないときに Http404
を送出します。
polls/detail.html
テンプレートに何を書けばよいかは後で解説しますが、さ
しあたって上の例題を動かしたければ、単に:
{{ poll }}
と書いておいてください。
get()
を実行し、該当オブジェクトがな
い場合には Http404
を返す、という作業は非常によく使われ
るイディオムです。 Django はこのイディオムのためのショートカットを提供してい
ます。ショートカットを使って detail()
ビューを書き換えてみましょう:
from django.shortcuts import render_to_response, get_object_or_404
# ...
def detail(request, poll_id):
p = get_object_or_404(Poll, pk=poll_id)
return render_to_response('polls/detail.html', {'poll': p})
get_object_or_404()
は Django のモデルクラスを第一
引数にとり、任意の個数のキーワード引数をとります。キーワード引数はそのまま
get()
メソッドに渡ります。オブジェク
トが存在しなければ Http404
を送出します。
設計哲学
なぜ ObjectDoesNotExist
例外を高水準で自
動的にキャッチせず、ヘルパー関数
get_object_or_404()
を使うのでしょうか、また、
なぜモデルAPI に ObjectDoesNotExist
では
なく Http404
を送出させるのでしょうか?
答えは、「モデルレイヤとビューレイヤをカップリングしてしまうから」です。 Django の最も大きな目標の一つは、ルーズカップリングの維持にあります。
get_list_or_404()
という関数もあります。この関数は
get_object_or_404()
と同じように働きますが、
get()
ではなく
filter()
を使います。リストが空の
場合は Http404
を送出します。
ビュー内で Http404
を送出すると、 Django は 404 エラー
処理用の特殊なビューをロードします。このビューは URLconf にある
handler404
という変数 ( URLconf にあるもののみ有効です。その他に設定さ
れたものは影響がありません) を参照して見つけます。変数は URLconf のコール
バックで使っているのと同じ、 Python のドット表記法で表した関数名の文字列で
す。 404 ビュー自体に特殊なところはありません。単なる普通のビューです。
普通はわざわざ苦労して 404 ビューを書く必要はありません。もし handler404
を設定しなかった場合は、ビルトインビューである
django.views.defaults.page_not_found()
がデフォルトで使用されます。
この場合、 404.html
テンプレートをテンプレートディレクトリのルートに
作成する必要があります。デフォルトの 404 ビューはこのテンプレートを全ての
404 エラーに対して使用します。もし (設定モジュールで) DEBUG
を False
に設定していて、なおかつ 404.html
を作成しなかった場合、
Http500
が代わりに送出されます。 404.html
を作成することを覚えて
おいてください。
404 ビューに関するいくつかの注意すべき点:
DEBUG
を True
に設定している場合、
Django は 404 ビューを使わず、トレースバックを表示します
(404.html
も使われません)。404 と同じように、ルートの URLconf で handler500
を定義できます。
handler500
はサーバエラーが生じたときに呼び出されるビューを指します。
サーバエラーになるのは、ビューコードに実行時エラーが生じた場合です。
detail()
ビューに戻りましょう。コンテキスト変数を poll
とすると、
polls/detail.html
テンプレートは以下のように書けます:
<h1>{{ poll.question }}</h1>
<ul>
{% for choice in poll.choice_set.all %}
<li>{{ choice.choice }}</li>
{% endfor %}
</ul>
テンプレートシステムはドットを使った表記法で変数の属性にアクセスします。
{{ poll.question }}
を例にとると、まず Django は poll
オブジェクト
を辞書とみなして question
の値を探します。これには失敗するので、今度は
属性として照合を行い、この場合は成功します。仮に属性の照合に失敗すると、
Django は リストインデックスでの照合を行おうとします。
メソッド呼び出しは {% for %}
ループの中で行われています。
poll.choice_set.all
は、 Python のコード poll.choice_set.all()
に解
釈されます。その結果、 Choice オブジェクトからなるイテレーション可能オブジェ
クトを返し {% for %}
タグで使えるようになります。
テンプレートの詳しい動作は テンプレートガイド を 参照してください。
しばらくビューとテンプレートシステムをいじってみてください。 URLconf を編集 していくうちに、実はかなり冗長な部分があることに気づくかもしれません:
urlpatterns = patterns('',
url(r'^polls/$', 'polls.views.index'),
url(r'^polls/(?P<poll_id>\d+)/$', 'polls.views.detail'),
url(r'^polls/(?P<poll_id>\d+)/results/$', 'polls.views.results'),
url(r'^polls/(?P<poll_id>\d+)/vote/$', 'polls.views.vote'),
)
明らかに、どのコールバックにも polls.views
が入っています。
これはよくあることなので、 URLconf フレームワークではプレフィクスを共有する
ためのショートカットがあります。共通のプレフィクスを切り出して、以下のよう
に patterns()
の最初の引数に追加してくださ
い:
urlpatterns = patterns('polls.views',
url(r'^polls/$', 'index'),
url(r'^polls/(?P<poll_id>\d+)/$', 'detail'),
url(r'^polls/(?P<poll_id>\d+)/results/$', 'results'),
url(r'^polls/(?P<poll_id>\d+)/vote/$', 'vote'),
)
機能的には前の形式と全く同じです。でもとてもすっきりしましたね。
普通、一つのアプリに対してのプレフィクスを URLconf にある全てのコールバック
に適用させたくはないので、複数の patterns()
を結合
させることが出来ます。 mysite/urls.py
は以下のようになるでしょう:
from django.conf.urls import patterns, include, url
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('polls.views',
url(r'^polls/$', 'index'),
url(r'^polls/(?P<poll_id>\d+)/$', 'detail'),
url(r'^polls/(?P<poll_id>\d+)/results/$', 'results'),
url(r'^polls/(?P<poll_id>\d+)/vote/$', 'vote'),
)
urlpatterns += patterns('',
url(r'^admin/', include(admin.site.urls)),
)
ところで、すこし時間を割いて polls アプリケーションの URL 設定を Django プ ロジェクトの設定から切り離しましょう。 Django アプリケーションはプラグ可能、 つまりあれこれ設定しなくても、特定のアプリケーションを別の Django インストー ルに移動できるようになっています。
今のところ、 python manage.py startapp
で作成した厳密なディレクトリ構成
のおかげで polls アプリケーションはかなりうまく脱カップリングできていますが、
一点だけ現在の Django の設定とカップリングしている場所があります: それは
URLconf です。
これまでは URL 設定を mysite/urls.py
で編集してきましたが、本来アプリケー
ションの URL 設計はアプリケーション固有のものであって、特定の Django インス
トールとは関係のないものです。そこで、 URL 設定をアプリケーションディレクト
リ内にもってきましょう。
mysite/urls.py
ファイルを polls/urls.py
にコピーしてください。
次に mysite/urls.py
から polls に関する URL 設定を全て削除し、以下の
include()
を一つだけ書くと、以下が残ります:
from django.conf.urls import patterns, include, url
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
url(r'^polls/', include('polls.urls')),
url(r'^admin/', include(admin.site.urls)),
)
簡単に説明すると、 include`()
は別の URLconf への
参照です。正規表現に $
(文字列の末尾にマッチする) が付いておらず、代り
にスラッシュがついていることに注意してください。 Django は
~django.conf.urls.include
を見つけると、リクエスト URL 中のマッ
チした部分を切り離し、残りの部分を include されている URLconf に送って処理
させます。
従って、このシステムでユーザが “/polls/34/” にアクセスすると、次のように処 理されます:
'^polls/'
へのマッチを検出します。"polls/"
) を取り去り、残りのテキスト
である "34/"
を ‘polls.urls’ という URLconf に送り、処理させ
ます。これでプロジェクト側の脱カップリングはできました。今度は
polls.urls
側を脱カップリングするために、 URLconf の各行から、
先頭の “polls/” を削除してください。 polls/urls.py
ファイルは以下のよう
になります:
from django.conf.urls import patterns, include, url
urlpatterns = patterns('polls.views',
url(r'^$', 'index'),
url(r'^(?P<poll_id>\d+)/$', 'detail'),
url(r'^(?P<poll_id>\d+)/results/$', 'results'),
url(r'^(?P<poll_id>\d+)/vote/$', 'vote'),
)
include()
と URLconf の脱カップリングの背景には、
URL のプラグ & プレイを簡単にしようという発想があります。今や polls は独自の
URLconf を持っているので、 “/polls/” や “/fun_polls/”, “/content/polls/”
といった、どんな パスルート下にも置けて、どこに置いてもきちんと動作します。
polls アプリケーションは絶対パスではなく相対パスだけに注意しているわけです。
ビューの作成に慣れたら、 チュートリアルその 4 に 進んで、簡単なフォーム処理と汎用ビュー (generic view) の使い方を学びましょ う。
Oct 26, 2017