0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Django Generic Base お前まじ誰? (没)

Last updated at Posted at 2021-06-16

何のための記事?

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よろしくお願いします。
先に、記事書きながら勉強を進めていましたが 勉強を先にして理解してから書きます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?