何のための記事?
generic baseに含まれるクラス関数について、ソースコード読んで勉強していくよ!
Djangoのクラスベースビューが色んな記事読んでも理解できなくて、分からないが分からないなら!
ソースコード読んで分からないをしろう?ゴリゴリ脳筋プレーでソースコード読みながら、
参考になりそうなサイトをピックアップしていきます。アホほど参考サイトが出てきます。全部読めば完璧
この記事が役に立つ方
この記事はソースコードを丸々読んでるだけあって、大分雑です!。
丁寧に書きたいのは山々ですが、丁寧に書くほど複雑で読みにくいものになると認識しています。
なので、ソースコード読んででも根本を知りたい方にはおすすめです。
最低限、TemplateViewやCreateView触ったことがないと分かりづらいかもです。
今回しようするコードはソースコードを参照しています。
Mixin class
Mixin class とは?
wikiから引用したものですが、以下に書いてあるとおり単体で動作することを考慮していないクラスです。
詳しく知りたい方は、こちらのサイトを参照(ミックスインってなに?)
mixin とはオブジェクト指向プログラミング言語において、サブクラスによって継承されることにより機能を提供し、単体で動作することを意図しないクラスである 参照元:wikipedia
Generic Baseに含まれるMixin classは以下の2個です。
- ContextMixin
- TemplateMixin
一つずつ解説していきます。
ContextMixin
当たり前ながら、これ単体をみても何やってるのかさっぱりですね。知識が足りない。。。
contextとは誰?ってところから 参考サイト:contextの正体をわかりやすく解説
contextの簡易的な見解
- 辞書型
- templateにレンダリングするデータを保管可能(templateに表示したい変数)
get_context_data(self, **kwargs)
ContextMixin では、get_context_data(**kwargs)で受け取ったキーワード引数をコンテキストとして保存する。
再記するが、コンテキスト(context)はテンプレートに渡される、変数名が含まれた辞書である。
これを使うことで、templateに表示したい値を追加できる。
class ContextMixin:
"""
get_context_data()で受け取ったキーワード引数をテンプレート・コンテキストとして渡す
"""
extra_context = None
def get_context_data(self, **kwargs):
kwargs.setdefault('view', self) # key:'view'が無い時、value:selfを追加
if self.extra_context is not None:
kwargs.update(self.extra_context) # extra_contextがある時、kwargsに上書き
return kwargs
# get_context_dataをオーバーレイ参考コード
class IndexView(TemplateView):
template_name = 'index.html'
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['items'] = ...
ctx[xxx] = ...
return ctx
例として、TemplateViewを使った時のget_context_data()オーバライドしたものである。
template側で{{ items }} をすることで、値をレンダリングできる。
get_context_dataをオーバーライドの参考コード元
オーバライドについて わかりやすい記事が見当たらなかったので検索で一番上のものです。
ContextMixin で注目しておくポイント
get_context_data関数にオーバーライドすることで、テンプレートコンテキストに直接書き込めるため
他の関数等を使わずともテンプレートにレンダリングする値を追加できる。
TemplateMixin
今回は先の内容も踏まえると簡単である。
先に見たいのは、get_template_names関数である。
get_template_names(self)
クラスベースビューを使ってれば一度は見たことあるであろう。template_name変数がある。
ここでは、render_to_response関数が呼び出された時に、template_nameが空(None)だとエラーを返す処理である。
render_to_response(self, context, **response_kwargs)
'response_class'を使用して、与えられたコンテキストでレンダリングされた
テンプレートを持つレスポンスを返してます。
簡単に言うと、テンプレートとそれをレンダリングするのに必要な値をセットにして返す処理です。
class TemplateResponseMixin:
"""テンプレートをレンダリングするために使用"""
template_name = None
template_engine = None
response_class = TemplateResponse
content_type = None
def render_to_response(self, context, **response_kwargs):
"""
'response_class'を使用して、与えられたコンテキストでレンダリングされた
テンプレートを持つレスポンスを返します。
レスポンスクラスのコンストラクタには、response_kwargs を渡します。
"""
response_kwargs.setdefault('content_type', self.content_type)
return self.response_class(
request=self.request,
template=self.get_template_names(), #get_template_names()呼び出してる。
context=context,
using=self.template_engine,
**response_kwargs
)
def get_template_names(self):
"""
リクエストに使用されるテンプレート名のリストを返します。
render_to_response()がオーバーライドされている場合は呼び出されません。
"""
if self.template_name is None:
raise ImproperlyConfigured( #errorコード
"TemplateResponseMixin requires either a definition of "
"'template_name' or an implementation of 'get_template_names()'"
)
else:
return [self.template_name]
ContextMixin で注目しておくポイント
template_nameだけ知ってれば良い!もう、みんな友達だと思うけど
この子を使って、表示したいtemplateの名前を与える!そこに限る。
template_name = "index.html" こんな感じ☺️
Main class
Mixinクラスを説明し終わったので、Generic Baseで主なクラスの説明をしていこうと思います。
Generic Baseで定義されているのは以下の3クラスです。
- View
- TemplateView
- RedirectView
こっからDjangoのクラスベースビューで大事になってくる要素がいくつか含まれてくるので、
そこは理解できるまで、いろんな記事を読み漁ったり実際にソースコードを見てみるなりした方が良いです。
それでは、一つずつ解説していきます。
View class
こいつ抜きにして、Djangoのクラスベースビューは機能しない!ってくらい大事です。
簡単に大切さを説明すると、どのDjangoのクラスベースビューのクラスを辿ってもViewくんが継承されています!
※ 今説明した通り、jangoのクラスベースビューのクラスです。MixinクラスはViewくんを継承していません。
ここでViewくんやクラスベースビューの構造がよく分からない方はこの記事がおすすめです。
意図的にシンプルにした、すべてのビューの親クラスです。
Dispatch-by-methodと簡単なサニティチェックのみを実装しています。(参照元:ソースコード)
サニティチェックとは、計算結果が正しいかどうかを素早く評価するための基本的なテスト(参照元:wikipedia)
それでは中身を見ていきます。元のソースコード
まず、View class がもつメソッドは以下の7個です。
- __init__
- as_view
- setup
- dispatch
- http_method_not_allowed
- options
- _allowed_methods
備考: http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
それじゃ、一つずつ見ていきます。
def __init__
有名なコンストラクタ関数です。知らない方はこちらのサイトを参照
中身では、インスタンスの際にキーワード因数を与えられた場合
そのデータを検証してインスタンスに保存するだけです。
組み込み関数 - setattr関数についての参考資料
def __init__(self, **kwargs):
"""
コンストラクタ。
役に立つ追加のキーワード引数やその他のものを含むことができます。
"""
#キーワード引数を調べて, その値をインスタンスに保存するか, エラーを発生させます.
for key, value in kwargs.items():
setattr(self, key, value)
as_view(cls, **initkwargs)
as_viewの特徴はクラスメソッドであるところですね。
中身のコードを一つずつ見ていきます。
最初のところでは、二つのエラーコードを設定してあります。
一つは、http_method_namesで定義された値をkeyで受け取った時と、classのインスタンス変数以外を受け取った時
詳しくは話さないが、と言うか分からないがわかる人いたら教えて欲しい
次のところでview関数を定義されている。
この中では、先ほどあげた関数のsetup関数とdispatch関数が呼び出されている。
最後にデコレータについて書かれています。
@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
# クラスから、Docstringとクラス名を呼び出す
update_wrapper(view, cls, updated=())
# デコレーターで設定可能な属性を呼び出す。
update_wrapper(view, cls.dispatch, assigned=())
return view
setup(self, request, *args, **kwargs)
ここは至って簡単です。
request, args, kwargs をインスタンス変数として登録しています。
def setup(self, request, *args, **kwargs):
"""すべてのビューメソッドで共有される属性を初期化します。"""
self.request = request
self.args = args
self.kwargs = kwargs
dispatch(self, request, *args, **kwargs)
ここもそれほど難しい事は行っていません。
httpメソッドに対応するメソッドを探して呼び出しています。(GET,POST)
もし、http_method_namesに未定義なものかhttpメソッドに対応するメソッドが見つからない場合
http_method_not_allowed()を呼び出します。
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)
http_method_not_allowed(self, request, *args, **kwargs) _allowed_methods(self)
loggerを使ってwarning文の追加と _allowed_methods(self)を呼び出してますね
ここら辺は正直、dispatchでhttp_method_namesに未定義なものか
httpメソッドに対応するメソッドが見つからない場合のエラーコードを定義しているところと捉えています。
なので、ここで終わりです。
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 _allowed_methods(self):
return [m.upper() for m in self.http_method_names if hasattr(self, m)]
options(self, request, *args, **kwargs)
HTTPの要求への応答を処理します。Allowビューで許可されているHTTPメソッド名のリストを含むヘッダーを含む応答を返します。
def options(self, request, *args, **kwargs):
"""Handle responding to requests for the OPTIONS HTTP verb."""
response = HttpResponse()
response['Allow'] = ', '.join(self._allowed_methods())
response['Content-Length'] = '0'
return response
没 編集予定未定
ここまで記事書いてなんなんですが、理解できて無いところが複数出てきて
役に立たない情報をまとめても仕方ない、、、となってしまっています。
ここまで読んで続きが読みたいとかあれば、LGTMよろしくお願いします。
先に、記事書きながら勉強を進めていましたが 勉強を先にして理解してから書きます。