dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

[django]Django REST Frameworkを使わずにDjangoでREST APIを作る

Django の Class-based View で、あるリソースを JSON 形式で返すような REST API っぽいものを作ろうと思ったんですが
Django REST API」とかでググるDjango REST Framework というフレームワークの話ばかり。

Google App Engine で開発しているので Django のモデルはそのままでは使っておらず、
フレームワークをそのまま利用できるのか微妙。。。
(ちゃんと調べてないですが。チュートリアル見よう)

と思ったら、公式ドキュメントにそういったことをやるためのサンプルがありました。
Django のバージョンは 1.5 です。

https://docs.djangoproject.com/en/1.5/topics/class-based-views/mixins/#jsonresponsemixin-example

今回のようにレスポンスを JSON で返す場合に限らず、Class-based View で何度も同じ処理を記述する場合、mixin クラスを用意して継承すると良い。

mixin クラス

以下はリンク先のものをそのまま引用。

import json
from django.http import HttpResponse

class JSONResponseMixin(object):
    """
    A mixin that can be used to render a JSON response.
    """
    response_class = HttpResponse

    def render_to_response(self, context, **response_kwargs):
        """
        Returns a JSON response, transforming 'context' to make the payload.
        """
        response_kwargs['content_type'] = 'application/json'
        return self.response_class(
            self.convert_context_to_json(context),
            **response_kwargs
        )

    def convert_context_to_json(self, context):
        "Convert the context dictionary into a JSON object"
        # Note: This is *EXTREMELY* naive; in reality, you'll need
        # to do much more complex handling to ensure that arbitrary
        # objects -- such as Django model instances or querysets
        # -- can be serialized as JSON.
        return json.dumps(context)

mixin クラスを使う

以下のように継承した View クラスを用意して使う。

class PostCollectionView(JSONResponseMixin, View):

    def get(self, request):
        objs = PostCard.query().fetch()  # GAE のデータストアから取得したオブジェクト
        return self.render_to_response(self._objects_to_dicts(objs))

    def _objects_to_dicts(self, objs):
        res = []
        for obj in objs:
            res.append({
                'id': obj.key.id(),
                ...
                })
        return res

デフォルトの mixin クラスの問題点と、改善策

json.dumps() は datetime 型をシリアライズできないので、
リソースのプロパティに datetime 型が含まれていると ↑ の JSONResponseMixin はうまく機能しない。

>>> import json
>>> import datetime
>>> d = datetime.datetime(2015, 4, 1, 9, 0, 0)
>>> type(d)
<type 'datetime.datetime'>
>>> json.dumps(d)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/*****/pyenv/versions/2.7.5/lib/python2.7/json/__init__.py", line 243, in dumps
    return _default_encoder.encode(obj)
  File "/Users/*****/.pyenv/versions/2.7.5/lib/python2.7/json/encoder.py", line 207, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/Users/*****/.pyenv/versions/2.7.5/lib/python2.7/json/encoder.py", line 270, in iterencode
    return _iterencode(o, 0)
  File "/Users/*****/.pyenv/versions/2.7.5/lib/python2.7/json/encoder.py", line 184, in default
    raise TypeError(repr(o) + " is not JSON serializable")
TypeError: datetime.datetime(2015, 4, 1, 9, 0) is not JSON serializable

この問題を解決するために、django.core.serializers.json にある DjangoJSONEncoder クラスを使う。
具体的には JSONResponseMixin クラスを次のように修正する。

import json
from django.core.serializers.json import DjangoJSONEncoder
from django.http import HttpResponse

class JSONResponseMixin(object):
    """
    A mixin that can be used to render a JSON response.
    """
    response_class = HttpResponse

    ...(略)...

    def convert_context_to_json(self, context):
        "Convert the context dictionary into a JSON object"
        # Note: This is *EXTREMELY* naive; in reality, you'll need
        # to do much more complex handling to ensure that arbitrary
        # objects -- such as Django model instances or querysets
        # -- can be serialized as JSON.
        return json.dumps(context, cls=DjangoJSONEncoder)

とりあえずこれでいい感じ。

json のエンコーダとかの話はこのあたり読むとよさそう。

http://docs.python.jp/2.7/library/json.html