========== 汎用ビュー ========== :revision-up-to: 17812 (1.4) .. versionchanged:: 1.3 .. note:: Django 1.3 から、クラスベースの汎用ビューが採用されたので、関数ベースの汎用 ビューは廃止されました。クラスベースビューの :doc:`トピックガイド ` と :doc:`詳細なリファレンス ` に記述されています。 Web アプリケーションの作成は、同じパターンを何度も何度も繰り返し書くことに なるため、退屈なものです。 Django は、こうした単調作業をモデルやテンプレー トのレイヤで軽減しようとしていますが、 Web 開発者はビューレベルでも退屈さを 感じています。 Django の *汎用ビュー (generic views)* はその苦労を無くすために開発されま した。ビューの開発時に見かける、特定の共通するイディオムとパターンを汎用ビュー という形で抽象化しています。余計なコードを書かずによく定義されるビューを素早く 実装できます。 我々はオブジェクトのリスト表示などの、ありふれたタスクを知っています。 *任意の* オブジェクトをリスト表示するコードも提供できます。その上、リスト表示させたい モデルは URLconf に追加の引数として渡せます。 Django の汎用ビューで以下のようなことができます: * 別のページへリダイレクトし、与えられたテンプレートをレンダリングするような、 よくある簡単なタスクの処理。 * リストと単一オブジェクトの詳細ページを表示。カンファレンスを管理するアプリケー ションを作っているなら、 ``talk_list`` ビューと ``registerd_user_list`` ビュー にリストビューを、単一の発表に関するページに詳細ビューが使えます。 * 日付ベースのオブジェクトを年/月/日のアーカイブで表示。詳細と最新ページで 関連付けて表示します。 Django のブログ (http://www.djangoproject.com/weblog/) の年月日ごとのアーカイブはこれで作られています。 * 認証あり、または無しでユーザにオブジェクトを生成、更新、削除を許可。 汎用ビューは開発者が遭遇する、よくあるタスクを果たすための簡単なインターフェース を提供しています。 汎用ビューを使う ================ これらビューはすべて、 URLconf ファイルに設定の入った辞書を作成し、 URLconf で URL パターンを記述しているタプルの 3 つめのメンバにその辞書を渡せば使えます。 例えばここでは、静的な "about" ページを提示するために使えるシンプルな URLconf を 示します:: from django.conf.urls import patterns, url, include from django.views.generic.simple import direct_to_template urlpatterns = patterns('', ('^about/$', direct_to_template, { 'template': 'about.html' }) ) これは一見、ちょっとした "魔法" のように見えるかもしません。だってほら、コー ド無しのビューですよ!実際には ``direct_to_template`` ビューは単に、特別なパ ラメータの入った辞書から情報を取得し、レンダリングに使用しています。 汎用ビューは独自のビュー内で再利用できます。汎用ビューは、その他ビューと同じよう に普通のビュー関数だからです。例として、 "about" を拡張して静的にレンダリングさ れた ``about/.html`` にURL ``/about//`` を対応させてみましょ う。まずは、ビュー関数を指定するように URLconf を変更します: .. parsed-literal:: from django.conf.urls import patterns, url, include from django.views.generic.simple import direct_to_template **from books.views import about_pages** urlpatterns = patterns('', ('^about/$', direct_to_template, { 'template': 'about.html' }), **('^about/(\\w+)/$', about_pages),** ) 次に ``about_pages`` ビューを書きましょう:: from django.http import Http404 from django.template import TemplateDoesNotExist from django.views.generic.simple import direct_to_template def about_pages(request, page): try: return direct_to_template(request, template="about/%s.html" % page) except TemplateDoesNotExist: raise Http404() ここでは ``direct_to_template`` をほかの関数と同じように扱っています。 ``direct_to_template`` は ``HttpResponse`` をそのまま返すので、戻り値を単純にそ のまま返せます。唯一トリッキーでやっかいなのが、存在しないテンプレートを扱ってい る点です。存在しないテンプレートがサーバエラーを引き起こさないように ``TemplateDoesNotExist`` 例外をキャッチし、代わりに404エラーを返しています。 .. admonition:: セキュリティ上の脆弱性はありますか? 目ざとい読者ならセキュリティホールの可能性に気がついてるかもしれません。ブラ ウザから補間されたコンテンツを使ってテンプレート名を構築しているからです (``template="about/%s.html" % page``) 。一見すると、古典的な *ディレクトリト ラバーサル* の脆弱性のようです。しかし本当でしょうか? そうではありません。たしかに、悪意をもって作成された ``page`` の値なら、 ディレクトリトラバーサルを引き起こす可能性があります。しかし、 ``page`` の値はリクエストURLから取得されますが、必ずしもすべての値が受け入れられるわ けではありません。鍵は URLconf にあります。正規表現 ``\w+`` を ``page`` の URL の一部とマッチさせるために使っています。 ``\w`` はアルファベットと数字の み受け付けます。なので、悪意ある文字列 (ここではドットやスラッシュなど) は ビュー自体に到達する前に、 URL リゾルバに拒否されます。 オブジェクトの汎用ビュー ======================== ``direct_to_template`` は確かに便利です。しかし、 Django の汎用ビューが真価を 発揮するのは、データベースの内容についてビューを提示するときです。驚くほど簡単に オブジェクトのリスト、詳細ビューを生成できる組み込みの汎用ビューが、 Django には少数付属しています。それらはよくあるタスクだからです。 それでは汎用ビューの 1 つ、 "オブジェクトリスト (object list)" ビューを見てみま しょう。以下のようなモデルを使います:: # models.py from django.db import models class Publisher(models.Model): name = models.CharField(max_length=30) address = models.CharField(max_length=50) city = models.CharField(max_length=60) state_province = models.CharField(max_length=30) country = models.CharField(max_length=50) website = models.URLField() def __unicode__(self): return self.name class Meta: ordering = ["-name"] class Book(models.Model): title = models.CharField(max_length=100) authors = models.ManyToManyField('Author') publisher = models.ForeignKey(Publisher) publication_date = models.DateField() 全ての出版社 (publisher) のリストページを構築するため、以下のような URLconf を 使います:: from django.conf.urls import patterns, url, include from django.views.generic import list_detail from books.models import Publisher publisher_info = { "queryset" : Publisher.objects.all(), } urlpatterns = patterns('', (r'^publishers/$', list_detail.object_list, publisher_info) ) 書くべき Python コードは以上です。しかしテンプレートも書く必要があります。追加の 引数の辞書に ``template_name`` キーを含めることにより、どのテンプレートを使用す るべきであるか ``object_list`` ビューに明示的に伝えることができます。明示的なテ ンプレートがない場合 Django は、オブジェクトの名前からテンプレートを推論します。 今回の場合、推論されるテンプレートは ``"books/publisher_list.html"`` となるで しょう。 "books" という部分はモデルを定義しているアプリケーションの名前からきて います。 "publisher" は単にモデル名が lowercase になったものです。 .. highlightlang:: html+django このテンプレートは、 ``object_list`` 変数を含むコンテキストに対してレンダリング されます。 ``object_list`` はすべての Publisher オブジェクトからなります。とても シンプルなテンプレートにするなら以下のようになるでしょう:: {% extends "base.html" %} {% block content %}

Publishers

{% endblock %} 本当にこれだけです。汎用ビューに渡される "info" 辞書を変更することで、クールな 機能をすべて使用できます。すべての汎用ビューとオプションの詳細は :doc:`generic views reference` に記されています。このドキュ メントの残りでは、よくある汎用ビューのカスタマイズ、拡張の方法を記しています。 汎用ビューの拡張 ================ .. highlightlang:: python 疑いようも無く、汎用ビューを使用すると開発スピードは大幅に上がります。しかし多 くのプロジェクトでは、汎用ビューでは物足りないときがあります。確かに、新米の Django 開発者からくる質問で典型的なのは、汎用ビューをより幅広い状況で扱うことに ついてです。 幸いほぼすべてのケースで、汎用ビューを簡単に拡張して、より大きいユースケースを 処理するようにできます。通常これらの状況は、以下のセクションで扱うごく少数の パターンに分類できます。 "フレンドリー" なテンプレートコンテキストを作る ----------------------------------------------- サンプルの出版社リストテンプレートでは、 ``object_list`` 変数にすべての 書籍 (book) オブジェクトが格納されていると思ってしまうかもしれません。現状でも うまく動作しますが、テンプレートの作者にとって "フレンドリー" ではありません。 テンプレート作者は、ここでは出版社のことだけを知っているべきです。より良い変数名は ``publisher_list`` です。変数の内容がかなり明らかになっています。 ``template_object_name`` 引数を変更することで、簡単に ``object_list`` の名前を 変えられます: .. parsed-literal:: publisher_info = { "queryset" : Publisher.objects.all(), **"template_object_name" : "publisher",** } urlpatterns = patterns('', (r'^publishers/$', list_detail.object_list, publisher_info) ) 便利な ``template_object_name`` を提供するのは常によいことです。テンプレートの デザインを担当する同僚は、あなたに感謝することでしょう。 追加のコンテキスト ------------------ 汎用ビューで提供されるもの以上に、追加の情報を提示したいときがあります。例えば、 各出版社の詳細ページに、書籍の全リストを表示したいときなどです。汎用ビュー ``object_detail`` はコンテキストに Publisher オブジェクトを提供していますが、 テンプレートに追加の情報を与えることはできなさそうです。 しかしそうではありません。すべての汎用ビューは追加のパラメータ ``extra_context`` をとります。これは追加オブジェクトの辞書で、テンプレートコンテキストに追加され ます。書籍の全リストを詳細ビューに提供するには、以下のように info 辞書を使い ます。 .. parsed-literal:: from books.models import Publisher, **Book** publisher_info = { "queryset" : Publisher.objects.all(), "template_object_name" : "publisher", **"extra_context" : {"book_list" : Book.objects.all()}** } これでテンプレートコンテキストで ``{{ book_list }}`` が使えるようになります。 このパターンは、汎用ビューのテンプレートに任意の情報を渡すために使えます。非常に 便利です。 しかし実際には、微妙なバグがあります。見つけられました? その問題は ``extra_context`` 中のクエリが評価されるときに関係します。この例で は、 URLconf に ``Book.objects.all()`` を置いているので、クエリは 1 度だけ (URLconf が最初にロードされるとき) 評価されます。書籍を追加か削除すれば、 Web サーバをリロードするまで変更が反映されません。 (クエリセットが、いつキャッ シュ、評価されるかついては :ref:`caching-and-querysets` を参照してください) 。 .. note:: この問題は汎用ビューの ``queryset`` 引数には当てはまりません。 特定の QuerySet は *絶対に* キャッシュすべきでないと Django は知っています。 汎用ビューはビューがそれぞれレンダリングされるときに、キャッシュを消去して くれます。 解決策は、値の代わりに ``extra_context`` でコールバックを使うことです。 ``extra_context`` に渡される呼び出し可能オブジェクト (つまり関数) は、ビューが レンダリングされたときに (1 度だけではなく) 評価されます。明示的に定義した関数を 使います: .. parsed-literal:: def get_books(): return Book.objects.all() publisher_info = { "queryset" : Publisher.objects.all(), "template_object_name" : "publisher", "extra_context" : **{"book_list" : get_books}** } 明示性に欠けるも、短く書く方法もあります。 ``Book.objects.all`` 自体が呼び出し 可能なので、それを利用します: .. parsed-literal:: publisher_info = { "queryset" : Publisher.objects.all(), "template_object_name" : "publisher", "extra_context" : **{"book_list" : Book.objects.all}** } ``Book.objects.all`` の後ろに丸括弧が足りませんね。実際に関数は呼び出されて いません (汎用ビューが後で呼び出します)。 オブジェクトのサブセットを表示 ------------------------------ さて、ずっと使ってきた ``queryset`` キーを詳しく見ていきましょう。ほとんどの 汎用ビューは ``queryset`` 引数を取ります。それはビューがどのオブジェクト のセットを表示すべきかを指定します。 ( ``QuerySet`` オブジェクトのについては :doc:`/topics/db/queries` 、詳細は :doc:`汎用ビューのリファレンス ` を参照してください)。 簡単な例として、出版日 (publication date) 順の書籍のリストを最新のものから取得 してみます: .. parsed-literal:: book_info = { "queryset" : Book.objects.all().order_by("-publication_date"), } urlpatterns = patterns('', (r'^publishers/$', list_detail.object_list, publisher_info), **(r'^books/$', list_detail.object_list, book_info),** ) かなり簡単な例ですが、うまく考えを表しています。もちろんオブジェクトを再整理する 以上のことをしたいでしょう。特定の出版社についての書籍を渡したい場合も、同じ テクニックが使えます: .. parsed-literal:: **acme_books = {** **"queryset": Book.objects.filter(publisher__name="Acme Publishing"),** **"template_name" : "books/acme_list.html"** **}** urlpatterns = patterns('', (r'^publishers/$', list_detail.object_list, publisher_info), **(r'^books/acme/$', list_detail.object_list, acme_books),** ) フィルタされた ``queryset`` 以外に、テンプレート名を指定しています。そうしな いと、汎用ビューは同じテンプレートを "ありきたりな" オブジェクトリストとして使い 回します。たぶん期待とは違うことでしょう。 これは、ある出版社固有の書籍を扱ううえでエレガントな方法ではありません。他の 出版社のページを追加したいなら、 URLconf に手動で追記する必要があるからです。 追記したうちのいくつかは不当 (unreasonable) になるでしょう。詳しくは次のセク ションで扱います。 .. note:: ``/books/acme/`` をリクエストするの際に 404 になったときは、 'ACME Publishing' という名の Publisher が実際に存在するか確かめてください。 こういったケースに備えて、汎用ビューは ``allow_empty`` というパラメータを 持っています。詳細は :doc:`汎用ビューのリファレンス ` を参照してください。 ラッパー関数で多重フィルタリング -------------------------------- 他によくあるニーズは、 URL のキーでオブジェクトをフィルタしてから、オブジェクト をリストページに渡すことです。すでに出版社名は URLconf にハードコードしてい ますが、ある任意の出版社について書籍をすべて表示するビューを書きたいなら、どう しますか? ``object_list`` 汎用ビューを "ラップ (wrap)" して大量のコードを手書き することを避けられます。いつも通り、 URLconf から書きます: .. parsed-literal:: from books.views import books_by_publisher urlpatterns = patterns('', (r'^publishers/$', list_detail.object_list, publisher_info), **(r'^books/(\\w+)/$', books_by_publisher),** ) 次に、 ``books_by_publisher`` ビュー自体を書きます:: from django.http import Http404 from django.views.generic import list_detail from books.models import Book, Publisher def books_by_publisher(request, name): # publisher を取得 (見つからなければ 404 エラーを返します)。 try: publisher = Publisher.objects.get(name__iexact=name) except Publisher.DoesNotExist: raise Http404 # 重労働には object_list view ビューを使います。 return list_detail.object_list( request, queryset = Book.objects.filter(publisher=publisher), template_name = "books/books_by_publisher.html", template_object_name = "books", extra_context = {"publisher" : publisher} ) 汎用ビューはとくに特別なものでもないので、これで動作します。単なる Python の関数 です。他のビューのように、汎用ビューは引数のセットを期待し、 ``HttpResponse`` オブジェクトを返します。このように、汎用ビューを小さな関数でラップするのは非常に 簡単です。ラッパー関数は、汎用ビューに引き渡す前 (もしくは後。詳細は次のセクション で) に追加の処理をします。 .. note:: 先の例では、表示されている Publisher オブジェクトを ``extra_context`` に 渡しています。この性質のラッパーにおいては良いアイディアです。どの "親" オブジェクトが現在閲覧されているかをテンプレートに知らせられるかれです。 追加の処理をさせる ------------------ 最後の共通パターンは、汎用ビューを呼び出す前か後に、追加の処理をさせることです。 ``Author`` オブジェクト中に ``last_accessed`` フィールドがあると想像してください。 著者 (author) が最後に問い合わせされた時間を記録します:: # models.py class Author(models.Model): salutation = models.CharField(max_length=10) first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=40) email = models.EmailField() headshot = models.ImageField(upload_to='/tmp') last_accessed = models.DateTimeField() ``object_detail`` 汎用ビューはもちろん、このフィールドについては知りません。 しかし再び、このフィールドの更新を維持するためのカスタムビューを書けます。 初めに、カスタムビューを指定するために URLconf に著者の詳細に関する 1 行を追加 します: .. parsed-literal:: from books.views import author_detail urlpatterns = patterns('', #... **(r'^authors/(?P\\d+)/$', author_detail),** ) そしてラッパー関数を書きます:: import datetime from books.models import Author from django.views.generic import list_detail from django.shortcuts import get_object_or_404 def author_detail(request, author_id): # Author を取得 (見つからなければ 404 エラーを返します)。 author = get_object_or_404(Author, pk=author_id) # 最終アクセス日時を記録します。 author.last_accessed = datetime.datetime.now() author.save() # 詳細ページを表示します。 return list_detail.object_detail( request, queryset = Author.objects.all(), object_id = author_id, ) .. note:: 実際には、このコードには ``books/author_detail.html`` テンプレートが必要 です。 汎用ビューのレスポンスを変更するために、同じイディオムが使えます。著者リストの プレーンテキスト版をダウンロード可能にするには、以下のようなビューが使えます:: def author_list_plaintext(request): response = list_detail.object_list( request, queryset = Author.objects.all(), mimetype = "text/plain", template_name = "books/author_list.txt" ) response["Content-Disposition"] = "attachment; filename=authors.txt" return response これで動作するのは、汎用ビューがシンプルな ``HttpResponse`` オブジェクトを返す からです。 ``HttpResponse`` オブジェクトを辞書のように扱って、 Http セッダを設定 できます。この ``Content-Disposition`` の編集によって、ブラウザにページ表示する 代わりに、ダウンロードと保存するよう指示します。