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 のエンコーダとかの話はこのあたり読むとよさそう。