クラスベースのジェネリックビュー

もともと 1.0 系くらいしかしらない Django 1.3 系を触っていて見知らぬものが出てきて使ってみたのでメモ。

今までのファンクションベースのジェネリックビューからクラスベースのジェネリックビューになった。

なったといってもファンクションベースが使えなくなったわけでは無い。

ファンクションベースは融通が利かなくて、ほんと特定用途にしか使えなかったのだけれど、クラスベースはかなり何でも出来る感じ。

ただ ... mixin で実装されているのでコード追いかけるのがめんどくさすぎる ... 。

更新履歴

  • get がいらなかったので削除

クラスベースを使った場合の方針

作ってみて感じたのは、クラスビューベースのジェネリックビューを使ってそのプロジェクト用の簡単なフレームワークを作っていくというのが、うまいやりかたの用です。

今回はあまりクラスを作りませんでしたが本来ならもう少し細かくクラスを作る必要があると思いました。複数の機能を持つビュークラスを作ってそれを urls.py から上手く使うというのがスマートな使い方なのかなと。

ただ、mixin ほんっとコード追いかけにくいですね ... 。

サンプルコード

あるイベントのユーザ管理とおやつ管理 API を使ったウェブサイトを作ってみたいといったイメージで書いてみました。

Django の ORM を使っていません。HTTP 経由でデータを取ってくるといった処理です。

ユーザ追加の URL
http://127.0.0.1:5000/pyspa/user/add/
ユーザ一覧の URL
http://127.0.0.1:5000/pyspa/user/list/csv/
おやつ追加の URL
http://127.0.0.1:5000/pyspa/snack/add/
おやつ一覧の URL
http://127.0.0.1:5000/pyspa/user/list/csv/

エラーハンドリングまったくやってない手抜きコードですが ... 。

forms.py は割愛しました。

HTTP 経由のデータベースにデータを追加する例(views.py)

import requests

from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response
from django.views.generic.base import TemplateView
from django.views.generic.edit import FormView

# 追加用フォーム
class AddFormView(FormView):
  post_url = None
  def post(self, request, *args, **kwargs):
    # request.POST が入ったフォームインスタンスを返す
    form = self.get_form(self.get_form_class())
    if form.is_valid():
      # バリデート済みの dict を渡す
      r = requests.post(self.post_url, data=form.cleaned_data)
      if r.status_code == 200:
        return HttpResponseRedirect(self.get_success_url())
    return render_to_response(self.template_name, {'form': form})

# リスト一覧ビュー
class ListView(TemplateView):
  get_url = None
  def get(self, request, *args, **kwargs):
    r = requests.get(self.get_url)
    if r.status_code == 200:
      reader = csv.reader(cStringIO.StringIO(r.content))
      return self.render_to_response({'reader': reader})
    return HttpResponse(u'API が上手いこと接続でけんかったよ')

HTTP 経由のデータベースにデータを追加する例(urls.py)

from django.conf.urls.defaults import *

from views import ListView, AddFormView

from forms import UserForm, SnackForm

urlpatterns = patterns('',
  (r'^user/add/$',
    AddFormView.as_view(
      template_name='core/user_form.html',
      form_class=UserForm,
      success_url='/pyspa/user/list/',
      post_url='http://127.0.0.1:5000/pyspa/user/add/'
    )
  ),
  (r'^user/list/$',
    ListView.as_view(
      template_name='core/user_list.html',
      get_url='http://127.0.0.1:5000/pyspa/user/list/csv/'
    )
  ),
  (r'^snack/add/$',
    AddFormView.as_view(
      template_name='core/snack_form.html',
      form_class=SnackForm,
      success_url='/pyspa/snack/list/',
      post_url='http://127.0.0.1:5000/pyspa/snack/add/'
    )
  ),
  (r'^snack/list/$',
    ListView.as_view(
      template_name='core/snack_list.html',
      get_url='http://127.0.0.1:5000/pyspa/snack/list/csv/'
    )
  ),
)

感想

とにかく楽になってました。今回作ったケースがシンプルだったというのもありますが ... 。今後は使えるところにはガンガン使っていくべきだと感じました。ただ、クラスを抽象化しすぎずにわかりやすく書いていくのは難しいだろうなぁと。

ただ、ある程度 Django わかっている人がベースクラスを作ってそれを上手く mixin しながら使えば開発コストも下げられるのかも知れません。

あとはクラスベースジェネリックビューのテストの書き方を知りたいところですね。今回みたいな例の場合は mock 必須になるし。

Django 1.3 面白いですね、管理画面はもっと色々出来ると聞きましたので、機会があれば触ってみたいと思いました。

感謝

実際に仕事で Django を使っている id:nullpobug に色々アドバイスを頂きました。感謝!