How to write a Django REST API
The objective of this project is to evaluate the potential API-programming frameworks for django
Even though multi-response is not a framework for API programming actually it is the best fit for one of my requirements (content negotiation), and as it builds upon all the cruft available in django it can handle the task pretty well, and has a comfortable learning curve. (There is not much to learn actually.)
What do I want?
I will build a project with all these frameworks with the following features:
- it uses the same code for html, xml and json rendering as much as possible
- the urls are the same as much as possible. The best would be to rely entirely on content negotiation.
- the returned data should represent relationships in details. This means that I should be able to adjust what data to send back and span relationships.
- there are POST, PUT, READ and DELETE methods to be handled by the implementation
- HTTP status codes are used exhaustively for response formats other than html. (Btw, why browsers don’t support 201 status codes with a redirect?)
Moreover, I’ll try to understand if the frameworks allow for
- HTTP Basic,
- HTTP Digest and
- a custom authentication handler (e.g. OAuth)
These are partly in line with Jacob Kaplan-Moss’ recommendations that I’ve found in the middle of this quest.
The project
It’s a simple poll project:
- users can add a question (POST method)
- users can vote for one option (PUT method if available, POST otherwise)
- users can delete their votes (DELETE is available, GET otherwise)
Users are stored with the authentication framework, but for the moment I won’t use it really. No authentication is actually in place, so it simply stores the users, and you select which user is voting.
The API provides
- poll list
- poll details: question, options, votes
- possibility to create a poll
- possibility to add a vote
First I wrote a classic solution (revision 1 and reworked it in revision 2) that does what I want. Then went on to code the APIs with reusing as much of my original code as possible.
I know that my API structure might be problematic, for example I realy could not decide whether casting your vote means to create a new poll (a POST call), or change the actual state of the poll (a PUT call).
Finally I decided to go with PUT as this might be more demanding from the frameworks. Similarly in my solution the variable might stand for different data types depending on the method, this should be avoided, but I was too lazy to correct. As a result a call for /multi/api/2/ might mean
- to show poll 2 (GET)
- to vote on option 2 (PUT)
- to delete vote 2 (DELETE)
Not at all self explanatory. :)
The results
Conclusion
First of all, the whole project is available at Launchpad (yes, I like Bazaar). Feel free to grab it and point out flaws in my implementations.
All in all I have found the simplicity of multiresponse to win my heart, but YMMV. Still, even with multiresponse I couldn’t figure out how to use status codes properly. That is return 302 for html calls, but 201 for real API calls, and similarly for put and delete. My idea is that a Middleware could be used that translates non-html response codes to html-friendly ones if needed, or one could hack multiresponse.
Moreover, finally I did not implement wapi as its learning curve seems to be rather steep, but I plan to do it in the near future.
As a result for me the best API framework would be built up from the following pieces:
- uses content negotiation á la multiresponse
- adds some additional status codes and support for PUT and DELETE
As you can see from the code I’ve tried to write quite detailed tests for the classical implementation, but then I couldn’t write any tests for the api implementation without adding separate names for every format. (And I was too lazy to do that. :) ) This actually shows one of the biggest problems with the frameworks, namely there was no way to provide the format specific url in the response without much boring hacking.
In the following subsections I will provide a more detailed discussion of each framework. To understand these you might really want to look at the code I wrote. In any ways, if you have something to add, please don’t hesitate to leave a comment!
Details
webapi
Very simple to use, but doesn’t include any “advanced” stuff, and it’s relatively difficult to do fine grained customisation with it. For example I could not set up the response encoder in such a way that the poll list does not include the options and their votes, but only the questions and the uri. Moreover, while I would prefer to provide absolute urls, here I had to provide relative urls, and it’s not straightforward to find out where should one go to vote on a question.
One of my biggest problems was that it does not handle status codes well. Instead of calling the superclass of WebAPIResponse by passing the status code, it always return 200, and the message body will contain the actual error.
Finally, I couldn’t come up with a neat way of returning the WebAPIResponse, and I have to use three lines of code:
a = WebAPIResponse(request, obj, api_format=self.format)
str(a)
return a
As I’ve said, this is a really basic framework, and actually lacks many features like out-of-the-box support for HTTP Basic, Digest or OAuth. Of course, all these can be added by a bit of coding.
If you check my solution thouroughly, then you can see as well that I did not implement the whole project in webapi. Actually, the html format is not supported for PUT and DELETE methods. As web browsers don’t support these methods either probably the only way is to add separate lines into your urls.py and then write a simple function that forwards the call to the appropriate method of the API handler.
wapi
Probably to most complex and flexible. Actually, I’ve found its serialisation too complex, and I prefer webapi’s serialisation. Finally this led to not implement wapi at all, hopefully it will arrive next week.
rest-interface
This is the only framework that supports PUT and DELETE methods by default. (Even if they are not widely used, I find them rather useful for APIs.) As a result the status code are set as expected by default.
Here I decided to stick with the Collection and Entry classes defined by its model_resource modul. Unfortunately, the logic of your models does not necessarily coincides with the API-consumer’s logic. Especially not in terms of the data that you would like to return. On the other hand one might argue that strictly following my model structure gives lots of benefits, and it is much easier to understand on what models the different methods operate then with the other implementations. For example the confusion pointed out in my design is avoided with django-rest-interfaces.
From the code one might think that it is really simple to use django-rest-interface, but actually returning proper urls is much harder than it seems as long as the url depends on the format. For example you can not write your models get_absolute_url method as you are used to because it should take care of the format as well.
Finally, this implementation is the furthest away from my objectives. Probably the error is in my machine, and I would be really happy to see a better implementation.
Still, I’ve found this project very useful to reuse code from as you can see it in my implementation of webapi and multiresponse.
multiresponse
My only problem here was how to handle some status codes with HTML format. For example for a CREATE call the HTTP specification recommends status code 201. But this in the browser translates to an empty page. Even though the location is specified in the header the browser won’t redirect there.
Besides this I realised that I can reorganise my code a little bit. For example there is no need to provide an empty request[‘POST’] case in my create method if I show the form on the poll list page.
My only worry with my implementation is that users are actually not restricted to the api url that I provide to them, but they can directly call the create, read, etc methods as well.
On the other hand this is relatively easy to change if the real logic is defined inside the api method (in this case a class), and the others are always call the corresponding method after checking request.method.
Given the simple url handling I created a full set of urls for this implementation, and I’ve tried to extend my tests as well.
Finally I have to confess that the multiresponse code could be made much more elegant. For example, in reality I would create two API classes, one for sets and one for entities, and would add the methods inside these ones as django-rest-interfaces does. On the other hand the actual implementation shows how close the classical solution can be to the API based one.
Comments
Comment form for «How to write a Django REST API»