Set up HTTP handling in Django view methods: HTTP errors, redirects, content types and status codes

Problem

You want to use HTTP handling directly in Django views to output HTTP error pages, HTTP redirects, HTTP content types (e.g. text/plain, text/xml) and HTTP status codes ('HTTP 200 for "OK"','HTTP 404 for "NOT FOUND"').

Solution

Use the status and content_type arguments with the django.shortcuts.render method to specify an HTTP status code and HTTP content type, respectively. To trigger an HTTP 500 (INTERAL SERVER ERROR) response do raise Exception in a view. To trigger an HTTP 404 (NOT FOUND) response do raise django.http.Http404. To trigger an HTTP 400 (BAD REQUEST) response do raise django.core.exceptions.SuspiciousOperation. To trigger an HTTP 403 (FORBIDDEN) response do raise django.core.exceptions.PermissionDenied.

You can customize the pages for HTTP 404, HTTP 500, HTTP 400 and HTTP 403 responses creating templates called 404.html, 500.html, 400.html and 403.html, respectively. Custom templates must be placed in a folder defined in the DIRS list of the TEMPLATES variable. You can also override the built-in view methods for HTTP 404, HTTP 500, HTTP 400 and HTTP 403 responses defining custom view methods in the variables handler404, handler500, handler400 and handler403. The variables for custom view methods must be defined in urls.py.

To do an HTTP redirect you can use django.http.HttpResponsePermanentRedirect() or django.http.HttpResponseRedirect(). To trigger an HTTP 304 (NOT MODIFIED) response use django.http.HttpResponseNotModified(). To trigger an HTTP 400 (BAD REQUEST) response with in-line content use django.http.HttpResponseBadRequest(). To trigger an HTTP 404 (NOT FOUND) response with in-line content use django.http.HttpResponseNotFound(). To trigger an HTTP 403 (FORBIDDEN) response with in-line content use django.http.HttpResponseForbidden(). To trigger an HTTP 405 (METHOD NOT ALLOWED) response with in-line content use django.http.HttpResponseNotAllowed(). To trigger an HTTP 410 (GONE) response with in-line content use django.http.HttpResponseGone(). To trigger an HTTP 500 (INTERNAL SERVER ERROR) response with in-line content use django.http.HttpResponseServerError().

To trigger a response with in-line content using any HTTP status code and HTTP content type use the generic method django.http.HttpResponse(). To trigger a response with in-line content serialized as JSON use django.http.JsonResponse().

How it works

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 Status HTTP 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 Content-Type HTTP 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 exception (i.e. an error), all Django view methods that use django.shortcuts.render to return a response are set with the HTTP Status value of 200 and the HTTP Content-Type of text/html. Although these default values are the most common, sometimes it's necessary to alter these values.

Set HTTP Content-type and HTTP Status code in Django django.shortcuts.render

Adding HTTP handling to the render method in django.shortcuts is as simple as providing the additional arguments status and/or content_type. Listing 1 illustrates various examples of this process.

Listing 1 - django.shortcuts.render with modified HTTP Content-type and HTTP Status

from django.shortcuts import render

# No method body(s) provided for simplicity, just return values

# Returns content type text/plain, with default HTTP 200
values_for_template = {}
return render(request,'stores/menu.csv', values_for_template, content_type='text/plain')

# Returns HTTP 404, wtih default text/html 
# NOTE: Django has a built-in shortcut & template for HTTP 404 responses, 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 for HTTP 500 responses, described in the next section
return render(request,'custom/internalerror.html',status=500)

# Returns content type application/json, with default HTTP 200
values_for_template = {}
return render(request,'stores/menu.json', values_for_template, content_type='application/json')

The first example in listing 1 is designed to return a response with plain text content. Notice the request arguments with the exception of the content_type are identical to previous recipes: the request object, a template to generate the response and a data dictionary to fill values in the template.

The second and third examples in listing 1 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 uses a special template for this purpose. Note that Django has built-in shortcuts and templates to deal with HTTP Status codes 404 and 500 that are described in the next section and that you can use instead of the examples in listing 1.

The fourth and last example in listing 1 is designed to return a response with JSON content. The HTTP Content-Type application/json is a common requirement when requests are made via AJAX. Finally, it's worth mentioning that can you can specify both the content_type and status arguments if required.

Shortcuts and built-in templates for HTTP Status codes: 404 (Not Found), 500 (Internal Server Error), 400 (Bad Request) and 403 (Forbidden)

Django has special shortcuts and built-in templates for certain HTTP Status codes that are very common. All that's required to use these shortcuts is to trigger an exception in a Django view. Table 1 illustrates the different shortcuts to trigger certain HTTP statuses.

Table 1 - Django shortcut exceptions to trigger HTTP statuses
HTTP status codePython code sample
404 (Not Found)from django.http import Http404
raise Http404
500 (Internal Server Error)raise Exception
400 (Bad Request)from django.core.exceptions import SuspiciousOperation
raise SuspiciousOperation
403 (Forbidden)from django.core.exceptions import PermissionDenied
raise PermissionDenied
Note Django automatically handles not found pages raising HTTP 404 and unhandled exceptions raising HTTP 500

