View method responses

The render() method to generate view method responses you've used up to this point is actually a shortcut. You can see toward the top of listing 2-23, the render() method is part of the django.shortcuts package.

This means there are other alternatives to the render() method to generate a view response, albeit the render() method is the most common technique. For starters, there are three similar variations to generate view method responses with data backed by a template, as illustrated in listing 2-24.

Listing 2-24 Django view method response alternatives

----------------------------------------------------------------------------
# Option 1)
from django.shortcuts import render

def detail(request,store_id=1,location=None):
    ...
    context = {}
    return render(request,'stores/detail.html', context)

----------------------------------------------------------------------------
# Option 2)
from django.template.response import TemplateResponse

def detail(request,store_id=1,location=None):
    ...
    context = {}
    return TemplateResponse(request, 'stores/detail.html', context)

----------------------------------------------------------------------------
# Option 3)
from django.http import HttpResponse
from django.template import loader

def detail(request,store_id=1,location=None):
     ...
     context = {}
     t = loader.get_template('stores/detail.html') 
     return HttpResponse(t.render(context))

The first option in listing 2-24 is the django.shortcuts.render() method which shows three arguments to generate a response: the (required) request reference, a (required) template route and an (optional) dictionary -- also known as the context -- with data to pass to the template.

There are actually three more (optional) arguments for the render() method which are not shown in listing 2-24: content_type which sets the HTTP Content-Type header for the response and which defaults to text/html; status which sets the HTTP Status code for the response which defaults to 200 that represents an HTTP 200 OK value; and using to specify the template engine -- either jinja2 or django -- to generate the response. The next section on HTTP handling for the render() method describes how to use content_type & status, while chapters 3 & 4 talk about Django templates and Jinja templates.

The second option in listing 2-24 is the django.template.response.TemplateResponse() class, which in terms of input is nearly identical to the render() method. The difference between the two variations is, TemplateResponse() can alter a response once a view method is finished (e.g. via middleware), where as the render() method is considered the last step in the lifecycle after a view method finishes. You should use TemplateResponse() when you foresee the need to modify view method responses in multiple view methods after they finish their work, a technique that's discussed in a later section in this chapter on view method middleware.

There are actually five more (optional) arguments for the TemplateResponse() class which are not shown in listing 2-24: content_type which defaults to text/html; status which defaults to 200 that represents an HTTP 200 OK value; charset which sets the response encoding from the HTTP Content-Type header or DEFAULT_CHARSET in settings.py which in itself defaults to utf-8; using to indicate the template engine -- either jinja2 or django -- to generate the response; and headers to override HTTP response headers that default to calculated values for Content-Length, Content-Type and Content-Disposition.

The third option in listing 2-24 represents the longest, albeit the most flexible response creation process. This process first loads a template with the django.template.loader.get_template() method and then returns an HTTPResponse instance which renders the template with its context. This last option should be the preferred choice when a view method response requires advanced options. The upcoming section on built-in response shortcuts for in-line & streamed content, has more details on HTTPResponse response types.

Response options for HTTP Status and Content-type headers

Browsers set HTTP headers in requests to tell applications to take into account certain characteristics for processing. Similarly, applications set HTTP headers in responses to tell browsers to take into account certain characteristics for the content being sent out. Among the most important HTTP headers set by applications like Django are Status and Content-Type.

The HTTP Status header is a three digit code number to indicate the response status for a given request. Examples of Status values are 200 which is the standard response for successful HTTP requests and 404 which is used to indicate a requested resource could not be found. The HTTP Content-Type header is a MIME(Multipurpose Internet Mail Extensions) type string to indicate the type of content in a response. Examples of Content-Type values are text/html which is the standard for an HTML content response and image/gif which is used to indicate a response is a GIF image.

By default and unless there's an error, all Django view methods that create a response with django.shortcuts.render(), a TemplateResponse() class or HttpResponse() class -- illustrated in listing 2-24 -- create a response with the HTTP Status value set to 200 and the HTTP Content-Type set to text/html. Although these default values are the most common, if you want to send a different kind of response (e.g. an error or non-HTML content) it's necessary to alter these values.

Overriding HTTP Status and Content-Type header values for any of the three options in listing 2-24 is as simple as providing the additional arguments status and/or content_type. Listing 2-25 illustrates various examples of this process.

Listing 2-25 HTTP Content-type and HTTP Status for Django view method responses

from django.shortcuts import render

# No method body(s) and only render() example provided for simplicity  
# Returns content type text/plain, with default HTTP 200
return render(request,'stores/menu.csv', context, content_type='text/plain')

# Returns HTTP 404, wtih default text/html 
# NOTE: Django has a built-in shortcut & template 404 response, described in the next section
return render(request,'custom/notfound.html',status=404)

