[Django] TemplateViewについて詳しく見てみる
Djangoでクラスベースビューを利用するときにまず、最初に使うことになるのがTemplateView
だと思うのですが、今回はこのTemplateView
がどんな実装になっているのか見てみたいと思います。
Django(GitHub)
https://github.com/django/django
TemplateView
TemplateView
の実装はdjango/django/views/generic/base.pyに書いてあります。
その中から重要そうなところを一部抜粋
class TemplateView(TemplateResponseMixin, ContextMixin, View):
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
何やら、三つほどクラスを継承しています。
それぞれ見ていきましょう。
まずは、View
から
View
class View:
"""
Intentionally simple parent class for all views. Only implements
dispatch-by-method and simple sanity checking.
"""
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
@classonlymethod
def as_view(cls, **initkwargs):
for key in initkwargs:
if key in cls.http_method_names:
raise TypeError("You tried to pass in the %s method name as a "
"keyword argument to %s(). Don't do that."
% (key, cls.__name__))
if not hasattr(cls, key):
raise TypeError("%s() received an invalid keyword %r. as_view "
"only accepts arguments that are already "
"attributes of the class." % (cls.__name__, key))
def view(request, *args, **kwargs):
self = cls(**initkwargs)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.setup(request, *args, **kwargs)
if not hasattr(self, 'request'):
raise AttributeError(
"%s instance has no 'request' attribute. Did you override "
"setup() and forget to call super()?" % cls.__name__
)
return self.dispatch(request, *args, **kwargs)
view.view_class = cls
view.view_initkwargs = initkwargs
update_wrapper(view, cls, updated=())
update_wrapper(view, cls.dispatch, assigned=())
return view
def setup(self, request, *args, **kwargs):
self.request = request
self.args = args
self.kwargs = kwargs
def dispatch(self, request, *args, **kwargs):
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
return handler(request, *args, **kwargs)
def http_method_not_allowed(self, request, *args, **kwargs):
logger.warning(
'Method Not Allowed (%s): %s', request.method, request.path,
extra={'status_code': 405, 'request': request}
)
return HttpResponseNotAllowed(self._allowed_methods())
def options(self, request, *args, **kwargs):
response = HttpResponse()
response['Allow'] = ', '.join(self._allowed_methods())
response['Content-Length'] = '0'
return response
def _allowed_methods(self):
return [m.upper() for m in self.http_method_names if hasattr(self, m)]
何やら、いっぱい書いてありますが、要はas_view
メソッドで、クラスビューを関数ビューへ変換するようなことをやっています。
もう少し詳しい説明を付け加えると、dispatch
メソッドで、httpリクエストのメソッドの種類が正常なものかをチェックし、対応するメソッドを返す処理を行っています。
例えば、GET
メソッドでリクエストが来た場合、自身のget
メソッドを返す。
その時、自身にget
メソッドが定義されていなければ、ステータスコード405を返す。
同様に、POST
メソッドでリクエストが来た場合、自身のpost
メソッドを返す。
その時、自身にpost
メソッドが定義されていなければ、ステータスコード405を返す。
というような処理を行っています。
また、http_method_names
で利用できるメソッドをコントロールしているため、特定のメソッドのみを許可したい場合には継承先のクラスでhttp_method_names
の値を上書きしてあげればよいです。
例えば、
class OnlyGetView(View):
http_method_names = ["get"]
とすることで実現できます。
※Django2.2よりsetup
メソッドが追加されました。
このメソッドはdispatch
メソッドより前に呼び出されて、request
やkwargs
,args
などの属性をView
クラスに付加しています。
View
クラスに属性を付加したい場合、2.2系以前ではdispatch
メソッドをオーバーライドして実装することが多かったですが、2.2系以降ではsetup
をオーバーライドして属性の追加を行った方が良さそうです。
続いて、ContextMixin
を見てみます。
ContextMixin
class ContextMixin:
extra_context = None
def get_context_data(self, **kwargs):
kwargs.setdefault('view', self)
if self.extra_context is not None:
kwargs.update(self.extra_context)
return kwargs
ContextMixin
では、テンプレートファイルに渡す、変数を追加しています。
継承先のクラスで、get_context_data
メソッドをオーバーライド又は、クラス変数extra_context
を上書きしてあげれば、テンプレートに渡す変数を追加できます。
オーバーライドする場合は、親クラスのget_context_data
を呼んであげないとview
という変数がテンプレート内で使えなくなってしまうので注意。
最後に、TemplateResponseMixin
です。
TemplateResponseMixin
class TemplateResponseMixin:
template_name = None
template_engine = None
response_class = TemplateResponse
content_type = None
def render_to_response(self, context, **response_kwargs):
response_kwargs.setdefault('content_type', self.content_type)
return self.response_class(
request=self.request,
template=self.get_template_names(),
context=context,
using=self.template_engine,
**response_kwargs
)
def get_template_names(self):
if self.template_name is None:
raise ImproperlyConfigured(
"TemplateResponseMixin requires either a definition of "
"'template_name' or an implementation of 'get_template_names()'")
else:
return [self.template_name]
継承先のクラスでtemplate_name
に利用するテンプレートファイルへのパスを記述します。
template_engine
は特に指定しなければ、settings.py
で指定されているテンプレートエンジンが利用されます。デフォルトでは、Djangoテンプレートエンジンが利用されます。
response_class
は基本的に触ることはないと思いますが、httpレスポンスを返すようなクラスを指定します。render
関数と同じようなものと思っていただければいいです。
content_type
には、MIME TYPEを入れておきます。
基本的には何も指定しなければ、text/html
になるはずです。
まとめ
最後に、TemplateView
の実装を見てわかったことを簡単にまとめてみましょう。
利用するテンプレートファイルを指定するには
class CustomView(TemplateView):
template_name = "app/template.html"
template_name
を書き換える
テンプレートで使える変数を増やすには
class CustomView(TemplateView):
extra_context = {"model_data":Model.objects.all()}
extra_context
を書き換える
又は、
class CustomView(TemplateView):
def get_context_data(self,**kw):
conetext = super().get_context_data(**kw)
context.update({"model_data":Model.objects.all()})
return context
get_context_data
をオーバーライド
この時に、親クラスのget_context_data
を呼び出しておかないとデフォルトで使える変数が使えなくなるので注意!
利用できるメソッドを制限するには
class CustomView(TemplateView):
http_method_names = ["get"]
http_method_names
に許可するメソッドを指定
上記だと、GET
メソッドだけを利用可能に
利用するテンプレートエンジンを変更するには
class CustomView(TemplateView):
template_engine = CustomTemplateEngine
以上、非常に簡単にでしたがDjangoTemplateView
の実装をのぞいてみました。