django-pistonでシンプルなAPIを作ってみる

最近django-tastypieをちょいちょい触っていたのですが、同じような目的のライブラリにdjango-pistonというのがあると知りました。

このサイトによるとtastypieとpistonで人気を二分しているように見えます。
http://www.djangopackages.com/grids/g/rest/


一番シンプルな使い方を試してみました。

まず普通のモデルを用意

app/models.py

class Todo(models.Model):
    title = models.CharField(max_length=255)
    finished = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)

apiへアクセスに何を返すかをHandlerクラスに定義します
apiという別のappを作ってそこに書くのが流儀のようです

親クラスのBaseHandlerに基本の実装っぽいのが書いてあるんだけど、なんかよくわからない動きをするので、
参考にしながら意図通りのCRUD操作ができるように書いてみました。


api/handlers.py

from django.core.exceptions import ObjectDoesNotExist
from piston.handler import BaseHandler
from piston.utils import rc
from todo.models import Todo


class TodoHandler(BaseHandler):
    allowed_methods = ('GET','POST', 'PUT', 'DELETE')
    fields = ('id', 'title','finished', 'created')
    model = Todo

    def read(self, request, *args, **kwargs):
        pkfield = self.model._meta.pk.name
        if pkfield in kwargs:
            try:
                return self.queryset(request).get(pk=kwargs.get(pkfield))
            except ObjectDoesNotExist:
                return rc.NOT_FOUND
        else:
            return self.queryset(request).all()


    def create(self, request, *args, **kwargs):
        attrs = self.flatten_dict(request.data)
        inst = self.model(**attrs)
        inst.save()
        return inst

    def delete(self, request, *args, **kwargs):
        if not args:
            for inst in self.queryset(request).all():
                inst.delete()
        else:
            inst = self.queryset(request).get(*args, **kwargs)
            inst.delete()
        return rc.DELETED


    def update(self, request, *args, **kwargs):
        pkfield = self.model._meta.pk.name

        if pkfield not in kwargs:
            # No pk was specified
            return rc.BAD_REQUEST

        try:
            inst = self.queryset(request).get(pk=kwargs.get(pkfield))
        except ObjectDoesNotExist:
            return rc.NOT_FOUND

        attrs = self.flatten_dict(request.data)
        for k,v in attrs.iteritems():
            setattr( inst, k, v )

        inst.save()
        return rc.ALL_OK


詳細はapi/urls.pyに書きます
api/urls.py

from api.handlers import TodoHandler
from django.conf.urls import patterns
from piston.resource import Resource

todo_resource = Resource(TodoHandler)

urlpatterns = patterns('',
                       (r'^todo/(?P<id>\d+)/$', todo_resource),
                       (r'^todo/$', todo_resource),
                       )

あとsyncdbしとけば、でとりあえずアクセスできるようになりました。


requestsでひと通りアクセスしてみます

# coding=utf-8
import requests
import json


# データを全部削除
r = requests.delete('http://localhost:8000/api/todo/')
assert(r.status_code == 204)

# データが全て消えていることを確認
r = requests.get('http://localhost:8000/api/todo/')
assert(r.status_code == 200)
assert(len(json.loads(r.text)) == 0)

# 登録
payload = {'title': u'test', 'finished': False}
r = requests.post('http://localhost:8000/api/todo/', data=json.dumps(payload), headers={"Content-Type": "application/json"})
assert(r.status_code == 200)


#全件取得
r = requests.get('http://localhost:8000/api/todo/')
res = json.loads(r.text)
assert(r.status_code == 200)
assert(len(res) == 1)
assert(res[0]["title"] == u"test")
assert(res[0]["id"] == 1)


## idを指定して取得
r = requests.get('http://localhost:8000/api/todo/1')
res = json.loads(r.text)
assert(r.status_code == 200)
assert(res["id"] == 1)
assert(res["finished"] == False)
assert(res["title"] == u"test")

# 更新
payload = {'title': u'test', 'finished': True}
r = requests.put('http://localhost:8000/api/todo/1', data=json.dumps(payload), headers={"Content-Type": "application/json"})
assert(r.status_code == 200)

# 更新内容を確認
r = requests.get('http://localhost:8000/api/todo/1')
res = json.loads(r.text)
assert(r.status_code == 200)
assert(res["id"] == 1)
assert(res["finished"] == True)
assert(res["title"] == u"test")


# idを指定して削除
r = requests.delete('http://localhost:8000/api/todo/1')
assert(r.status_code == 204)

# データが消えていることを確認
r = requests.get('http://localhost:8000/api/todo/1/')
assert(r.status_code == 404)


ちゃんと動きました。

感想

どっちがいいかはもっと使い込んでみないとなんともいえない。
ただとりあえず動かしてみるまではtastypieのほうが楽でした。

あと、pistonはbitbucket、tastypieはgithubなんだけど、みた感じtastypieのほうが盛んにcommitされているようです。

django-piston
https://bitbucket.org/jespern/django-piston/wiki/Home

django-tastypie
https://github.com/toastdriven/django-tastypie