Skip to content

Instantly share code, notes, and snippets.

@jsocol
Last active September 28, 2015 07:27
Show Gist options
  • Save jsocol/1405096 to your computer and use it in GitHub Desktop.
Save jsocol/1405096 to your computer and use it in GitHub Desktop.
@json_view decorator
from functools import wraps
import json
from django import http
from django.core.exceptions import PermissionDenied
from core.exceptions import BadRequest
JSON = 'application/json'
# TODO(james): Log the hell out of the exceptions.
def json_view(f):
"""Ensure the response content is well-formed JSON.
Views wrapped in @json_view can return JSON-serializable Python objects,
like lists and dicts, and the decorator will serialize the output and set
the correct Content-type.
Views may also throw known exceptions, like Http404, PermissionDenied,
etc, and @json_view will convert the response to a standard JSON error
format, and set the status code and content type.
"""
@wraps(f)
def _wrapped(req, *a, **kw):
try:
ret = f(req, *a, **kw)
blob = json.dumps(ret)
return http.HttpResponse(blob, content_type=JSON)
except http.Http404, e:
blob = json.dumps({
'error': 404,
'message': str(e),
})
return http.HttpResponseNotFound(blob, content_type=JSON)
except PermissionDenied, e:
blob = json.dumps({
'error': 403,
'message': str(e),
})
return http.HttpResponseForbidden(blob, content_type=JSON)
except BadRequest, e:
blob = json.dumps({
'error': 400,
'message': str(e),
})
return http.HttpResponseBadRequest(blob, content_type=JSON)
except Exception, e:
blob = json.dumps({
'error': 500,
'message': str(e),
})
return http.HttpResponseServerError(blob, content_type=JSON)
return _wrapped

I finally put this in its own package! Check out django-jsonview.

This is a decorator that guarantees a response will be JSON, and sends meaningful (to a developer) error messages. It relies on a BadRequest exception I created elsewhere, because there is no standard way of handling that in Django (frex, we should really return HTTP 400 on form validation failures but they don't make that particularly easy). That could easily be removed, though.

import json
from django import http
from django.core.exceptions import PermissionDenied
from django.test import RequestFactory, TestCase
from nose.tools import eq_
from core.decorators import json_view
from core.exceptions import BadRequest
rf = RequestFactory()
JSON = 'application/json'
class JsonViewTests(TestCase):
def test_object(self):
data = {
'foo': 'bar',
'baz': 'qux',
'quz': [{'foo': 'bar'},],
}
expect = json.dumps(data)
@json_view
def temp(req):
return data
res = temp(rf.get('/'))
eq_(200, res.status_code)
eq_(expect, res.content)
eq_(JSON, res['content-type'])
def test_list(self):
data = ['foo', 'bar', 'baz']
expect = json.dumps(data)
@json_view
def temp(req):
return data
res = temp(rf.get('/'))
eq_(200, res.status_code)
eq_(expect, res.content)
eq_(JSON, res['content-type'])
def test_404(self):
@json_view
def temp(req):
raise http.Http404, 'foo'
res = temp(rf.get('/'))
eq_(404, res.status_code)
eq_(JSON, res['content-type'])
data = json.loads(res.content)
eq_(404, data['error'])
eq_('foo', data['message'])
def test_permission(self):
@json_view
def temp(req):
raise PermissionDenied, 'bar'
res = temp(rf.get('/'))
eq_(403, res.status_code)
eq_(JSON, res['content-type'])
data = json.loads(res.content)
eq_(403, data['error'])
eq_('bar', data['message'])
def test_bad_request(self):
@json_view
def temp(req):
raise BadRequest, 'baz'
res = temp(rf.get('/'))
eq_(400, res.status_code)
eq_(JSON, res['content-type'])
data = json.loads(res.content)
eq_(400, data['error'])
eq_('baz', data['message'])
def test_server_error(self):
@json_view
def temp(req):
raise TypeError, 'fail'
res = temp(rf.get('/'))
eq_(500, res.status_code)
eq_(JSON, res['content-type'])
data = json.loads(res.content)
eq_(500, data['error'])
eq_('fail', data['message'])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment