Views On Views

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.

  1. From Browser To Django
  2. URLs Lead The Way
  3. Views On Views
  4. Templates For User Interfaces
  5. User Interaction With Forms
  6. Store Data With Models
  7. Administer All The Things
  8. Anatomy Of An Application
  9. User Authentication
  10. Middleware Do You Go?
  11. Serving Static Files
  12. Test Your Apps
  13. Deploy A Site Live
  14. Per-visitor Data With Sessions
  15. Making Sense Of Settings
  16. User File Use
  17. Command Your App
  18. Go Fast With Django
  19. Security And Django
  20. Debugging Tips And Techniques
Understand Django is available as a paperback or e-book for $29.99.
For those who are not financially able, this content will always remain free on this website.
If you would like to own a copy for yourself or would like to support my work, please consider purchasing the book format.
Buy Understand Django Now

What Is A View?

A view is a chunk of code that receives an HTTP request and returns an HTTP response. Views are where you use Django’s core functionality: 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 that views come in multiple forms. To say views are functions would be part of the story. Later chapters in that story cover how they can also be implemented in classes.

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. For instance, a view based on a class will produce a callable as we’ll see in a later section.

Let’s start with functions since I think they are the gentlest introduction to views.

Function Views

A function view is precisely that, a function. The function takes an HttpRequest instance 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')

Adding the hello_world view to a URL configuration which we learned about in the last article, 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 use that information, but it’s accessible if we need it. Likewise, we’re not using much of HttpResponse. Still, it’s doing all the work to ensure it appears on a user’s browser and delivers our message.

To see what we can do with views, let’s look closely at HttpRequest and HttpResponse to get a glimpse at 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 it 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. When considering the example, the request would have:

  • method - This matches the HTTP method of POST 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 be application/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 be Science in our example.
  • GET - Anything added to the query string (i.e., the content after a ? character such as student=Matt in /courses/?student=Matt) is stored in a dictionary-like attribute as well.
  • headers - This is where all the HTTP headers like Host, Accept-Language, and the others are stored. headers is also dictionary-like and can be accessed like request.headers['Host'].

Other attributes are available to HttpRequest, but that list will get you far enough to get started. Check out Request and response objects for the other attributes.

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), there is code that can attach a request.user attribute to represent a user in your system. It’s very handy.

You can 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 from 400 and up will indicate some error, like 404 when a requested resource 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 it 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 a 404 Not Found response. Django provides some helper functions to return this so you might not use this subclass directly, but it’s good to know it’s available.
  • HttpResponseForbidden - This type of response can be used when you don’t want a user to access a part of your website (i.e., HTTP status 403 Forbidden).

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.

  1. 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.
  2. 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.
  3. 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 the templates themselves. What’s worth noting is that render loads the content from template.html, gets the output, and adds that output to an HttpResponse instance.

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. *args and **kwargs are a common convention in Python to make a method or function that accepts any number of positional or keyword based arguments. We need these to match the expect method signature that Django requires for CBVs. 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, 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 this boilerplate code when you can make a function and be done? If this were the full story, I would absolutely agree with you. A class-based view doesn’t add much beyond the function-based version. If anything, CBVs have more to remember, so they are 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 many. 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

Use RedirectView 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 lets 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()),
    # The RedirectView below acts like SubclassedRedirectView.
    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 briefly saw how to 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 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 a particular 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).
  • Show data from specific time ranges like days, weeks, and months.

As we continue to explore Django, We 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. Decorators are helpful when you have common functionality that you want to add to many views without copying and pasting a lot of code.

Mixin classes serve a very similar purpose as decorators, but use 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. By default, a function based view can receive requests from any HTTP method. Some views will handle multiple methods like:

# application/views.py
from django.http import (
    HttpResponse,
    HttpResponseNotAllowed,
)

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.')
    return HttpResponseNotAllowed()

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 (
    HttpResponse,
    HttpResponseNotAllowed,
)

def guard_clause_view(request):
    if request.method != 'POST':
        return HttpResponseNotAllowed()

    return HttpResponse('Method was a POST.')

# OR

def if_clause_view(request):
    if request.method == 'POST':
        return HttpResponse('Method was a POST.')
    else:
        return HttpResponseNotAllowed()

Both 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. If a user tries a different method (like a GET), then Django will respond with HTTP status code 405, which is an error code for “method not allowed.”

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.')

Any unauthenticated user will be redirected automatically to the login page for your web app.

A final example of a useful built-in decorator is user_passes_test. This is another decorator used with the user management system that lets 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 inherited base classes. This will ensure that Python will behave appropriately with these classes. The exact reason for this placement is because of Python’s method resolution order (MRO) rules when using multiple inheritance. MRO is outside of our scope, but that’s what you can search for if you want to learn more.

There are plenty of other mixin classes. 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, a site showing the built-in CBVs and the mixins and attributes available to those classes.

Summary

That’s a wrap on view fundamentals. We’ve looked at:

  • View functions
  • HttpRequest and HttpResponse
  • 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 X where I am @mblayman.  

Translations