In the previous Understand Django article, I covered URLs and the variety of tools that Django gives us to describe the outside interface to the internet for your project. In this article, we'll examine the core building block that makes those URLs work: the Django view.
What Is A View?
A view is a chunk of code that receives an HTTP request and returns an HTTP response. Views describe Django's entire purpose: to respond to requests made to an application on the internet.
You might notice that I'm a bit vague about "chunk of code." That was deliberate. The reason is because views come in multiple forms. To call views functions would only be part of the story. To call them classes would be a different chapter in the story.
Even if I attempted to call views callables, I still would not portray them accurately because of the ways that certain types of views get plugged into a Django app.
Let's start with functions since I think they are the gentlest introduction to views.
Function Views
A function view is exactly that, a function. The function takes an instance of HttpRequest
as input and returns an HttpResponse
(or one of its many subclasses) as output.
The classic "Hello World" example would look like what is listed below.
# application/views.py
from django.http import HttpResponse
def hello_world(request):
return HttpResponse('Hello World')
If you added that view to a URL configuration which we learned about in the last article, then you could visit a browser at the URL and find the text "Hello World" on your browser page.
Maybe you don't find that very exciting, but I do, and I think you should! The framework did so much work for us, and our job is to write a mere couple of lines of Python. When plugged into a web server on the internet, your greeting can reach anyone with access to the net. That's staggering and is worth reflecting on.
Django does most of the heavy lifting for us. The raw HTTP request fits neatly into the HttpRequest
class. Our example view doesn't make any use of that information, but it's accessible if we need it. Likewise, we're not using much of HttpResponse
, but it's doing all the work to make sure it can appear on a user's browser and deliver our message.
To see what we can do with views, let's look closely at HttpRequest
and HttpResponse
to get a glimpse and what's going on.
HttpRequest
HttpRequest
is a Python class. Instances of this class represent an HTTP request. HTTP is the transfer protocol that the internet uses to exchange information. A request can be in a variety of formats, but a standard request might look like:
POST /courses/0371addf-88f7-49e4-ac4d-3d50bb39c33a/edit/ HTTP/1.1
Host: 0.0.0.0:5000
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 155
Origin: http://0.0.0.0:5000
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache
name=Science
&monday=on
&tuesday=on
&wednesday=on
&thursday=on
&friday=on
This example is from a side project that uses school data. I have trimmed some lines out of the request so it will fit better on the screen, and I did some slight reformatting to make the content a bit clearer.
When Django receives a request like this, it will parse the data and store the data in an HttpRequest
instance. The request provides convenient access to all parts of the raw data with helpful attributes for the most commonly used parameters. Considering the example, the request would have:
-
method
- This matches the HTTP method ofPOST
and can be used to act on the kind of request the user sent. -
content_type
- This attribute instructs Django on how to handle the data in the request. The example value would beapplication/x-www-form-urlencoded
to indicate that this is user submitted form data. -
POST
- For POST requests, Django processes the form data and stores the data into a dictionary-like structure.request.POST['name']
would beScience
in our example. -
GET
- For a GET request, anything added to the query string (i.e., the content after a?
character such asstudent=Matt
in/courses/?student=Matt
is stored in a dictionary-like attribute as well. -
headers
- This is where all the HTTP headers likeHost
,Accept-Language
, and the others are stored.
Other attributes are available to HttpRequest
, but that list will get you far to get started.
I should also note that HttpRequest
instances are a common place to attach extra data. Django requests pass through many pieces in the framework. This makes the objects great candidates for extra features that you may require. For instance, if you need user management (which we will explore in a future article), then there is code that can attach a request.user
attribute to represent a user in your system. It's very handy.
I like to think of HttpRequest
objects as the common interface for most of the inputs that my code uses.
HttpResponse
The other major interface that your views will use either directly or indirectly is the HttpResponse
interface.
Your job as a Django user is to make your views return some kind of HttpResponse
. A response instance will include all the necessary information to create a valid HTTP response for a user's browser.
Some of the common HttpResponse
attributes include:
-
status_code
- This is the HTTP status code. Status codes are a set of numbers that HTTP defines to tell a client (e.g., a browser) about the success or failure of a request.200
is the usual success code. Any number from400
and up will indicate some error, like404
when something is not found. -
content
- This is the content that you provide to the user. The response stores this data as bytes. If you supply Python string data, Django will encode to bytes for you.
>>> from django.http import HttpResponse
>>> response = HttpResponse('Hello World')
>>> response.content
b'Hello World'
When working with Django views, you won't always use HttpResponse
directly. HttpResponse
has a variety of subclasses for common uses. Let's look at some:
-
HttpResponseRedirect
- You may want to send a user to a different page. Perhaps the user bought something on your site, and you would like them to see a receipt page of their order. This subclass is perfect for that scenario. -
HttpResponseNotFound
- This is the subclass used to create a404 Not Found
response. Django provides some helper functions to return this so you may not use this subclass directly, but it's good to know it's available. -
HttpResponseForbidden
- This type of response happens when you don't want a user to access a part of your website. -
JsonResponse
- I haven't focused on JSON yet in this series, but it's a data format which matches closely to Python native data types and can be used to communicate with JavaScript.
>>> from django.http import JsonResponse
>>> response = JsonResponse({'hello': 'world'})
>>> response.content
b'{"hello": "world"}'
Aside from the subclasses, Django has other techniques to return HttpResponse
instances without creating one yourself. The most common function is render
.
render
is a tool for working with templates. Templates are the topic of the next article, but here is a sneak peek.
You could write a view for a webpage and include a lot of HTML in your Python. HTML is the markup language of internet pages that we use to describe the format of a page.
This view might look like:
from django.http import HttpResponse
def my_html_view(request):
response_content = """
<html>
<head><title>Hello World!</title>
<body>
<h1>This is a demo.</h1>
</body>
</html>
"""
return HttpResponse(response_content)
While this works, it has many shortcomings.
- The HTML chunk isn't reusable by other views. That doesn't matter much for this small example, but it would be a huge problem when you try to make many views that use a lot of markup and need to share a common look.
- The mixing of Python and HTML is going to get messy. Need proof? Go look at computing history and learn about CGI. It wasn't pretty.
- How can you join pieces of HTML together? Not easily.
With templates, we can separate the layout from the logic.
# application/views.py
from django.shortcuts import render
def my_html_view(request):
return render(request, "template.html", {})
And we would have another file named template.html
containing:
<html>
<head><title>Hello World!</title>
<body>
<h1>This is a demo.</h1>
</body>
</html>
The important part for this article is not about templates themselves. What's worth noting is that render
returns an HttpReponse
instance that will contain the rendered template as the content.
That wraps up HttpRequest
and HttpResponse
. With those building blocks, we can now look at other ways that you can make Django views for your project.
View Classes
By now we've seen this relationship with views:
HttpRequest -> view -> HttpResponse
Views do not need to be functions exclusively. Django also provides tools to make views out of classes. These types of views derive from Django's View
class.
When you write a class-based view (often abbreviated to CBVs), you add instance methods that match up with HTTP methods. Let's see an example:
# application/views.py
from django.http import HttpResponse
from django.views.generic.base import View
class SampleView(View):
def get(self, request, *args, **kwargs):
return HttpResponse("Hello from a CBV!")
The get
method on the class corresponds to a GET
HTTP request. Similarly, you would write a post
method to respond to a POST
HTTP request and so on. With that view defined, we can connect it to a URLconf:
# project/urls.py
from django.urls import path
from application.views import SampleView
urlpatterns = [
path("", SampleView.as_view()),
]
Note that we don't pass SampleView
to path
as is. path
expects a callable object so we must call as_view
which is a class method that returns a function that will call the code in our class.
At this point, I would be suitably unimpressed if I were in your shoes. Why would we add all of this boilerplate code when you can make a function and be done? If this were the full story, I would absolutely agree with you. This doesn't add much beyond the function-based version. If anything, it has more to remember so it's probably more confusing.
Where class-based views begin to shine is when using some other classes beyond the initial View
class.
Django includes a host of class-based views to use for a variety of purposes. We can explore a few of them with our limited exposure to the full framework so far.
Out Of The Box Views
I won't exhaustively cover all the class-based views because there are a lot of them. Also, if you're joining this article series from the beginning and have never done Django before, then there will still be holes in your knowledge (which we will plug together!), and some of the views will not make much sense.
RedirectView
This is a view to use when you want to send users of your site to a different place. You could make a view that returns an HttpResponseRedirect
instance, but this class-based view can handle that for you.
In fact, you can use RedirectView
without subclassing it. Check this out:
# project/urls.py
from django.urls import path
from django.views.generic.base import RedirectView
from application.views import NewView
urlpatterns = [
path("old-view-path/",
RedirectView.as_view(url="https://www.somewhereelse.com")),
path("other-old-path/", RedirectView.as_view(pattern_name='new-view')),
path("new-path/", NewView.as_view(), name='new-view'),
]
RedirectView
can use url
for a full URL or it can use pattern_name
if you need to route to a view that moved somewhere else in your project.
as_view
is what let's us avoid subclassing RedirectView
. The arguments passed to as_view
override any class attributes. The following two RedirectView
uses are equivalent:
# project/urls.py
from django.urls import path
from django.views.generic.base import RedirectView
from application.views import NewView
class SubclassedRedirectView(RedirectView):
pattern_name = 'new-view'
urlpatterns = [
path("old-path/", SubclassedRedirectView.as_view()),
path("old-path/", RedirectView.as_view(pattern_name='new-view')),
path("new-path/", NewView.as_view(), name='new-view'),
]
TemplateView
Earlier in the article, we saw briefly how we can separate web page layout from the logic needed to build a page with templates.
Templates are so commonly used that Django provides a class that knows how to produce a proper response with nothing more than a template name.
An example looks like:
# application/views.py
from django.views.generic.base import TemplateView
class HomeView(TemplateView):
template_name = 'home.html'
We will look at template views in greater detail in the next article when we dive into templates.
Other View Classes
Django's other class-based views serve a variety of purposes. Django has views that will:
- Display and handle HTML forms so users can input data and send the data to the application.
- Pull data from a database and show an individual record to the user (e.g., a webpage to see facts about an individual movie).
- Pull data from a database and show information from a collection of records to the user (e.g., showing the cast of actors from a movie).
- Allow a user to create or update data that will be saved to a database.
- Show data from specific time ranges like days, weeks, and months.
As we continue to explore Django, I will discuss these views when their related topic (like forms) is the primary subject of an article. For now, when you're developing your own views, try to remember that Django probably has a class-based view to aid your work.
Useful View Decorators And Mixins
Before we finish the tour of views, let's discuss some useful decorators and mixin classes.
Decorators are a feature of Python (and many other languages) that let you extend a function with additional capabilities. A decorator can wrap a view function to provide new behavior to a view. This is useful when you have common functionality that you want to add to many views without copying and pasting a lot of code around.
Mixin classes serves a very similar purpose as decorators, but behave with Python's multiple inheritance feature of classes to "mix in" the new behavior with an existing class-based view.
Decorators To Know
When you work with function-based views, there is a challenge when handling different HTTP methods. Some views will handle multiple methods like:
# application/views.py
from django.http import HttpResponse
def multi_method_view(request):
if request.method == 'GET':
return HttpResponse('Method was a GET.')
elif request.method == 'POST':
return HttpResponse('Method was a POST.')
This view uses the request
instance method
attribute to check the request's HTTP method. What if you only want your view to respond to one HTTP method? Let's say you only want to respond to a POST. We could write:
# application/views.py
from django.http import Http404, HttpResponse
def guard_clause_view(request):
if request.method != 'POST':
raise Http404()
return HttpResponse('Method was a POST.')
# OR
def if_clause_view(request):
if request.method == 'POST':
return HttpResponse('Method was a POST.')
else:
raise Http404()
Both of these techniques work, but the code is a little messier because of the extra indentation. Instead, we can use the require_POST
decorator and let Django check the method for us.
# application/views.py
from django.http import HttpResponse
from django.view.decorators.http import require_POST
@require_POST
def the_view(request):
return HttpResponse('Method was a POST.')
This version states the expectation up front with the decorator and declares the contract that the view will work with.
Another common decorator you may encounter is the login_required
decorator. When we get to the subject of user management, you'll see that we can make a protected view for an app by including this decorator.
# application/views.py
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
@login_required
def the_view(request):
return HttpResponse('This view is only viewable to authenticated users.')
A final example of a useful built-in decorator is user_passes_test
. This is another decorator used with the user management system that let's us control which users should be allowed to access a view. For instance, we could make a view that only staff-level users could access.
# application/views.py
from django.contrib.auth.decorators import user_passes_test
from django.http import HttpResponse
@user_passes_test(lambda user: user.is_staff)
def the_view(request):
return HttpResponse('Only visible to staff users.')
The decorator takes a callable that will accept a single argument of a user object. The view will only be accessible if the return value of the test callable evaluates to True
.
What I'm trying to show with these examples is how single decorators can quickly augment your views with new features. And, because of how decorators work to wrap functions, you can "stack" these together.
# application/views.py
from django.contrib.auth.decorators import user_passes_test
from django.http import HttpResponse
from django.view.decorators.http import require_POST
@require_POST
@user_passes_test(lambda user: user.is_staff)
def the_view(request):
return HttpResponse('Only staff users may POST to this view.')
Mixins To Know
Mixin classes are to class-based views as decorators are to function-based views. This isn't completely true since class-based views can also use decorators, but it should give you an idea of where mixins fit.
Like the login_required
and user_passes_test
decorators, we have mixin equivalents of LoginRequiredMixin
and UserPassesTestMixin
. Maybe you have some template views that should only be accessible to authenticated users or staff-level users. Those views could look like:
# application/views.py
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.views.generic.base import TemplateView
class HomeView(LoginRequiredMixin, TemplateView):
template_name = 'home.html'
class StaffProtectedView(UserPassesTestMixin, TemplateView):
template_name = 'staff_eyes_only.html'
def test_func(self):
return self.request.user.is_staff
You can see that these views are similar to their decorator counterparts with a slightly different usage pattern.
One thing worth noting with mixins is their placement. Because of the way that Python handles multiple inheritance, you should be sure to include mixin classes to the left in the list of classes. This will ensure that Python will behave appropriately with these classes.
There are plenty of other mixin classes. In fact, most of Django's built-in class-based views are constructed by composing various mixin classes together. If you'd like to see how they are constructed, check out Classy Class-Based Views which is a site showing the built-in CBVs along with the mixins and attributes available to those classes.
Summary
That's a wrap on view fundamentals. We've looked at:
- View functions
-
HttpRequest
andHttpResponse
- View classes
- Some built-in supporting views
- Decorators and mixins that supercharge views.
In the next article, we'll see how views can mix static layout with the dynamic data we provide by using templates. Templates are the workhorse for your Django-based user interfaces. We're going to see:
- How to set up templates for your site
- Ways to call templates from views
- How to use data
- How to handle logic
- Built-in functions available to templates
- Customizing templates with your own code extensions
If you'd like to follow along with the series, please feel free to sign up for my newsletter where I announce all of my new content. If you have other questions, you can reach me online on Twitter where I am @mblayman.
This article first appeared on mattlayman.com.
Top comments (0)