Working with wrappers to write API views
Our code in the games/views.py
file declared a JSONResponse
class and two function-based views. These functions returned JSONResponse
when it was necessary to return JSON data and a django.Http.Response.HttpResponse
instance when the response was just of an HTTP status code.
No matter the accepted content type specified in the HTTP request header, the view functions always provide the same content in the response body-JSON. Run the following two commands to retrieve all the games with different values for the Accept
request header-text/html
and application/json
:
http :8000/games/ Accept:text/html http :8000/games/ Accept:application/json
The following are the equivalent curl commands:
curl -H 'Accept: text/html' -iX GET :8000/games/ curl -H 'Accept: application/json' -iX GET :8000/games/
The preceding commands will compose and send the following HTTP request: GET http://localhost:8000/games/
. The first command defines the text/html
value for the Accept
request header. The second command defines the application/json
value for the Accept
request header.
You will notice that both the commands produce the same results, and therefore, the view functions don't take into account the value specified for the Accept
request header in the HTTP requests. The header response for both commands will include the following line:
Content-Type: application/json
The second request specified that it will only accept text/html
but the response included a JSON body, that is, application/json
content. Thus, our first version of the RESTful API is not prepared to render content other from JSON. We will make some changes to enable the API to render other contents.
Whenever we have doubts about the methods supported by a resource or resource collection in a RESTful API, we can compose and send an HTTP request with the OPTIONS
HTTP verb and the URL for the resource or resource collection. If the RESTful API implements the OPTIONS HTTP verb for a resource or resource collection, it provides a comma-separated list of HTTP verbs or methods that it supports as a value for the Allow
header in the response. In addition, the response header will include additional information about other supported options, such as the content type it is capable of parsing from the request and the content type it is capable of rendering on the response.
For example, if we want to know the HTTP verbs that the games collection supports, we can run the following command:
http OPTIONS :8000/games/
The following is the equivalent curl command:
curl -iX OPTIONS :8000/games/
The previous command will compose and send the following HTTP request: OPTIONS http://localhost:8000/games/
. The request will match and run the views.game_list
function, that is, the game_list
function declared within the games/views.py
file. This function only runs the code when the request.method
is equal to 'GET'
or 'POST'
. In this case, request.method
is equal to 'OPTIONS'
, and therefore, the function won't run any code and won't return any response, specifically, it won't return an HttpResponse
instance. As a result, we will see the following Internal Server Error
listed in Django's development server console output:
Internal Server Error: /games/ Traceback (most recent call last): File "/Users/gaston/Projects/PythonRESTfulWebAPI/Django01/lib/python3.5/site-packages/django/core/handlers/base.py", line 158, in get_response % (callback.__module__, view_name)) ValueError: The view games.views.game_list didn't return an HttpResponse object. It returned None instead. [08/Jun/2016 20:21:40] "OPTIONS /games/ HTTP/1.1" 500 49173
The following lines show the header for the output that also includes a detailed HTML document with detailed information about the error because the debug mode is activated for Django. We receive a 500 Internal Server Error
status code:
HTTP/1.0 500 Internal Server Error Content-Type: text/html Date: Wed, 08 Jun 2016 20:21:40 GMT Server: WSGIServer/0.2 CPython/3.5.1 X-Frame-Options: SAMEORIGIN
Obviously, we want to provide a more consistent API and we want to provide an accurate response when we receive a request with the OPTIONS
verbs for either a game resource or the games collection.
If we compose and send an HTTP request with the OPTIONS
verb for a game resource, we will see the same error and we will have a similar response because the views.game_detail
function only runs the code when the request.method
is equal to 'GET'
, 'PUT'
, or 'DELETE'
.
The following commands will produce the explained error when we try to see the options offered for the game resource whose id or primary key is equal to 3
. Don't forget to replace 3
with a primary key value of an existing game in your configuration:
http OPTIONS :8000/games/3/
The following is the equivalent curl command:
curl -iX OPTIONS :8000/games/3/
We just need to make a few changes in the games/views.py
file to solve the issues we have been analyzing for our RESTful API. We will use the @api_view
decorator, declared in rest_framework.decorators
, for our function-based views. This decorator allows us to specify the HTTP verbs that our function can process. If the request that has to be processed by the view function has an HTTP verb that isn't included in the string list specified as the http_method_names
argument for the @api_view
decorator, the default behavior returns a 405 Method Not Allowed
status code. This way, we make sure that whenever we receive an HTTP verb that isn't considered within our function view, we won't generate an unexpected error as the decorator handles the response for the unsupported HTTP verbs or methods.
Tip
Under the hoods, the @api_view
decorator is a wrapper that converts a function-based views into a subclass of the rest_framework.views.APIView
class. This class is the base class for all views in Django REST Framework. As we might guess, in case we want to work with class-based view, we can create classes that inherit from this class and we will have the same benefits that we analyzed for the function-based views that use the decorator. We will work with class-based views in the forthcoming examples.
In addition, as we specify a string list with the supported HTTP verbs, the decorator automatically builds the response for the OPTIONS
HTTP verb with the supported methods and parser and render capabilities. Our actual version of the API is just capable of rendering JSON as its output. The usage of the decorator makes sure that we always receive an instance of the rest_framework.request.Request
class in the request
argument when Django calls our view function. The decorator also handles the ParserError
exceptions when our function views access the request.data
attribute that might cause parsing problems.