The exceptions in table 1 are meant to be used explicitly in Django views when developers know end users should get them. However, Django automatically triggers an HTTP 404 (Not Found) exception when a page is not found or triggers an HTTP 500 (Internal Server Error) exception when an unhandled exception is thrown in a view.

As you can see in the examples in table 1, the shortcut syntax is straightforward. For example, you can make evaluations in a Django view like if article_id < 100: or if unpayed_subscription: and based on the result throw exceptions from table 1 so end users get the proper HTTP status response.

So what is the actual content sent in a response besides the HTTP status when an exception from table 1 is triggered ? The default for HTTP 400 (Bad Request) and HTTP 403 (Forbidden) is a single line HTML page that says "Bad Request (400)" and "403 Forbidden", respectively. For 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 404 (Not Found) generates a page with the available URLs -- as illustrated in figure 1 -- and HTTP 500 (Internal Server Error) generates a page with the detailed error -- as illustrated in figure 2. If a Django project has DEBUG=False in settings.py, HTTP 404 (Not Found) generates a single line HTML page that says "Not Found. The requested URL <url_location> was not found on this server." and HTTP 500 (Internal Server Error) generates a single line HTML page that says "A server error occurred. Please contact the administrator.".

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

It's also possible to override the default response for all the previous HTTP codes using a custom template. To use a custom response template, 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 responses.

Note Custom 404.html and 500.html pages only work when DEBUG=False

If DEBUG=True, it doesn't matter if you have the 404.html and 500.html templates in the right location, Django keeps using the default response behavior illustrated in figure 1 and figure 2, respectively. You need to set DEBUG=False for the custom 404.html and 500.html templates to work.

On certain occasions, simply customizing the HTTP response template may not be enough. For example, you may need to pass a custom data structure to use in the template. Under these circumstances you need to customize the built-in Django HTTP view methods themselves because there's no other way to get custom data into a template. To customize the built-in Django HTTP view methods you need to use special handlers in urls.py. Listing 2 illustrates the urls.py file with custom handlers for Django's built-in HTTP view methods.

Listing 2 - Override built-in Django HTTP view methods in 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 = [....
]
Note Handlers handler404 and handler500 only work when DEBUG=False

If DEBUG=True, the 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 handler404 and handler500 handlers to work.

As you can see in listing 2, there are a series of variables in urls.py right above the standard urlpatterns variable. Each variable in listing 2 represents an HTTP 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 to the actual structure of the custom Django view methods, they are identical to any other Django view method. Listing 3 show the structure of the custom view methods used in listing 2.

Listing 3 - Custom views to override built-in Django HTTP view methods

from django.shortcuts import render

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

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

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

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

As you can see in listing 3, we use the same render method from django.shortcuts to build the custom HTTP view methods. The methods point to a template named by the HTTP status code, use a custom data dictionary that can be accessible on the template and use the status argument to indicate the HTTP status code.

Shortcuts for other HTTP Status codes and responses that don't require templates

All the previous samples assumed the responses with an HTTP status code had to return content from a template. However, there can be times when using a template to output a response can be unnecessary, for example, you might want a one line response that says "Nothing to see here". Other times it makes no sense for an HTTP status code to use a template, such is the case for HTTP 301 (Permanent Redirect) or HTTP 302 (Redirect) where the response should be a redirection URL.

Table 2 illustrates the different shortcuts to trigger HTTP redirects.

Table 2 - Django shortcuts for HTTP redirects
HTTP status codePython 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 redirect to an application's home page (i.e."/"). However, you could 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 the HTTP redirection shortcuts, Django also offers a series of shortcuts for common HTTP status codes where you can add in-line responses instead of relying on a template. These shortcuts can also use the content_type argument to indicate the type of content in a response. Table 3 illustrates the various other shortcuts for HTTP status codes with in-line content responses.

Table 3 - Django shortcuts for various HTTP status codes with in-line content responses
HTTP status codePython code sample
304 (NOT MODIFIED)from django.http import HttpResponseNotModified
return HttpResponseNotModified()1
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>")
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>")
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)
1The 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 3, you can provide an in-line content response, as well as the content_type argument if the content is something other than text/html.

The HttpResponse method in the second to last line in table 3 is a generic method for in-line content responses. The HttpResponse method can specify any HTTP status code using the status argument, the content type using the content_type argument -- similar to the render method in listing 1 -- or any other HTTP header (e.g.Content-Disposition). 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 inline responses as illustrated in listing 4.

Listing 4 - HttpResponse with template and custom CSV file download

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

response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename=Users_%s.csv' % str(timezone.now().today())
t = loader.get_template('dashboard/users_csvexport.html')                 
c = Context({'users': sorted_users,})                                                  
response.write(t.render(c))                                         
return response

The HTTPResponse object in listing 4 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 Users_%s.csv where the %s is substituted with the current server date.

Next, using the loader module we use the get_template method to load the template users_csvexport.html that will have a CSV like structure with data placeholders. Then we create a Context object to hold the data that will fill the template, in this case it's just a single variable named users. Next, we call the template's render method with the context object in order to fill in the template's data placeholders with the data. Finally, the rendered template is written to the response object via the write method and the response object is returned.

And finally, turning our attention back to table 3 with the Django shortcuts, the JsonResponse method is used to transform an in-line response into a JSON format. Because this method converts the payload to a JSON data structure, it also automatically sets the content_type to application/json.