RESTful API using django

REST API를 지원하는 server가 필요해서 django로 구성 가능한 몇가지 framework (django-piston, django-tasypie, django-rest-framework)을 테스트 해 봤다. 세가지 framework 중에서 django-piston을 이용해서 작업을 했는데, 나중에는 django-rest-framework으로 변환할까 생각중이다.

django-piston

두개의 테이블(user, rule)과 10여개의 API set을 가진 서버를 구성하는데, 반나절 남짓의 작업으로 가능했다. 2011.10월 시점으로 공식적으로 v0.2.2버전이 release 되었다. 개발은 쉽고 편했는데, 2년여간 update가 되지 않았고, 문서가 상대적으로 빈약했다. 최근에 새로운 maintainer가 코드를 인수인계 받아서 update가 기대되고 있다.

urls.py

from piston.resource import Resource
from latte.api.handlers import UserHandler, RuleHandler, RuleSearchOwnerHandler, RuleSearchTargetHandler

class CsrfExemptResource(Resource):
    """A Custom Resource that is csrf exempt"""
    def __init__(self, handler, authentication=None):
        super(CsrfExemptResource, self).__init__(handler, authentication)
        self.csrf_exempt = getattr(self.handler, 'csrf_exempt', True)
 
user = CsrfExemptResource(UserHandler)

urlpatterns = patterns('',
    url(r'^user/?$', user),
    url(r'^user/(?P<email>[^/]+)/?$', user),
)

handler.py

from piston.handler import BaseHandler, AnonymousBaseHandler
from piston.utils import rc, require_mime, require_extended

from django.contrib.auth.models import User
from latte.api.models import Rule

class UserHandler(BaseHandler):
    """
    EntryPoint for registration
    """

    allowed_methods = ('GET', 'POST', 'DELETE')
    fields = ('email',)

    def read(self, request, email=None):
        """
        """
        if not email:
            return User.objects.filter(is_superuser=False)

        try:
            return User.objects.get(email=email,is_superuser=False)
        except (User.DoesNotExist, KeyError):
            return rc.NOT_FOUND


    def delete(self, request, email=None):
        """
        """
        if not email:
            return rc.BAD_REQUEST

        try:
            user = User.objects.get(email=email,is_superuser=False)
        except (User.DoesNotExist):
            return rc.NOT_FOUND

        user.delete()
        return rc.DELETED

    def create(self, request):
        """
        Process connection : register user with email and password
        """

        if not request.content_type:
            return rc.BAD_REQUEST
            
        data = request.data

        try:
            user = User.objects.get(username=data['email'])
            return rc.DUPLICATE_ENTRY
        except (User.DoesNotExist, KeyError):
            user = User(username=data['email'], email=data['email'])

        try:
            user.set_password(data['password'])
            user.save()
        except KeyError:
            return rc.BAD_REQUEST
            
        return rc.CREATED

django-tastypie

v1.0.0-beta 버전이 release 되었다. dehydrate, hydrate function에서 GET과 PUT에 대한 세부적인 제어를 하게 되어 있다.

urls.py

from django.conf.urls.defaults import *
from tastypie.resources import ModelResource
from tastypie.api import Api

from api.resources import UserResource, RuleResource

from django.contrib import admin
admin.autodiscover()

v1_api = Api(api_name='v1')
v1_api.register(UserResource())

urlpatterns = v1_api.urls

resources.py

from django.contrib.auth.models import User
from latte.api.models import Rule

from tastypie.resources import ModelResource
from tastypie.authorization import Authorization
from tastypie import fields
from tastypie import http

class UserResource(ModelResource):
    class Meta:
        queryset = User.objects.filter(is_superuser=False)
        list_allowed_methods = ['get', 'post']
        detail_allowed_methods = ['get', 'post']
        excludes = ['id']
        resource_name = 'user'
        include_resource_uri = False
        authorization = Authorization()
        fields = ['username']

    # def hydrate(self, bundle):
    #     bundle.obj.email = bundle.data['username']
        
    #     return bundle

    def obj_create(self, bundle, request=None, **kwargs):
        try:
            user = User.objects.get(username=bundle.data['email'])
            return http.HttpConflict()
        except (User.DoesNotExist, KeyError):
            user = User(username=bundle.data['email'], email=bundle.data['email'])
        
        bundle.obj = user
        return bundle
...

django-rest-framework

앞의 두 framework 대비해서 자동으로 API 접근에 관한 문서를 만들어주는 장점이 있다. Django의 class based view에 기반을 해서 간단하고, 모듈화 된 장점도 있다.

urls.py

from django.conf.urls.defaults import *

from djangorestframework.resources import ModelResource
from djangorestframework.views import ListOrCreateModelView, InstanceModelView

from latte.api.resources import RuleResource
from latte.api.views import TestView

urlpatterns = patterns('',
    url(r'^test/$', TestView.as_view(), name="test"),
                       
    url(r'^rule/$', ListOrCreateModelView.as_view(resource=RuleResource), name="rule-list"),
    url(r'^rule/(?P<pk>[^/]+)/$', InstanceModelView.as_view(resource=RuleResource),
    name="rule-detail"),
...
)

views.py

from django.core.urlresolvers import reverse

from djangorestframework.views import View
from djangorestframework.response import Response
from djangorestframework import status

from latte.api.models import Rule

class TestView(View):
    """
    TestView basic
    """
    # model = Rule
    fields = ('title', 'when', 'where', 'message')
    
    def get(self, request):
        """
        Handle Get requests
        """

        # print request.data
		return Rule.objects.all()
Written on October 31, 2011