# Returns HTTP 500, wtih default text/html 
# NOTE: Django has a built-in shortcut & template 500 response, described in the next section
return render(request,'custom/internalerror.html',status=500)

# Returns content type application/json, with default HTTP 200
# NOTE: Django has a built-in shortcut JSON response, described in the next section
return render(request,'stores/menu.json', context, content_type='application/json')

The first example in listing 2-25 is designed to return a response with plain text content. Notice the render method content_type argument. The second and third examples in listing 2-25 set the HTTP Status code to 404 and 500. Because the HTTP Status 404 code is used for resources that are not found, the render method uses a special template for this purpose. Similarly, because the HTTP Status 500 code is used to indicate an error, the render method also uses a special template for this purpose.

The fourth and last example in listing 2-25 is designed to return a response with JavaScript Object Notation(JSON) content. The HTTP Content-Type application/json is a common requirement for requests made by browsers that consume JavaScript data via Asynchronous JavaScript (AJAX).

Tip Django has built-in shortcuts and templates to deal with HTTP Status codes 404 and 500, as well as a JSON short-cut response, all of which are described in the next section and that you can use instead of the examples in listing 2-25.

Built-in response shortcuts and templates for common HTTP Status: 400 (Bad Request), 403 (Forbidden), 404 (Not Found) & 500 (Internal Server Error)

Although you'll commonly use the response syntax samples in listings 2-24, they can be verbose when all you're trying to do is return a quick response without the need to create/reference a template or pass custom values to a response. For example, you can make evaluations in a Django view like if article_id < 100: or if unpayed_subscription: and based on the result respond with a one-liner HTTP 404 Not Found response or HTTP 403 Forbidden response.

Table 2-4 illustrates a series of shortcuts to trigger the most common HTTP status responses.

Table 2-4 Django shortcut exceptions to trigger HTTP statuses

HTTP status code Python code sample
400 (Bad Request)
from django.core.exceptions import BadRequest

raise BadRequest
403 (Forbidden)
from django.core.exceptions import PermissionDenied

raise PermissionDenied
404 (Not Found)
from django.http import Http404

raise Http404
500 (Internal Server Error)
raise Exception

As you can see in the examples in table 2-4, the shortcut syntax is straightforward. For example, for a Django view clause like if article_id < 100: you can do raise Http404 or for a Django view clause like if unpayed_subscription: you can do raise PermissionDenied.

Tip Django automatically handles not found pages raising HTTP 404 and unhandled exceptions raising HTTP 500, so you don't need to explicilty add raise Http404 or raise Exception everywhere.

So what is the actual content sent in a response besides the HTTP status when an exception from table 2-4 is triggered ? The default for HTTP 403 (Forbidden) is a single line HTML page that says "403 Forbidden". For 400 (Bad Request), HTTP 404 (Not Found) and HTTP 500 (Internal Server Error), it depends on the DEBUG value in settings.py.

If a Django project has DEBUG=True in settings.py: HTTP (400) (Bad Request) generates a page with the message added to BadRequest (e.g.raise BadRequest('Invalid hours value')) as well as a traceback report -- as illustrated in figure 2-1; HTTP 404 (Not Found) generates a page with valid urls, hinting the user on what's available -- as illustrated in figure 2-2; and HTTP 500 (Internal Server Error) generates a page with a traceaback report associated with the Exception -- as illustrated in figure 2-3.

If a Django project has DEBUG=False in settings.py: HTTP 400 (Bad Request) generates a single line HTML page that says "Bad Request (400)"; HTTP 404 (Not Found) generates a single line HTML page that says "Not Found. The requested resource was not found on this server."; and HTTP 500 (Internal Server Error) generates a single line HTML page that says "Server Error (500)".

Note To learn about the full implications of changing DEBUG in settings.py, see Django settings.py for the real world.

Figure 2-1. HTTP 400 for Django project when DEBUG=True


Figure 2.2- HTTP 404 for Django project when DEBUG=True


Figure 2.3- HTTP 500 for Django project when DEBUG=True


It's also possible to override the default response page for all the previous HTTP codes -- 400, 403, 404 & 500 with custom templates. To use a custom response page, you need to create a template with the desired HTTP code and .html extension. For example, for HTTP 403 you would create the 403.html template and for HTTP 500 you would create the 500.html template. All these custom HTTP response templates need to be placed in a folder defined in the DIRS list of the TEMPLATES variable so Django finds them before it uses the default HTTP response templates.

Caution Custom 400.html, 404.html and 500.html pages only work when DEBUG=False

If DEBUG=True, it doesn't matter if you have 400.html, 404.html or 500.html templates in the right location, Django uses the default response behavior illustrated figure 2-1, figure 2-2 and figure 2-3, respectively. You need to set DEBUG=False for the custom 400.html, 404.html and 500.html templates to work. When a custom 403.html is detected it will always be used, irrespective of the DEBUG value.

