リリースノートを参照してください
Note
Django 1.3 の前は、汎用ビューは関数として実装されていました。ここで述べる クラスベース汎用ビューが採用されたので、関数ベース汎用ビューは廃止されま した。
Web アプリケーションの作成は、同じパターンを何度も何度も繰り返し書くことに なるため、退屈なものです。 Django は、こうした単調作業をモデルやテンプレー トのレイヤで軽減しようとしていますが、 Web 開発者はビューレベルでも退屈さを 感じています。
Django の 汎用ビュー (generic views) はその苦労を無くすために開発されま した。ビューの開発時に見かける、特定の共通するイディオムとパターンを汎用ビュー という形で抽象化しています。余計なコードを書かずによく定義されるビューを素早く 実装できます。
我々はオブジェクトのリスト表示などの、ありふれたタスクを知っています。 任意の オブジェクトをリスト表示するコードも提供できます。その上、リスト表示させたい モデルは URLconf に追加の引数として渡せます。
Django の汎用ビューで以下のようなことができます:
TalkListView
と RegisteredUserListView
に
リストビューを、単一の発表に関するページに詳細ビューが使えます。汎用ビューは開発者が遭遇する、よくあるタスクを果たすための簡単なインターフェース を提供しています。
クラスベース汎用ビュー (と、 Django が提供する基底クラスを継承したすべてのクラス ベースビュー) は、 2 つの方法で設定します。サブクラス化か、引数を直接 URLconf に 渡すとできます。
クラスベースビューをサブクラス化するとき、引数 (template_name
など) やメソッ
ド (get_context_data
など) をオバーライドします。そうして新しい値やメソッド
を提供できます。ここで、テンプレート about.html
を単に表示するビューを例と
します。 Django の汎用ビュー TemplateView
をサブクラス化して、テンプレート名をオーバーライドするとできます:
# some_app/views.py
from django.views.generic import TemplateView
class AboutView(TemplateView):
template_name = "about.html"
あとは、このビューを URLconf に追記するだけです。クラスベースビュー自体はクラス
なので、 URLconf では代わりに as_view
メソッドを使います。このメソッドが、
クラスベースビューの入り口になります:
# urls.py
from django.conf.urls import patterns, url, include
from some_app.views import AboutView
urlpatterns = patterns('',
(r'^about/', AboutView.as_view()),
)
クラスベースビューで簡単な引数を少し変えるだけなら、 as_view
を呼び出す
ときの引数としても渡せます:
from django.conf.urls import patterns, url, include
from django.views.generic import TemplateView
urlpatterns = patterns('',
(r'^about/', TemplateView.as_view(template_name="about.html")),
)
同じオーバーライドのパターンが url
属性で使えます。
url
は RedirectView
などのシンプルな
汎用ビューで使われています。
TemplateView
は確かに便利です。しかし、
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()
class Meta:
ordering = ["-name"]
def __unicode__(self):
return self.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 ListView
from books.models import Publisher
urlpatterns = patterns('',
(r'^publishers/$', ListView.as_view(
model=Publisher,
)),
)
書くべき Python コードは以上です。しかしテンプレートも書く必要があります。
as_view への引数に template_name
キーを含めることにより、どのテンプレートを
使用するべきであるかビューに明示的に伝えることができます。明示的なテンプレートが
ない場合 Django は、オブジェクトの名前からテンプレートを推論します。今回の場合、
推論されるテンプレートは "books/publisher_list.html"
となるでしょう。
“books” という部分はモデルを定義しているアプリケーションの名前からきています。
“publisher” は単にモデル名が lowercase になったものです。
Note
ここで、 TEMPLATE_LOADERS
の
django.template.loaders.app_directories.Loader
テンプレートローダを
有効にすると、テンプレートは以下の場所になります:
/path/to/project/books/templates/books/publisher_list.html
このテンプレートは、 object_list
変数を含むコンテキストに対してレンダリング
されます。 object_list
はすべての出版社オブジェクトからなります。とても
単純なテンプレートにするなら以下のようになるでしょう:
{% extends "base.html" %}
{% block content %}
<h2>Publishers</h2>
<ul>
{% for publisher in object_list %}
<li>{{ publisher.name }}</li>
{% endfor %}
</ul>
{% endblock %}
本当にこれだけです。汎用ビューに渡される “info” 辞書を変更することで、クールな 機能をすべて使用できます。汎用ビューとオプションの詳細は generic views reference に記されています。この ドキュメントの残りでは、よくある汎用ビューのカスタマイズ、拡張の方法を記して います。
疑いようも無く、汎用ビューを使用すると開発スピードは大幅に上がります。しかし多 くのプロジェクトでは、汎用ビューでは物足りないときがあります。確かに、新米の Django 開発者からくる質問で典型的なのは、汎用ビューをより幅広い状況で扱うことに ついてです。
1.3 リリース以前は単なるビュー関数で、困惑させるようなオプション群を備えていま した。これが汎用ビューを設計し直した理由の 1 つです。より良い汎用ビューの拡張 方法は、汎用ビューをサブクラス化して、属性とメソッドをオーバーライドすること です。 URLconf で大量の設定を渡すことではありません。
サンプルの出版社リストテンプレートでは、 object_list
変数にすべての出版社
オブジェクトが格納されています。現状でもうまく動作しますが、テンプレートの作者に
とって “フレンドリー” ではありません。ここでテンプレート作者には、出版社オブジェ
クトを扱うことだけを知らせるべきです。
モデルオブジェクトを扱っているなら、これはもう完了しています。オブジェクトか
クエリセットを扱っているとき、 Django は表示されているオブジェクトの
冗長名(verbose name) を使って、コンテキストを追加します。オブジェクトのリストの
場合は、複数の冗長名を使います。これはデフォルトの object_list
変数に加えて
提供されますが、同じデータが含まれています。
冗長名 (または複数の冗長名) がまだマッチしていない場合は、手動でコンテキスト変数
の名前を設定することができます。汎用ビューの context_object_name
属性で使用
するコンテキスト変数を明記できます。この例では簡単な変更だけで良いので、 URLconf
でオーバーライドします:
urlpatterns = patterns('', (r'^publishers/$', ListView.as_view( model=Publisher, context_object_name="publisher_list", )), )
便利な context_object_name
を提供するのは常によいことです。テンプレートの
デザインを担当する同僚は、あなたに感謝することでしょう。
汎用ビューで提供されるもの以上に、追加の情報を提示したいときがあります。例えば、
各出版社の詳細ページに、書籍 (book) の全リストを表示したいときなどです。汎用
ビュー DetailView
はコンテキストに出版社
オブジェクトを提供していますが、テンプレートに追加の情報を与えることはできなさ
そうです。
しかしそうではありません。 DetailView
を
サブクラス化して、 get_context_data
メソッドに独自の実装を提供できます。
DetailView
のデフォルトの実装では、
表示されているオブジェクトをテンプレートに追加します。より多く表示するためには
オーバーライドします:
from django.views.generic import DetailView
from books.models import Publisher, Book
class PublisherDetailView(DetailView):
context_object_name = "publisher"
model = Publisher
def get_context_data(self, **kwargs):
# コンテキストを取得するために、先に基底クラスの機能を呼び出します。
context = super(PublisherDetailView, self).get_context_data(**kwargs)
# 全書籍のクエリセットを追加します。
context['book_list'] = Book.objects.all()
return context
さて、ずっと使ってきた model
引数を詳しく見ていきましょう。 model
引数
は、ビューが操作するデータベースモデルを指定します。単一のオブジェクト、もしくは
複数のオブジェクトを操る、すべての汎用ビューで利用可能です。しかし model
引数以外にも、ビューで操作するオブジェクトを指定する方法があります。
queryset
引数を使って、オブジェクトのリストを指定することができます:
from django.views.generic import DetailView
from books.models import Publisher, Book
class PublisherDetailView(DetailView):
context_object_name = "publisher"
queryset = Publisher.objects.all()
model = Publisher
という書き方は queryset = Publisher.objects.all()
を
単に略記したものです。 queryset
はフィルタされたオブジェクトのリストを定義
するものです。これを使ってビューにあるオブジェクトをより特定することができます。
QuerySet
については クエリを生成する を参照してください。
より詳細には クラスベースビューのリファレンス
を参照してください。
簡単な例として、出版日 (publication date) 順の書籍のリストを最新のものから取得 してみます:
urlpatterns = patterns('',
(r'^publishers/$', ListView.as_view(
queryset=Publisher.objects.all(),
context_object_name="publisher_list",
)),
(r'^books/$', ListView.as_view(
queryset=Book.objects.order_by("-publication_date"),
context_object_name="book_list",
)),
)
かなり簡単な例ですが、うまく考えを表しています。もちろんオブジェクトを再整理する 以上のことをしたいでしょう。特定の出版社についての書籍を渡したい場合も、同じテク ニックが使えます (URLconf で引数を渡すのでなくサブクラス化を使う例はこちら):
from django.views.generic import ListView
from books.models import Book
class AcmeBookListView(ListView):
context_object_name = "book_list"
queryset = Book.objects.filter(publisher__name="Acme Publishing")
template_name = "books/acme_list.html"
フィルタされた queryset
以外に、テンプレート名を指定しています。そうしな
いと、汎用ビューは同じテンプレートを “ありきたりな” オブジェクトリストとして使い
回します。たぶん期待とは違うことでしょう。
これは、ある出版社固有の書籍を扱ううえでエレガントな方法ではありません。他の 出版社のページを追加したいなら、 URLconf に手動で追記する必要があるからです。 追記したうちいくつかは不当 (unreasonable) になるでしょう。詳しくは次のセクション で扱います。
Note
/books/acme/
をリクエストするの際に 404 になったときは、
‘ACME Publishing’ という名の Publisher が実際に存在するか確かめてください。
こういったケースに備えて、汎用ビューは allow_empty
というパラメータを
持っています。詳細は
クラスベースビューのリファレンス を参照して
ください。
他によくあるニーズは、 URL のキーでオブジェクトをフィルタしてから、オブジェクト をリストページに渡すことです。すでに出版社名は URLconf にハードコードしてい ますが、ある任意の出版社について書籍をすべて表示するビューを書きたいなら、どう しますか?
扱いやすいように、 ListView
は
get_queryset()
メソッドを持って
おり、オーバーライドできます。以前は queryset
属性の値を返すだけでしたが、
今ではより多くのロジックを追加できます。
クラスベースビューが呼ばれたとき、有用なオブジェクトは self
に格納されていま
す。これはこの仕事するうえで重要です。リクエスト (self.request
) と同様に、
URLconf でキャプチャされた、位置指定引数 (self.args
) とキーワード引数
(self.kwargs
) を含みます。
URLconf は以下のようになり、キャプチャ用の正規表現グループ (captured group) がつきます:
from books.views import PublisherBookListView
urlpatterns = patterns('',
(r'^books/(\w+)/$', PublisherBookListView.as_view()),
)
次は PublisherBookListView
ビュー自身を書きます:
from django.shortcuts import get_object_or_404
from django.views.generic import ListView
from books.models import Book, Publisher
class PublisherBookListView(ListView):
context_object_name = "book_list"
template_name = "books/books_by_publisher.html"
def get_queryset(self):
publisher = get_object_or_404(Publisher, name__iexact=self.args[0])
return Book.objects.filter(publisher=publisher)
見ての通り、クエリセットの選別 (selection) をするロジックを、とても簡単に追加
できます。 self.request.user
を使ってカレントユーザをフィルタするなど、他の
複雑なロジックも追加できます。
コンテキストにも同時に、出版社オブジェクトを追加できます。なのでテンプレートで 使えます:
class PublisherBookListView(ListView):
context_object_name = "book_list"
template_name = "books/books_by_publisher.html"
def get_queryset(self):
self.publisher = get_object_or_404(Publisher, name__iexact=self.args[0])
return Book.objects.filter(publisher=self.publisher)
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(PublisherBookListView, self).get_context_data(**kwargs)
# Add in the publisher
context['publisher'] = self.publisher
return 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()
DetailView
クラスはもちろん、このフィールドについては知りません。
しかし再び、このフィールドの更新を維持するためのカスタムビューを書けます。
初めに、カスタムビューを指定するために URLconf に著者の詳細に関する 1 行を追加 します:
from books.views import AuthorDetailView urlpatterns = patterns('', #... (r'^authors/(?P<pk>\d+)/$', AuthorDetailView.as_view()), )
ここで新しいビューを書きます。 get_object
はオブジェクトを取得するメソッド
なので、これをオーバーライドして呼び出しをラップします:
import datetime
from books.models import Author
from django.views.generic import DetailView
from django.shortcuts import get_object_or_404
class AuthorDetailView(DetailView):
queryset = Author.objects.all()
def get_object(self):
# 基底クラスを呼び出す
object = super(AuthorDetailView, self).get_object()
# 最終アクセス日時を記録する
object.last_accessed = datetime.datetime.now()
object.save()
# オブジェクトを返す
return object
Note
実際には、このコードには books/author_detail.html
テンプレートが必要
です。
Note
ここで URLconf は pk
という正規表現グループを使います。この名前は
DetailView
がクエリセットをフィルタするときに使う、プライマリキーの値に
なるデフォルト名です。
これを変更したいなら self.kwargs
に新しい名前のパラメーターを追加して、
self.queryset
の get()
でそれを使って呼び出す必要があります。
今のところ、レスポンスを生成するためのテンプレートレンダリングに焦点をあててい ました。しかし、これが汎用ビューにできることのすべてではありません。
どの汎用ビューも一連の Mixin から構成されています。それぞれの Mixin はビュー全体
の一部を提供します。そのうち例えば
TemplateResponseMixin
はテンプレートを使って
HTML レスポンスをレンダリングするよう設計されています。様々な動作をする独自の
Mixin を書くこともできます。
例えば、単純な JSON Mixin は以下のようになるでしょう:
from django import http
from django.utils import simplejson as json
class JSONResponseMixin(object):
def render_to_response(self, context):
"Returns a JSON response containing 'context' as payload"
return self.get_json_response(self.convert_context_to_json(context))
def get_json_response(self, content, **httpresponse_kwargs):
"`HttpResponse` オブジェクトのコンストラクタ"
return http.HttpResponse(content,
content_type='application/json',
**httpresponse_kwargs)
def convert_context_to_json(self, context):
"コンテクスト辞書を JSON オブジェクトに変換"
# Note: これは *非常に (EXTREMELY)* 単純です。実際には、任意のオブジ
# ェクト (Django のモデルインスタンスやクエリセットなど) を JSON に
# シリアライズするには、より複雑な処理をする必要があります。
return json.dumps(context)
これで、 JSON を返す DetailView
を、
JSONResponseMixin
と
BaseDetailView
を Mixin して構築できます。
(テンプレートレンダリングの前に DetailView
が Mixin されています):
class JSONDetailView(JSONResponseMixin, BaseDetailView):
pass
その後このビューは、他の DetailView
と
全く同じようにデプロイできます。 (レスポンスのフォーマットを除いて)
さらに大胆なことをするなら、 HTML 、 JSON コンテンツの 両方を 返せる
DetailView
サブクラスを作ることもでき
ます。これはクエリ引数や HTTP ヘッダなど、いくつかの HTTP リクエストのプロパティ
に依存します。 JSONResponseMixin
と
SingleObjectTemplateResponseMixin
を
合わせて、ユーザがリクエストしたレスポンスタイプに応じて、該当するサブクラスに
任せるために render_to_response()
をオーバーライドします:
class HybridDetailView(JSONResponseMixin, SingleObjectTemplateResponseMixin, BaseDetailView):
def render_to_response(self, context):
# Look for a 'format=json' GET argument
if self.request.GET.get('format','html') == 'json':
return JSONResponseMixin.render_to_response(self, context)
else:
return SingleObjectTemplateResponseMixin.render_to_response(self, context)
JSONResponseMixin
と
SingleObjectTemplateResponseMixin
の
render_to_response
を、ローカルのそれでオーバーライドすることで、Python は
メソッドのオーバーライドを解決します。
クラスベースビューの拡張には、 Mixin 以外を使う方法があります。デコレータを 使う方法です。
クラスベースビューをデコレートする 1 番単純な方法は、
as_view()
メソッドの結果をデコレートする
ことです。ビューをデプロイした URLconf に置くのが 1 番楽でしょう:
from django.contrib.auth.decorators import login_required, permission_required
from django.views.generic import TemplateView
from .views import VoteView
urlpatterns = patterns('',
(r'^about/', login_required(TemplateView.as_view(template_name="secret.html"))),
(r'^vote/', permission_required('polls.can_vote')(VoteView.as_view())),
)
このアプローチでは、インスタンスごとにデコレータを適用します。ビューのインス タンス全てをデコレートしたいなら、他のアプローチが必要です。
クラスベースビューの全インスタンスをデコレートするには、クラスの定義そのものを
デコレートします。クラスの dispatch()
メソッドにデコレータを適用することでできます。
クラスメソッドは単なる関数とは違います。メソッドに関数デコレータを適用することは
できません。関数デコレータをメソッドデコレータに変換する必要があります。
method_decorator
デコレータは関数デコレータをメソッドデコレータに変換する
ので、これをインスタンスメソッドで使えます。例を以下に示します:
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView
class ProtectedView(TemplateView):
template_name = 'secret.html'
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(ProtectedView, self).dispatch(*args, **kwargs)
この例で、 ProtectedView
の全インスタンスはログインが必要になります。
Note
method_decorator
は *args
と **kwargs
をパラメータとして、デコレ
ートされたクラスメソッドに渡します。メソッドが互換性を持つパラメータのセット
を許可していない場合は、 TypeError
例外が送出されます。
Oct 26, 2017