クラスベース な View の作成

Last Change:27-Apr-2016.
Author:qh73xe
Reference:https://docs.djangoproject.com/ja/1.9/topics/class-based-views/intro/

このページでは Django で views.py を作成する際に、 クラスを基本にしながら記述する方法に関してまとめて行きます。

class-based views

まず大前提として、 Django ではあるモデルと、それを表示するテンプレートとの橋渡しをする関数群として views.py を作成していきます。 ここで公式のチュートリアルを確認していくと、このファイルは基本的に関数群を記述していくように見えます。

しかし、ある関数として view を記述すると、 request が get か post か によって if 文分岐がおり、やや面倒くさい感じになります。 例えば以下のような感じですね。

views.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def contact(request):
    if request.method == 'POST': # フォームが提出された
        form = ContactForm(request.POST) # POST データの束縛フォーム
        if form.is_valid(): # バリデーションを通った
            # form.cleaned_data を処理
            # ...
            return HttpResponseRedirect('/thanks/') # POST 後のリダイレクト
    else:
        form = ContactForm() # 非束縛フォーム
    return render_to_response('contact.html', {
        'form': form,
    })

基本的に Django で作成する web app は良くも悪くもモデルベースでものを考えるので、 こういう関数を沢山作成するよりは、クラスベースで view も作成したい場合が多々あります。

Django ではそのための抽象クラスも用意してあるので、これを利用するとよいかと思います。 例えば以下のように記述します。

views.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
from django.shortcuts import render, redirect
from django.views.generic import View
from .forms import MyForm

class MyFormView(View):
    form_class = MyForm
    initial = {'key': 'value'}
    template_name = 'form_template.html'

    def get(self, request, *args, **kwargs):
        form = self.form_class(initial=self.initial)
        return render(request, self.template_name, {'form': form})

    def post(self, request, *args, **kwargs):
        form = self.form_class(request.POST)
        if form.is_valid():
           # <process form cleaned data>
           return redirect('/success/')
        return render(request, self.template_name, {'form': form})

このようなクラスを作成していくことで、get の場合と post の場合で 処理を明確に分けて記述することができます。

なお、 class-based views を使用している場合 urls.py は以下のように記述します.

urls.py
1
2
3
4
5
6
from django.conf.urls import url
from .views import MyFormView

patterns = [
    url(r'^about/', MyFormView.as_view()),
]

view class の継承

例えば以下のような view class があるとします。

ciews.py
1
2
3
4
5
6
7
8
from django.http import HttpResponse
from django.views.generic import View

class GreetingView(View):
    greeting = "Good Day"

    def get(self, request):
        return HttpResponse(self.greeting)

このクラスを継承し新しいクラスを作成するには以下のようにすればよいです。

views.py
1
2
class MorningGreetingView(GreetingView):
    greeting = "Morning to ya"

また、変数の変更は urls.py から行うことも可能です。

urls.py
1
2
3
urlpatterns = [
    url(r'^about/', GreetingView.as_view(greeting="G'day")),
]

Decorating class-based views

特定の view class に関しては、 例えば、ログイン済みのユーザーのみであるとか、特定の権限を持つユーザーのみであるとか 閲覧者に制約を儲けたい場合があると思います。

このような場合、 urls.py で以下のように設定を行います。

urls.py
1
2
3
4
5
6
7
8
from django.contrib.auth.decorators import login_required, permission_required
from django.views.generic import TemplateView
from .views import VoteView

urlpatterns = [
    url(r'^about/', login_required(TemplateView.as_view(template_name="secret.html"))),
    url(r'^vote/', permission_required('polls.can_vote')(VoteView.as_view())),
]

class-based views の初期値指定

Reference:http://stackoverflow.com/questions/11448690/is-it-okay-to-set-instance-variables-in-a-django-class-based-view

警告

この内容に関して

この内容は公式の説明には存在しないものになります。 暫定的に django 1.9 では上手くいくことを確認していますが、 今後変更が有り得るものであることに注意してください。

上記の例にもあるように class-based views の初期値は __init__ 関数で定義をすることはないようです。 これは urls.py で 使用している as_view が上記のクラスをインスタンス化しているわけではないことに由来します。 一方で、 class-based views の初期値として request の値を使用したい場合(例えば現在ログインしているユーザーを取得しておきたい場合など)、 初期値に特定の引数を渡したい場合もあります。

このような場合以下の例のように dispatch 関数をオーバーライトするといいようです。

views.py
1
2
3
4
5
6
7
class YourView(SomeView):
    def dispatch(self, request, *args, **kwargs):
        # parse the request here ie.
        self.foo = request.GET.get('foo', False)

        # call the view
        return super(YourView, self).dispatch(request, *args, **kwargs)