On certain occasions, using custom HTTP response templates may not be enough. For example, if you want to add context data to a custom template that handles an HTTP response, you need to customize the built-in Django HTTP view methods themselves, because there's no other way to pass data into this type of template. To customize the built-in Django HTTP view methods you need to declare special handlers in a project's urls.py file. Listing 2-26 illustrates the urls.py file with custom handlers for Django's built-in HTTP Status view methods.

Listing 2-26. Override built-in Django HTTP Status view methods in urls.py

# Contents coffeehouse/urls.py

# Overrides the default 400 handler django.views.defaults.bad_request
handler400 = 'coffeehouse.utils.views.bad_request'
# Overrides the default 403 handler django.views.defaults.permission_denied
handler403 = 'coffeehouse.utils.views.permission_denied'
# Overrides the default 404 handler django.views.defaults.page_not_found
handler404 = 'coffeehouse.utils.views.page_not_found'
# Overrides the default 500 handler django.views.defaults.server_error
handler500 = 'coffeehouse.utils.views.server_error'

urlpatterns = [....
]
Caution If DEBUG=True, the handler400, handler404 and handler500 handlers won't work, Django keeps using the built-in Django HTTP view methods. You need to set DEBUG=False for the handler400, handler404 and handler500 handlers to work.

As you can see in listing 2-26, there are a series of variables in urls.py right above the standard urlpatterns variable. Each variable in listing 2-26 represents an HTTP Status handler, with its value corresponding to a custom Django view to process requests. For example, handler400 indicates that all HTTP 400 requests should be handled by the Django view method coffeehouse.utils.views.bad_request instead of the default django.views.defaults.bad_request. The same approach is taken for HTTP 403 requests using handler403, HTTP 404 requests using handler404 and HTTP 500 requests using handler500.

As far as the actual structure of custom Django view methods is concerned, they are identical to any other Django view method. Listing 2-27 shows the structure of the custom view methods used in listing 2-26.

Listing 2-27. Custom views to override built-in Django HTTP view methods

# Contents coffeehouse/utils/views.py

from django.shortcuts import render

def bad_request(request):
    # Dict to pass to template, data could come from DB query
    context = {'view':'bad_request'}
    return render(request,'400.html', context, status=400)

def permission_denied(request):
    # Dict to pass to template, data could come from DB query
    context = {'view':'permission_denied'}
    return render(request,'403.html', context, status=403)

def page_not_found(request):
    # Dict to pass to template, data could come from DB query
    context = {'view':'page_not_found'}
    return render(request,'404.html', context, status=404)

def server_error(request):
    # Dict to pass to template, data could come from DB query
    context = {'view':'server_error'}    
    return render(request,'500.html', context, status=500)

As you can see in listing 2-27, the custom HTTP view methods use the same render method from django.shortcuts as previous view method examples. The methods point to a template named by the HTTP Status code, use a custom data dictionary that becomes accessible on the template and use the status argument to indicate the HTTP status code.

Other built-in response shortcuts for in-line & streamed content

In addition to the HTTP responses presented in the last section in table 2-4, there are other Django HTTP shortcut responses for less commonly used HTTP codes. Table 2-5 illustrates the different shortcuts to trigger HTTP redirects: HTTP 301 (Permanent Redirect) or HTTP 302 (Redirect), where the response just requires a redirection url.

Table 2-5 Django shortcuts for HTTP redirects

HTTP status code Python code sample
301
(Permanent Redirect)
from django.http import HttpResponsePermanentRedirect

return HttpResponsePermanentRedirect("/")
302
(Redirect)
from django.http import HttpResponseRedirect

return HttpResponseRedirect("/")

Both samples in table 2-5 redirect to an application's home page (i.e."/"). However, you can also set the redirection to any application url or even a full url on a different domain (e.g.http://maps.google.com/).

In addition to response redirection shortcuts, Django also offers a series of response shortcuts where you can add in-line responses. Table 2-6 illustrates the various other shortcuts for HTTP status codes with in-line content responses.

Table 2-6 Django shortcuts for in-line and streaming content responses

Purpose or HTTP Status code Python code sample
304
(NOT MODIFIED)
from django.http import HttpResponseNotModified

return HttpResponseNotModified()*
400
(BAD REQUEST)
from django.http import HttpResponseBadRequest

