LoginSignup
1
3

More than 1 year has passed since last update.

Djangoのas_viewについて

Last updated at Posted at 2021-05-08

今回やること

前回TemplateViewの流れをまとめた中でas_viewあたりが分かりにくかったのでまとめてみた。

実装を見てみる

as_viewのところのおおまかな実装
django.views.generic.base.py
    ...
    @classonlymethod
    def as_view(cls, **initkwargs):
        ... 
        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            self.setup(request, *arg, **kwargs)
            return self.dispatch(request, *args, **kwargs)
        return view

classonlymethodというのが意味不明のため、
まずこのclassonlymethodというデコレータから追ってみる。

classonlymethodとは?

名前からするとクラスのみのメソッド?のようだが実装から見てみる。

django.utils.decoraters.py
class classonlymethod(classmethod):
    def __get__(self, instance, cls=None):
        if instance is not None:
            raise AttributeError('...')
        return super().__get__(instance, cls)

実装はこのようになっており、getメソッドが呼ばれた時にinstanceが空じゃなかったらエラーを投げるようになっているだけ。
じゃあこのgetメソッドについて公式ドキュメントから引用してみる。

object.get(self, instance, owner=None)

Called to get the attribute of the owner class (class attribute access) or of an instance of that class (instance attribute access). The optional owner argument is the owner class, while instance is the instance that the attribute was accessed through, or None when the attribute is accessed through the owner.

所有者クラスの属性にアクセスされた時か、そのクラスのインスタンスを取得する時に呼ばれる。引数のownerは所有者クラス。
もう1つの引数のinstanceは、インスタンスからアクセスされた時は所有者クラスが入り、所有者クラスからアクセスされた時はNoneが入る。

インスタンス束縛

オブジェクトインスタンスへ束縛すると、a.xは呼び出し、type(a).dict['x'].get(a, type(a))に変換されます。

クラス束縛

クラスへ束縛すると、A.xは呼び出し、A.dict['x'].get(None, A)に変換されます。

つまり、インスタンスからアクセスした時には、第2引数に所有者クラスが入ってしまうため、上記のclassonlymethodではエラーを投げ、所有者クラスからのアクセスのみ通すような実装になっている。

つまり、なんちゃらView.as_view()といった呼び出し方だけを通すようにするデコレータだと思われる。一応挙動を確認してみた。

view.py
class classonlymethod(classmethod):
    def __get__(self, instance, cls=None):
        print('self', self)
        print('instance', instance)
        print('cls', cls)
        if instance is not None:
            raise AttributeError('Error')
        return super().__get__(instance, cls)

class View:
    @classonlymethod
    def as_view(cls, **initkwargs):
        pass

class TestView(View):
    pass

print('クラスからアクセス')
TestView.as_view()

print('インスタンスからアクセス')
t = TestView()
t.as_view()

# クラスからアクセス
# self <__main__.classonlymethod object at ...>
# instance None
# cls <class '__main__.TestView'>

# インスタンスからアクセス 
# self <__main__.classonlymethod object at ...>
# instance <__main__.TestView object at ...>
# cls <class '__main__.TestView'>
# AttributeError: Error

といった感じの動作でメソッド名の通り、クラスからアクセスした時のみ通るようになっていた。

ではas_viewの中身を見ていく。

django.views.generic.base.py
    ...
    @classonlymethod
    def as_view(cls, **initkwargs):
        # バリデーションとか
        ... 
        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            self.setup(request, *arg, **kwargs)
            if not hasattr(self, 'request'):
               raise AttributeError('...')
            return self.dispatch(request, *args, **kwargs)
        # 属性らセット
        ...
        return view

といったようにas_viewは、viewメソッドを返すエンクロージャーになっているみたい。
viewメソッドでは以下の処理が実装されている。
1. インスタンス化
2. setup
3. 検証
4. dispatchメソッドを返す

1. インスタンス化

classonlymethodによって、クラスからしかアクセスさせないため、ここには呼び出したクラスがそのまま入り、そのクラスをインスタンス化している。
TemplateView.as_view()だったら、TemplateViewがそのまま入ってきてself = TemplateView(**initkwargs)というようにインスタンス化する。

2. setup

リクエストと引数をインスタンスにセットしている。

django.views.generic.base.py
    def setup(self, request, *args, **kwargs):
        is hasattr(self, 'get') and not hasattr(self, 'head'):
            self.head = self.get
        self.request = request
        self.args = args
        self.kwargs = kwargs

3. 検証

setupメソッドによってインスタンスにrequestがセットされなかった場合、AttributeErrorが投げられる。

4. dispatchメソッドを返す

django.views.generic.base.py
    http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']

    ...

    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):
        ...
        return HttpResponseNotAllowed(self._allowed_methods())

    def _allowed_methods(self):
        return [m.upper() for m in self.http_method_names if hasattr(self, m)]

許容しているHTTPメソッドリストに存在するか、そのメソッドをViewが持っているか確認し、OKの場合はそのメソッドを返す。NGの場合はエラーを投げる。
TemplateViewだったらgetだったら許容リストに存在し、実装もされているためhandlerはgetメソッドになり、getメソッドが返る。

urls.pyでas_viewを書いてからの流れ

① urls.pyでas_viewで宣言

urls.py
    ...
    path('', views.TemplateView.as_view(), name='index'),
    ...

と宣言する事で、TemplateViewを元にas_viewが動き検証と属性類をセットし、view関数により、渡ってきたリクエストのメソッド名と同じメソッドが呼ばれるhandlerが指定される。

② リクエストが来てから
そして、いざgetメソッドでリクエストが渡ってきたら、許容されているメソッドか、TemplateViewに実装されているかなどを確認した後、OKであればgetメソッドが動き、レスポンスが返る。

まとめ

まとめてみようとする事で、改めて理解が深まった気がする?・・・
作った人尊敬します。

1
3
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
1
3