DjangoのClass Based Veiw(クラスベースビュー)についてちょっと軽くコードリーディングした
これからは関数ベースじゃなくてクラスベースになるらしい
Note
Prior to Django 1.3, generic views were implemented as functions. The function-based implementation has been deprecated in favor of the class-based approach described here.
というわけでDjango1.3から関数ベースはオワコンにしてクラスベースビューにしていこうということになってたらしい。知らなかった。情弱乙。実際1.5のdevでは常用していた便利メソッド direct_to_template(reqest, "tempalte.html", extra_content={'items', items})みたいなことができなくなってる。では、どうするか。クラスベースビューにこれからは移行するなら、知らなければならない。この件については PyCon2012 で @hirokiky さんが発表してくれるらしいけど、ちょっと自分でも覗いた。
嘘を書くつもりはないが、以下自己責任で。
公式参考::Class-based generic views | Django documentation | Django
参考::Django1.3のClass-based generic viewsを使う - 偏った言語信者の垂れ流し
参考::Django 1.4 で Class-based views 使ってみた - 断章10100
そもそも関数ベースでの指定ってどうしてたっけ?
urls.py にこんなのかいてた
from django.conf.urls.defaults import url, patterns urlpatterns = patterns('fav.views', url(r'^$', 'index', name='index'), # こっちでもいい感じだったけど間違ってるかも url(r'^$', 'fav.views_index', name='index'), url(r'^login$', 'login', name='login'), url(r'^logout$', 'logout', name='logout'),
この場合はよくわからないが patterns のかきかたとして from fav.views import index, login, logout と同等の書き方をしていたことになる。で、この URL ルーティングを元に views.py の index 関数なりが発火するという流れになっている。このとき関数を指定しているというのが「関数ベース」ということなんだろう。
クラスベースビューでの指定をする
urls.py にこんな感じで書く
from django.conf.urls.defaults import patterns, url urlpatterns = patterns('', # これも微妙にかきかた間違ってるかも url(r'^$', hoge.views.MyView.as_view()),
hoge.views に MyView があるという想定。結果的に as_view()で呼び出しているからクラスメソッドであるけど、そのときクラスであるとなにかとフックしたりなどが都合がいいらしい。
ではクラスベースビューの views.py はどうなる
サンプル
class MyView(View): def get(self, request, *args, **kwargs): ... def post(self, reqest, *args, **kwargs) ...
なるほど、get, post などをこうして指定して読んでやると。実際にはそこ前にいろいろパラメータを渡したりフックしたりするんだろうなぁ。
View を読む(django.views.generic.base.py)
ではクラスベースビューの基底となっている View とはなにか、ってのを軽く読む。ちゃんと読めるほどのスキルはないけど、エスパーする。ちょっと長いけど貼るよ。
from functools import update_wrapper from django import http from django.utils.decorators import classonlymethod class View(object): """ Intentionally simple parent class for all views. Only implements dispatch-by-method and simple sanity checking. """ http_method_names = ['get', 'post', 'put', 'delete', 'head', 'options', 'trace'] def __init__(self, **kwargs): """ Constructor. Called in the URLconf; can contain helpful extra keyword arguments, and other things. """ # Go through keyword arguments, and either save their values to our # instance, or raise an error. for key, value in kwargs.iteritems(): setattr(self, key, value) @classonlymethod def as_view(cls, **initkwargs): """ Main entry point for a request-response process. """ # sanitize keyword arguments for key in initkwargs: if key in cls.http_method_names: raise TypeError(u"You tried to pass in the %s method name as a " u"keyword argument to %s(). Don't do that." % (key, cls.__name__)) if not hasattr(cls, key): raise TypeError(u"%s() received an invalid keyword %r" % ( cls.__name__, key)) def view(request, *args, **kwargs): self = cls(**initkwargs) if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get return self.dispatch(request, *args, **kwargs) # take name and docstring from class update_wrapper(view, cls, updated=()) # and possible attributes set by decorators # like csrf_exempt from dispatch update_wrapper(view, cls.dispatch, assigned=()) return view def dispatch(self, request, *args, **kwargs): # Try to dispatch to the right method; if a method doesn't exist, # defer to the error handler. Also defer to the error handler if the # request method isn't on the approved list. if request.method.lower() in self.http_method_names: handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed self.request = request self.args = args self.kwargs = kwargs return handler(request, *args, **kwargs) def http_method_not_allowed(self, request, *args, **kwargs): allowed_methods = [m for m in self.http_method_names if hasattr(self, m)] logger.warning('Method Not Allowed (%s): %s', request.method, request.path, extra={ 'status_code': 405, 'request': self.request } ) return http.HttpResponseNotAllowed(allowed_methods)
as_view のところが urls.py で呼び出していたところですね。 @classonlymethod は実装みればわかるけど @classmethod をちょっとなんか拡張した感じ。基本的には同じ。で、さっき MyView で get post とか指定して渡せるようになっているのはおそらく http_method_names にはいっていて、それが dispatch 関数の if request.method.lower() in self.http_method_names の部分らへんで判定しているとエスパーできる。
で、Viewオブジェクト自体は基底のもので、基本的には Mixin して使う。詳しくは Class-based generic views | Django documentation | Django をみればわかる……けど英語だしなんか面倒だ。なんとなくわかるのは(あと実装をみていてわかるのは)、いろんなカスタマイズされた CustomView を定義するために HogeMixin とか MogeMixin とかをたくさん定義してあげて、それをまぜあわせて CustomView みたいに便利にしてあげているということだ。なんかこう FormMixin とか DayMixin とか TemplateResponseMixin とかいろいろあるし。実際どう使えばいいかというと……ちょっとよくわからないけど*1。
まあそんな
クラスベースビューあんまりよくわかってないのでこれから使うときはどううれしいかが具体的にわかるとイイナァ。
*1:いちおう direct_to_template みたいな render 関数などは残っているのでいちおうそちらも使えるし……という逃げ道