return HttpResponseBadRequest("<h4>The request doesn't look
right</h4>")
404
(NOT FOUND)
from django.http import HttpResponseNotFound

return HttpResponseNotFound("<h4>Ups, we can't find that
page</h4>")
403
(FORBIDDEN)
from django.http import HttpResponseForbidden

return HttpResponseForbidden("Can't look at anything
here",content_type="text/plain")
405
(METHOD NOT ALLOWED)
from django.http import HttpResponseNotAllowed

return HttpResponseNotAllowed("<h4>Method not allowed</h4>")
410
(GONE)
from django.http import HttpResponseGone

return HttpResponseGone("No longer here",content_type="text/plain")
500
(INTERNAL SERVER ERROR)
from django.http import HttpResponseServerError

return HttpResponseServerError("<h4>Ups, that's a mistake on our
part, sorry!</h4>")
JSON
(In-line response that serializes data to JSON, defaults to HTTP 200 and content type application/json)
from django.http import JsonResponse

data_dict = {'name':'Downtown','address':'Main #385','city':'San
Diego','state':'CA'}
return JsonResponse(data_dict)
Streaming
(In-line response that stream data, defaults to HTTP 200 and streaming content which is an iterator of strings)
from django.http import StreamingHttpResponse

return StreamingHttpResponse(large_data_structure)
Files
(In-line response that stream binary files, defaults to HTTP 200 and streaming content)
from django.http import FileResponse

return FileResponse(open('Report.pdf','rb'))
Generic - All purpose
(In-line response with any HTTP status code, defaults to HTTP 200)
from django.http import HttpResponse

return HttpResponse("<h4>Django in-line response</h4>")

* The HTTP 304 status code indicates a 'Not Modified' response, so you can't send content in the response, it should always be empty.

As you can see in the samples in table 2-6, there are multiple shortcuts to generate different HTTP Status responses with in-line content and entirely forgo the need to use a template. In addition, you can see the shortcuts in table 2-6 can also accept the content_type argument if the content is something other than HTML (i.e. content_type=text/html).

Since non-HTML responses have become quite common in web applications, you can see table 2-6 also shows three Django built-in response shortcuts to output non-HTML content. The JsonResponse class is used to transform an in-line response into JavaScript Object Notation (JSON). Because this response converts the payload to a JSON data structure, it automatically sets the content type to application/json. The StreamingHttpResponse class is designed to stream a response without the need to have the entire payload in-memory, a scenario that's helpful for large payload responses. The FileResponse class -- a subclass of StreamingHttpResponse -- is designed to stream binary data (e.g. PDF or image files).

This takes us to the last entry in table 2-6, the HttpResponse class. As it turns out, all the shortcuts in table 2-6 are customized subclasses of the HttpResponse class, which I initially described in listing 2-24 as one of the most flexible techniques to create view responses.

The HttpResponse method is helpful to create responses for HTTP status codes that don't have direct shortcut methods (e.g. HTTP 408 [Request Timeout], HTTP 429 [Too Many Requests]) or to inclusively harness a template to generate in-line responses.

Listing 2-28 illustrates an even more elaborate use case for HttpResponse than the one in listing 2-24, to respond with a custom CSV file generated from an in-line Django template.

Listing 2-28. HttpResponse with template and custom CSV file download

from django.http import HttpResponse
from django.template import Context, Template
from django.utils import timezone


def menu(request, store_id=1):
    # Create inline CSV template
    csv_inline_template = Template("type, name\n{% for type, items in menu.items %}{% for item in items %}{{type}},{{item}}\n{% endfor %}{% endfor %}")
    # Define menu as dictionary to fill template
    context = {'menu':
               {'drinks': ['Espresso','Latte','Mocha'],
                'foods': ['Grilled cheese','Turkey Sandwich','Whole-Grain Oatmeal']
               }
              }
    # Prepare response as CSV
    response = HttpResponse(content_type='text/csv')
    # Add HTTP header so response is returned in file attachment with custom name
    response['Content-Disposition'] = f'attachment; filename=Menu_Store_{store_id}_Date_{timezone.now().today()}.csv'
    # Render template with context and write to response
    response.write(csv_inline_template.render(Context(context)))
    return response

Listing 2-28 starts by generating an in-line Django template through django.template.Template vs. reading a template from a template file. Next, a context dictionary is declared with data to fill the CSV boilerplate in-line template. Then the HTTPResponse object in is generated with a text/csv content type to advise the requesting party (e.g. browser) that it's about to receive CSV content. Next, the Content-Disposition header also tells the requesting party (e.g. browser) to attempt to download the content as a file named Menu_Store_{store_id}_Date_{timezone.now().today()}.csv where the store_id is substituted with the store_id provided in the request and timezone.now().today() is substituted with the current server date.

Next, using the render method of the csv_inline_template, we create a Context object to hold the data that will fill in the template's data placeholders. Finally, the rendered template is written to the response object via the write method and the response object is returned.

Finally, as a closing remark it's worth point out the HttpResponse class offers over twenty options between attributes and methods[5] to customize Django responses like it's shown in listing 2-28.

  1. https://docs.djangoproject.com/en/3.2/ref/request-response/#django.http.HttpResponse