Djangoで Class-based views を使って開発するなら 「Classy Class-Based Views.」は必須(初心者向け)

この投稿は 「Django Advent Calendar 2020 – Qiita」 12日目の記事です。

初めまして、SUNABACOの いとむら です。
この記事は、Djangoで初めて「Class-based views」を使って開発をする人の手助けになるといいなと思って書きました。

Class-based views とは

Class-based views は「汎用クラスビュー」または「汎用ビュー」とも言います。公式ドキュメントには以下のように説明されています。

ビューとは、リクエストを受け取りレスポンスを返す、呼び出し可能なオブジェクトです。(略)。ビューを構造化し、継承とミックスインを利用してコードを再利用することを可能にします。

https://docs.djangoproject.com/ja/2.2/topics/class-based-views/

私の理解だと、一般的に使われるであろう(汎用性のある)処理をつくっておき(構造化)し、それぞれ使うときに継承、ミックスインの利用でビューを作り上げいくということです。

そもそもビューとは

DjangoのMVTでいうところの「V」に当たる部分です。本記事では触れませんが、ビューを書く方法としては「function-based views」もあります。公式ドキュメントでは Class-based views より先に function-based views について触れられています。 https://docs.djangoproject.com/ja/3.1/topics/

ビューは前提として以下の3つの条件を満たさないといけません。

  • request を受け取れること
  • response を返すこと
  • 呼び出し可能であること

function-based-views であろうと Class-based views であろうと以上3つの条件を満たす必要があります。なので両者とも最終的にやっていることは同じになります。

ビューの例

リソース作成の処理を function-based viewsClass-based views で見比べてみます。(CRUDでいうところのCreate)

function-based views

from django.shortcuts import render
from items.forms import ItemForm


def item_create(request):
    """商品の新規作成をする"""
    if request.method == 'GET':
        form = ItemForm()
        return render(
            request,
            'stores/item_form.html',
            dict(form=form)
        )
    elif request.method == 'POST':
        form = ItemForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect(form.instance.get_absolute_url())
        else:
            return render(
                request,
                'stores/item_form.html',
                dict(form=form)
            )

Class-based views

from django.views import generic
from items.forms import ItemForm
from items.models import Item


class ItemCreateView(generic.CreateView):
    """商品の新規作成をする"""
    model = Item
    form_class = ItemForm

    def get_success_url(self):
        return self.object.get_absolute_url()


item_create = ItemCreateView.as_view()

フォームクラスについては触れません。また、 Class-based views のコードの最後にある as_view() については過去のDjangoアドベントカレンダーでtell-kさんが詳しく書かれていますのでそちらをご確認ください。 Djangoのクラスベースビューのas_viewて何なの?

上記に2パターンのビューの書き方を例に出しました。私が Class-based-views の書き方を初めて見たとき、「よくわからない。。。」が感想でした。 (この記事を読んでいる方は function-based views でビューを一度でも書いた事あると思ってます笑)

Class-based views でビューを定義したとき、モデルの指定やフォームクラスの指定については、「まー、なんとなくわかる」。でも、テンプレートへ渡す変数は?そもそもテンプレート名の指定は?as_view()って?みたいに謎だらけでした。function-based views でビューを定義した場合は、上から下に処理を追っていくだけなので何をやっているかがわかりやすい。しかし、Class-based views でビューを定義した場合は、処理を追えない。これは、汎用クラスビューを継承して定義したので、見えるところには処理は書いていないからです。なので、理解するためには継承した CreateView を調べないといけません。

そんなときに超便利な Classy Class-Based Views. を使うわけです!やっと本題。

Class-based views については akiyokoさんの 現場で使える Django の教科書《基礎編》【紙の本】 で学べますので是非御覧ください。僕は基礎編も発展編も紙とKindleの両方を持っています。

Classy Class-Based Views.

Classy Class-Based Views. TOPページ

このサイトは、Djangoで開発する際にChromeのタブに常駐させています。
Classy Class-Based Views.

