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 HTTPStatus
codes404
and500
, 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 addraise Http404
orraise 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 changingDEBUG
insettings.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.