Djangoのクラスベースビューのas_viewて何なの？

ハッピーメリークリスマス。 この記事はDjangoアドベントカレンダーの18日目の代打です。

## 概要

- Djangoには関数ベースのビューと、クラスベースのビューの2種類がある
- クラスベースの方は利用する時に `as_view` というクラスメソッドからviewオブジェクトを生成している
- これは一体何してるんですか？ というメモを雑に残す。

## 前提

- Djangoのビューはドキュメントにも書いてある通り以下の条件を満たす必要がある

 - requestオブジェクトを(第一引数として)受け取る
 - callableである
 - responseオブジェクトを返す

```
A view is a callable which takes a request and returns a response.

refs https://docs.djangoproject.com/en/2.0/topics/class-based-views/
```

- Django 1.3 以前は関数ベースで書くのが普通だったが、なるべくコードを書かずに再利用可能なビューを作るためにクラスベースのビューが導入された。

## as_view クラスメソッド

例えば以下のような関数ベースビューがあるとする

```python
from django.http import HttpResponse, HttpResponseNotAllowed

def myview(request):
    if request.method == 'GET':
      return HttpResponse('Hello, World!')
    return HttpResponseNotAllowed('GET only accept')
```

これと等価なクラスベースビューを作ると下記のようになる

```python
from django.http import HttpResponse
from django.views import View

class MyView(View):

    def get(self, request, *args, **kwargs):
        return HttpResponse('Hello, World!')

myview = MyView.as_view()
```

- この `myview = MyView.as_view()`  の `myview` は実態は関数である。
- `as_view` は **Djangoのビューの条件を満たす関数** を作り出しているだけである

## as_view を使わずにクラスベースビューを使う

たとえば `as_view` を使わずにクラスベースビューを使おうとすると下記のようになる。(この例自体に意味はない。)


```python
from django.http import HttpResponse
from django.views import View

class MyView(View):

    def get(self, request, *args, **kwargs):
        return HttpResponse('Hello, World!')

def myview(request, *args, **kwargs):
    view_obj = MyView()
    view_obj.request = request
    view_obj.args = args
    view_obj.kwargs = kwargs
    return view_obj.dispatch(request, *args, **kwargs)
```

- クラスベースビューは最初の起点が `dispatch` メソッドとなる
- `dispatch` メソッド は **リクエストメソッド** を判定して、その **リクエストメソッド** と同じ名前の**メソッド**が実装されていたらそれを実行する。
- つまり `request.method == 'GET'` だったら `Myview.get` を実行するということ
- `dispatch` が実装されてるメソッドに応じて処理を自動的に分岐してくれるので、関数ベースでやっていた時のように自分で分岐を書かなくても良い。

```python
# こんな感じで分岐を書かなくても良いという話

def myview(request):
    if request.method == 'GET':
      # code for GET

    if request.method == 'POST':
      # code for POST

    if request.method in ('PATCH', 'PUT'):
      # code for PATCH or PUT

    if request.method == 'DELETE':
      # code for DELETE

    return HttpResponseNotAllowed('Not accept method {}'.format(request.method))
```

## Djangoの該当コード

実際にDjangoがview関数を生成している部分をgithubのコードから抜粋して確認する

``` python
# refs https://github.com/django/django/blob/master/django/views/generic/base.py#L49

@classonlymethod
def as_view(cls, **initkwargs):

   # 〜 省略 〜

   # 自分自身をインスタンス化するビュー関数を生成してreturn してる
   def view(request, *args, **kwargs):
       self = cls(**initkwargs)
       if hasattr(self, 'get') and not hasattr(self, 'head'):
           self.head = self.get
       self.request = request
       self.args = args
       self.kwargs = kwargs
       return self.dispatch(request, *args, **kwargs)

   # 〜 省略 〜

   return view
```

- `as_view` は 自ら(=`cls`) をインスタンス化して `dispatch`メソッドを実行するような関数(=`view`)を生成して `return` しているのがわかる。

## まとめ

- クラスベースビューの`as_view`は Djangoのビューの条件を満たす関数を生成しているだけ。極論を言えば条件満たせばなんでもいい。
- クラスベースビューの基本的な機能は、リクエストメソッドに応じた処理の自動分岐を提供することである
- それをベースにテンプレートをレンダリングする機能を加えたり、CRUDの機能が追加されたのが他の`views.genrice`群

 - 例) TemplateView, RedirectView, CreateView, etc...


以上おわり