TOPページに、汎用ビューがまとめられています。デフォルトで表示されているだけで24個あります。「Show more」を押せばミックスイン含めさらに表示されます。

今回は、前の例で CreateView をみていきます。
https://ccbv.co.uk/projects/Django/3.0/django.views.generic.edit/CreateView/

CreateViewの概要 (Classy Class-Based Views より引用)
CreateViewの概要 (Classy Class-Based Views より引用)

見方は図に示した通りです。一番の有用と思うのが「Hierarchy diagram」ボタンを押したときに表示される継承関係を示した図です。

CreateViewの継承関係を示した図 (Classy Class-Based Views より引用)

汎用クラスビューはそれぞれで、上書きできる属性や関数が異なっています。また、公式ドキュメントではそれらが整理されていなく、GitHubでDjangoのコードを読みに行くしか方法はない。。。のですが、この「Classy Class-Based Views.」には、属性や関数、継承関係など見やすくまとめられています。またメソッドの処理もまとめて書かれているのでGitHubまでコードを見に行かなくても済みます。

CreateViewのGETリクエスト

CreateView のGETリクエストでは下記の順番で処理されます。super() で親クラスのメソッドを呼び出している。

BaseCreateView の get()
def get(self, request, *args, **kwargs):
    self.object = None
    return super().get(request, *args, **kwargs)
ProcessFormView の get()
def get(self, request, *args, **kwargs):
    """Handle GET requests: instantiate a blank version of the form."""
    return self.render_to_response(self.get_context_data())
FormMixin の get_context_data()
def get_context_data(self, **kwargs):
    """Insert the form into the context dict."""
    if 'form' not in kwargs:
        kwargs['form'] = self.get_form()
    return super().get_context_data(**kwargs)
SingleObjectMixin の get_context_data()
def get_context_data(self, **kwargs):
    """Insert the single object into the context dict."""
    context = {}
    if self.object:
        context['object'] = self.object
        context_object_name = self.get_context_object_name(self.object)
        if context_object_name:
            context[context_object_name] = self.object
    context.update(kwargs)
    return super().get_context_data(**context)

ContextMixin の get_context_data()
def get_context_data(self, **kwargs):
    kwargs.setdefault('view', self)
    if self.extra_context is not None:
        kwargs.update(self.extra_context)
    return kwargs
TemplateResponseMixin の render_to_response()
def render_to_response(self, context, **response_kwargs):
    """
    Return a response, using the `response_class` for this view, with a
    template rendered with the given context.
    Pass response_kwargs to the constructor of the response class.
    """
    response_kwargs.setdefault('content_type', self.content_type)
    return self.response_class(
        request=self.request,
        template=self.get_template_names(),
        context=context,
        using=self.template_engine,
        **response_kwargs
    )

これも「Classy Class-Based Views.」を見ながらだと簡単に追うことができます。ぜひご自身で、処理を追ってみてください。

おわりに

Class-based views でビューを作ろうと思ったときに、概要を掴むために「Classy Class-Based Views.」が有用だということを伝えました。まず、これで全体をつかみそのあとに、Djangoのコード、公式ドキュメントを見るとさらに理解が進むと思います。

この記事が、Class-based views を使った開発をする方の手助けとなれば嬉しく思います。

【宣伝】デザインコーススクール生募集

デザイン思考、情報アーキテクチャ、UI、UX、タイポグラフィ、色彩論、Adobe系ツールなど、「デザインは機能である」ことを学ぶ4週間のスクールを開催します。また、今回は弊社代表が大手企業コンサルでも実施している「ユーザービリティエンジニアリング」について特別講義も入っています!

申込締切
2021年1月8日(金)
面接日
1月9日(土)
受講期間
1月11日(月)~2月5日(金) 土曜・日曜を除く20日間
講義時間
昼コース 13:00 ~ 16:00, 夜コース 19:00 ~ 22:00
新規受講
¥70,000(税別)
継続受講
デザインコースの卒業生は継続料金¥20,000(税別)で受講可能