The Right Way¶
The Pattern¶
This is how you start writing any HTML-based view in Django:
from django.template.response import TemplateResponse
def example_view(request, arg):
return TemplateResponse(request, 'example.html', {})
And the corresponding urls.py:
from django.urls import path
from . import views
urlpatterns = [
path('example/<str:arg>/', views.example_view, name='example_name'),
]
Which bits do you change?
example_view
should be a function name that describes your view e.g.home
orarticle_list
.example.html
should be the path to the template you are using. I’m not going to cover how to write templates anywhere in this guide. Please see the Django tutorial on views and templates, and the Django template language topic.{}
, the third argument toTemplateResponse
, is the context data you want available in your template.arg
is a placeholder for any number of optional URL parameters — parts of the URL path that you are matching with a path converter (here we usedstr
) and supplying to the view function as a parameter. You can remove it, or add more, but have to change the URLconf to match.In
urls.py
, you change the arguments topath
to be, respectively:the matched URL (with any captured parts),
your view function defined above,
and an optional name that needs to be unique across your project, e.g.
home
ormyapp_articles_list
, to enable URL reversing.
That’s it!
But, you need a slightly deeper understanding if you’re going to write good Django views, so this page is quite a bit longer than most, for an explanation.
The Explanation¶
First, it’s vital to know what a view is. As the Django docs state:
A view…is a Python function that takes a Web request and returns a Web response
Given that definition, what does your most basic view function look like? Time for Hello World! In your views.py, you’d have this:
from django.http import HttpResponse
def hello_world(request):
return HttpResponse('Hello world!')
This function expects to receive a ‘request’ object as its parameter. This will
be an instance of HttpRequest,
which contains all the information about the request that the user’s browser
sent. It then returns an HttpResponse
object as its return value. This contains all the data to be sent back to the
user’s browser — HTTP headers and body, which is typically a web page. In our
case, we sent just the text Hello world!
. This request-response cycle is the
heart of the Django web framework.
In order to get Django to actually call our view function, we have to hook it into a “URLconf” somewhere. This is covered in the Django tutorial part 1, so I won’t cover all the app layout stuff in detail — in brief, we’ll have this in our urls.py:
from django.urls import path
from . import views
urlpatterns = [
path('hello/', views.hello_world, name='hello_world'),
]
In many cases, we want a single view function to actually match a family of URLs
which have some kind of parameter in them, and access that parameter in our view
function. Django has built-in support for this. Suppose we want to match URLs
like /hello/XXX/
where XXX
could be any string. Then our URLconf becomes:
urlpatterns = [
path('hello/<str:my_arg>/', views.hello_world, name='hello_world'),
]
and our view signature:
def hello_world(request, my_arg):
# etc
Now, for our classic web app, we are normally serving HTML i.e. web pages.
Further, our HTML normally has bits we want to insert into it — this is a
dynamic web site, not a static one — and we want to build it up in an ordered
way that will handle HTML escaping, and also provide a common set of page
elements (like navigation) for our different pages. So we’ll almost always want
to use Django’s template engine — covered in the Django tutorial part 3.
Instead of passing that "Hello world"
string, we’re going to have a
hello_world.html
template, and pass it some “context data” — any dynamic
information that needs to appear in the page.
So our revised view might look like this:
from django.http import HttpResponse
from django.template import loader
def hello_world(request, my_arg):
template = loader.get_template('hello_world.html')
context = {}
return HttpResponse(template.render(context, request))
Note that a template is not an essential part of a Django view — HTTP requests and responses are the essential parts. Templates are just a way of building up the body of the response. But for this kind of app, they are extremely common. So, as the Django tutorial notes, there is a shortcut for this process of loading a template, rendering it and putting it into a response — render(). With that, we can condense our view to this:
from django.shortcuts import render
def hello_world(request, my_arg):
return render(request, 'hello_world.html', {})
The third parameter here is the empty context dictionary.
This is a great pattern for writing views. Django has one more trick up its sleeve, however — TemplateResponse.
The issue with just using render
is that you get a plain HttpResponse
object back that has no memory that it ever came from a template. Sometimes,
however, it is useful to have functions return a value that does remember what
it’s “made of” — something that stores the template it is from, and the context.
This can be really useful in testing, but also if we want to something outside
of our view function (such as decorators or middleware) to check or even change
what’s in the response before it finally gets ‘rendered’ and sent to the user.
For now, you can just accept that TemplateResponse
is a more useful return
value than a plain HttpResponse
. (If you are already using render
everywhere, there is absolutely no need to go and change it though, and almost
everything in this guide will work exactly the same with render
instead of
TemplateResponse
).
With that substitution, we’ve arrived at the pattern you’ll want to start with for views:
from django.template.response import TemplateResponse
def example_view(request, arg):
return TemplateResponse(request, 'example.html', {})
You need to know what each bit is, as described above. But that is the end of the lesson. You can skip to the next part. Or you can even just stop reading — you now know all the essentials of writing HTML views in Django.
You don’t need to learn any of the CBV APIs - TemplateView
, ListView
,
DetailView
, FormView
, MultipleObjectMixin
etc. etc. and all their
inheritance trees or method flowcharts. They will only make your life harder.
Print out their documentation, put in a shed — or rather, a warehouse given how
much there is — fill the warehouse with dynamite and
don’t look back.
Next up: How to do anything in a view.
Discussion: keep the view viewable!¶
The most important thing about the pattern I’m recommending is that the view itself is visible. The fundamental nature of a view is that it is a function (or callable) that takes a request and returns a response. This is pretty obvious in the pattern above — we have:
a function ✓
that takes an argument called
request
✓and returns some kind of response — a
TemplateResponse
✓
Using CBVs we could rewrite the code as:
from django.views.generic import TemplateView
class ExampleView(TemplateView):
template_name = "example.html"
But now all 3 essential elements of the view have disappeared. Where is the function or callable? Where is the request? Where is the response? To have any idea that this matches the description of what a view is you have to know what the base classes do, and the fundamental simplicity of what you are doing has been obscured.
There may be reasons for doing this, of course, but let’s be aware of this problem and weigh up the advantages and disadvantages.
You might think “this is shorter than the FBV” is one of the advantages. It is, slightly, but as soon as you add the need for context data this advantage disappears, and we’ll find we have more boilerplate, not less, with CBVs.