Building Better Web Services With Django (Part 2)
In the first part I talked about using the Content-Type and Accept HTTP headers to allow a single website to be use both by humans and programs.
In the previous part I gave a decorator which can be used to make working with JSON very easy. For our use though this isn’t great because a view decorated in this way only accepts JSON as the POST body and only returns JSON, regardless of the HTTP headers.
The decorator given below relies on a django snippet to decode the Accept header for us so don’t forget to added it to your middleware.
def content_type(func, common=None, json_in=None, json_out=None, form_in=None):
def wrapper(req, *args, **kwargs):
# run the common function, if we have one
if common is not None:
args, kwargs = common(req, *args, *kwargs), {}
if isinstance(args, HttpResponse): return args
content_type = req.META.get("content_type", "")
if content_type == "application/json":
args, kwargs = json_in(req, json.loads(req.raw_post_data), *args, *kwargs), {}
elif content_type == "application/x-www-form-urlencoded":
args, kwargs = json_in(req, req.POST, *args, *kwargs), {}
else:
return HttpResponse(status=415, "Unsupported Media Type")
if isinstance(args, HttpResponse): return args
for (media_type, q_value) in req.accepted_types:
if media_type == "text/html":
return func(req, args, kwargs)
else:
r = json_out(req, args, kwargs)
if isinstance(r, HttpResponse):
return r
else:
return HttpResponse(json.dumps(r), mimetype="application/json")
return func(req, args, kwargs)
return wrapper
So, how can we use this decorator? Let’s imagine we’re creating a blog and we have a view which displays a post on that blog. If they user posts it should create a new comment. Firstly we create a function, common, which gets the blog object and returns a 404 if it doesn’t exist. The return of this function is passed onto all other functions as their arguments.
def common(req, blog_id):
try:
return (get_post_by_id(int(blog_id)), )
except ValueError:
return HttpResponse(status=404)
Next we write two functions to handle the cases where the users POSTs a form encoded body, or some JSON. The return values of these functions are passed onto the chosen output function as the arguments.
def json_in(req, json, blog_post):
# process json
return (blog_post ,)
def form_in(req, form, blog_post):
# process form
return (blog_post, )
The JSON output function doesn’t need to return an HttpResponse object like a normal Django view because the output is automatically encoded as a string and wrapped in a response object.
def json_out(req, blog_post):
return blog_post.to_json()
Finally we come to the HTML output function. This function is also called if not mime type in Accept is suitable.
@content_type(common=common, json_in=json_in, json_out=json_out, form_in=form_in)
def blog_post(req, blog_post):
return render_to_template("post.html", {"post": blog_post})
This decorator is really little more than a sketch. Many more content types could be supported, but hopefully it gives a good example of how you can write a very flexible webservice and still reduce code duplication as much as possible.





Thanks for great code and doc. Kudos!
Benny Daon
April 25, 2009 at 10:07 pm
very nice, but could you please change the ugly and eye-hurting colors? The light blue background makes especially the code nearly unreadable. Also maybe you could enhance yuor blog a lot by using syntax highliting for the code parts – thanks!!!
Galer Organ Bestofio
April 16, 2010 at 2:20 pm