Building RESTful Python Web Services
上QQ阅读APP看书,第一时间看更新

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.