Django’s Views - Python Django Tutorials

Django’s Views

Django’s views are the information brokers of a Django application. A view sources data from your database (or an external data source or service) and delivers it to a template. For a web application, the view delivers webpage content and templates; for a RESTful API this content could be formatted JSON data.

The view decides what data gets delivered to the template—either by acting on input from the user or in response to other business logic and internal processes. Each Django view performs a specific function and has an associated template.

Views are represented by either a Python function or a method of a Python class. In the early days of Django, there were only function-based views, but, as Django has grown over the years, Django’s developers added class-based views.

Class-based views add extensibility to Django’s views. Django also has many built-in class-based views that make creating common views (like displaying a list of articles) easier to implement. Don’t worry too much about the differences between function- and class-based views now, we will cover both in more detail later in the book.

We covered the basics of Django’s models in Chapter 4. In this chapter, we will take a detailed look at the basics of Django’s views without getting into any detail on how views interact with models and templates. To help you grasp how views work at a fundamental level, I will show you how they work using simple HTML responses. In the next chapter, we’ll cover the basics of Django’s templates, and we’ll cover class-based views in Chapter 13.

Your First View

To create our first view, we need to modify the views.py file in our events app (changes in bold):

# \myclub_root\events\views.py

1  from django.shortcuts import render
2  from django.http import HttpResponse
3  
4  def index(request):
5      return HttpResponse("<h1>MyClub Event Calendar</h1>")

Let’s examine this code closely:

  • Line 1. Import the render() method. startapp adds this line to the file automatically. render() is used when rendering templates, which we will cover in Chapter 6.
  • Line 2. We import the HttpResponse method. HTTP, the communication protocol used by all web browsers, uses request and response objects to pass data to and from your app and the browser. We need a response object to pass view information back to the browser. We cover request and response objects in Chapter 10.
  • Lines 4 and 5. This is your view function. It’s an example of a function-based view. It takes a request from your web browser and returns a response. In this simple case, it’s just a line of text formatted as an HTML heading.

Configuring the URLs

If you started the development server now, you would notice it still displays the welcome page. For Django to use your new view, you need to tell Django the index view is the view you want to display when someone navigates to the site root (home page). We do this by configuring our URLs.

In Django, the path() function is used to configure URLs. In its basic form, the path() function has a very simple syntax:

path(route, view)

A practical example of the basic path() function would be:

path('mypage/', views.myview)

In this example, a request to http://example.com/mypage would route to the myview function in the application’s views.py file. Don’t worry if this is a bit confusing now; it will make a lot more sense once you have written more views.

The path() function also takes an optional name argument, and zero or more keyword arguments passed as a Python dictionary. We will get to these more advanced options later in the book.

The path() function statements live in a special file called urls.py.

When startproject created our website, it created a urls.py file in our site folder (\myclub_site\urls.py). This is the correct place for site-wide navigation, but is rarely a good place to put URLs relating to individual applications. Not only is having all our URLs in the one file more complex and less portable, but it can lead to strange behavior if two applications use a view with the same name.

To solve this problem, we create a new urls.py file for each application. If you are wondering why startapp didn’t create the file for us, not all apps have public views accessible via URL. For example, a utility program that performs background tasks would not need a urls.py file. For this reason, Django lets you decide whether your app needs its own urls.py file.

First, we need to create a urls.py file in our events app (new file):

# \myclub_root\events\urls.py

1  from django.urls import path
2  from . import views
3  
4  urlpatterns = [
5      path('', views.index, name='index'),
6  ]

Let’s look at this code closer:

  • Line 1 imports the path() function. This import is necessary for the URL dispatcher to work and is common to all urls.py files.
  • Line 2 imports the local views.py file. The dot operator (“.”) in this case is shorthand for the current package, so this is saying “import all views from the current package (events)”.
  • Line 4 lists the URL patterns registered for this app. For readability, the list is broken into multiple lines, with one URL pattern per line.
  • Line 5 is the actual URL dispatcher:
    • '' matches an empty string. It will also match the “/” as Django automatically removes the slash. In other words, this matches both http://example.com and http://example.com/.
    • views.index points to our index view. I.e., the dot operator is pointing to the index view inside the views.py file that we imported in line 2.
    • name='index'. While it’s optional, you should always name your URLs. We name URLs so they can be referred to in code (reverse lookup). URL reversing is common in both templates and views, so you will see several examples as we work through the book.

Now let’s look at the site urls.py file (changes in bold):

# \myclub_site\urls.py

1  from django.contrib import admin
2  from django.urls import include, path
3  
4  urlpatterns = [
5      path('admin/', admin.site.urls),
6      path('', include('events.urls')),
7  ]

We have made two important changes to the file:

  • Line 2. We have added the include() function to our imports.
  • Line 6. We have added a new URL dispatcher. In this file, the dispatcher is including the urls.py file from the events app. The empty string ('') will match everything after the domain name. This pattern must be the last entry in the urlpatterns list, otherwise Django’s shortcut logic will switch to the events app before trying to match any of the other site URLs.

If you now run the development server and navigate to http://127.0.0.1:8000/ in your browser, you should see a plain, but functioning home page (Figure 5-1).

Figure 5-1: A plain, but functioning homepage for your website.

So What Just Happened?

To better understand how Django works, let’s build on the generic example from Chapter 3 with a concrete example of what Django did to display our home page:

  1. Our browser sent a message to the Django development server requesting it return content located at the root URL (http://127.0.0.1:8000/).
  2. Django then looked for a URL pattern matching the request, by first searching the site level urls.py, and then each of the apps for a urls.py file containing a pattern that matches.
  3. Django checks the first pattern (admin/) in our site level urls.py which doesn’t match and moves on to the second line in which the empty string (root URL) matches.
  4. The matching pattern includes the urls.py from the events app. Basically, this include says “go look in the events app for a pattern that matches”.
  5. Once in the app-level urls.py, the empty string matches again. But this time, the request is sent to the index view.
  6. The index view then renders our simple HTML message to a HttpResponse and sends it to the browser.
  7. The browser renders the response and we see our page heading.

Every Django application follows this same basic process each time it receives a request from the browser.

Your Second View: Dynamic Content

Our simple index view does an excellent job of demonstrating how content from a Django view is rendered to the browser, but it’s not a very good example of a modern web page. That’s because it’s static, i.e., the page content is always the same.

For a modern interactive site, you want the content of your pages to change dynamically based on interaction with the user and program logic.

With our event calendar, it would be good for the page heading to include the month and year. This can’t be hard-coded into the HTML, so we need to develop a way to show the current month and year in the title.

We should probably add an actual calendar too—given it will not be much of an event calendar without you being able to see the calendar!

Before we modify our index view, let’s use the interactive shell to test out some new code. You can use either the Python interactive shell or Django’s shell; the code will work in both:

>>> from datetime import date
>>> t = date.today()
>>> t
datetime.date(2020, 3, 22)

To start, we’re importing the date class from Python’s datetime module. We then assign today’s date to the variable t. Now we have some useful information we can use to extract the current month and year:

>>> t.year
2020
>>> date.strftime(t, '%b')      
'March'
>>> month = date.strftime(t, '%b')
>>> year = t.year
>>> title = "MyClub Event Calendar - %s %s" % (month,year)
>>> title
'MyClub Event Calendar - March 2020'
>>>

The first line should be straightforward—we’re accessing the year attribute of the datetime object t.

The next bit of code is more interesting. We’re using the strftime (string format) module from Python to format t. The %b format string converts datetime object t into a string containing the month formatted to its abbreviated (3-character) name. strftime is a handy module for formatting dates and times. If you want to learn more check out the Python strftime documentation.

In the last bit of test code, we’re putting the month and year into two variables and then using Python’s string formatting syntax to generate a dynamic title for our event calendar page.

Now we’ve got some working code, let’s jump over to our index view and put it to practical use (changes in bold):

# \myclub_root\events\views.py

1  from django.shortcuts import render
2  from django.http import HttpResponse
3  from datetime import date
4  
5  
6  def index(request):
7      t = date.today()
8      month = date.strftime(t, '%b')
9      year = t.year
10     title = "MyClub Event Calendar - %s %s" % (month,year)
11     return HttpResponse("<h1>%s</h1>" % title)

Let’s have a look at the changes to our index view:

  • Line 3. Import the date class from Python’s datetime module.
  • Line 7. Assign today’s date to the variable t.
  • Line 8. Set the month variable to the 3-character month.
  • Line 9. Assign the current year to the variable year.
  • Line 10. Use a Python string format function to create the dynamic title for our page.
  • Line 11. Use another Python string format to return our dynamic title as HTML.

If you run the development server, you should see the homepage now shows the event calendar title with the current date appended (Figure 5-2).

Figure 5-2: Your homepage with a dynamic title.

Your Third View: Dynamic URLs

We’ve updated our index view to include the current month in the title (we’ll get to the calendar bit soon), but what if we wanted to show a month other than this month?

In most web applications, dynamic URLs are used to serve different content. For example, an eCommerce site might give each product a unique URL, like /widgets/green and /widgets/blue.

In Django, we create dynamic URLs with path converters. A path converter has a simple syntax:

<type:variable>

There are five different path converter types:

  • str—matches any non-empty string, excluding ‘/’
  • path—matches any non-empty string, including ‘/’ Useful for matching the entire URL
  • int—matches an integer
  • slug—matches any slug string. e.g., slugs-are-text-strings-with-hyphens-and_underscores
  • UUID—matches a universally unique identifier (UUID)

Here’s an example of a URL containing path converters:

/<int:year>/<str:month>/

When Django is presented with a URL matching this pattern, for example /2019/may/, Django puts the value “2019” into the variable year and the string ‘may’ into the variable month. If you are familiar with regular expressions, path converters are equivalent to capturing groups.

Let’s put this into practice and modify the events app’s urls.py file (change in bold):

urlpatterns = [
    # path('', views.index, name='index'),
    path('<int:year>/<str:month>/', views.index, name='index'),
]

A simple change here—we’ve just added the path converters to the path statement in our events app. The events app will now capture any URL with the format /year/month and pass the year and month to the index view. I’ve left the original path statement commented out in the source for your reference, so you can see what’s changed.

If you tried to do this now, you would get an error because the index view doesn’t know what to do with the new variables. Let’s fix that now by modifying views.py (changes in bold):

# \myclub_root\events\views.py

1 def index(request, year, month):
2     # t = date.today()
3     # month = date.strftime(t, '%b')
4     # year = t.year
5     title = "MyClub Event Calendar - %s %s" % (month, year)
6     return HttpResponse("<h1>%s</h1>" % title)

You can see on line 1, I have added year and month as input parameters to the view function. We no longer need to calculate the year and month values, so I have commented out lines 2 to 4. You can delete these lines if you wish—I left them in to make it easier for you to see what has changed.

To test the new view, navigate to http://127.0.0.1:8000/2020/may/ in your browser. You should see something that looks like Figure 5.3.

Figure 5-3: The dynamic title is now being generated from the URL.

This is pretty cool, but what if you entered some nonsense URL like http://127.0.0.1:8000/123456/12monkeys? You can try this URL—the view will still work, but the title no longer makes sense.

Path converters are an easy to understand and simple to code method for capturing URL arguments. However, when you need a more robust option for validating URLs, Django also provides regular expression (regex) matching for URLs.

Let’s change our urls.py file to use regexes (changes in bold):

# \myclub_root\events\urls.py

1 from django.urls import path, re_path
2 from . import views
3 
4 urlpatterns = [
5     # path('<int:year>/<str:month>/', views.index, name='index'),
6     re_path(r'^(?P<year>[0-9]{4})/(?P<month>0?[1-9]|1[0-2])/', views.index, name='index'),
7 ]

You can see in line 1, I have imported the re_path function from django.urls. I have commented out the original URL configuration using path converters in line 5, so you can compare it to the new URL configuration using regexes in line 6.

If you have used previous versions of Django, notice that the regex syntax is very familiar. The re_path() function replaced the url() function in Django 2.0. This means if you prefer regexes, you can continue to use them with the re_path() function just as you did with the url() function in Django 1.11 and earlier.

Let’s have a closer look at the regexes:

  • [0-9]{4} captures any 4-digit number and assigns it to the variable year. This is not perfect because it will still accept an invalid year, but at least it limits the number to 4 digits. We will perform additional validation logic in the view.
  • 0?[1-9]|1[0-2] will only capture numbers between 1 and 12, which is the range of valid month numbers, and assign the captured value to month. The 0? at the beginning is an optional leading zero, so (for example) both “1” and “01” are valid inputs for January. Note we are no longer capturing a string here. This is because validating a month as a string is too difficult—you would have to check for all variations of English spelling of the month, plus additional checks for localization.

Try a few URLs in YYYY/MM/ format to test the new URL configuration. For example, you could try http://127.0.0.1:8000/2020/01/ to show January 2020 in the title. You should also see that any URL that doesn’t match either of the arguments will throw an HTTP 404 (Not found) error, which is the behavior we want.

Note the month no longer shows a string—the title only includes the month number. To fix this, we need to modify the index view to work with our new URL configuration (changes in bold):

# \myclub_root\events\views.py

1  from django.shortcuts import render
2  from django.http import HttpResponse
3  from datetime import date
4  import calendar
5  
6  def index(request,year,month):
7      year = int(year)
8      month = int(month)
9      if year < 2000 or year > 2099: year = date.today().year
10     month_name = calendar.month_name[month]
11     title = "MyClub Event Calendar - %s %s" % (month_name,year)
12     return HttpResponse("<h1>%s</h1>" % title)

Let’s step through this code in a little more detail:

  • Line 4. We import the calendar module from Python.
  • Lines 7 and 8. While the URLconf is capturing numbers, they are passed in as strings, so we need to convert year and month to integers.
  • Line 9. Validates the year. If the year is less than 2000 or greater than 2099, year is set to this year.
  • Line 10. We’re using the month_name() function from the calendar module to retrieve the month name that matches the month number.
  • Line 11. Replaced month with the month_name variable.

Now when you fire up the development server, you should be able to enter any URL in the form /YYYY/M or /YYYY/MM, and you will see the dynamic title change to match the URL arguments.

Now, all we need to do is add the calendar. Luckily, Python makes this easy with the HTMLCalendar() class from the calendar module. If you want to explore the HTMLCalendar() class and the calendar module in more detail, check out the Python documentation.

In this lesson, we will instantiate a new HTMLCalendar object and pass the HTML formatted content for the selected month to the response. Open up your views.py file and make the changes below (in bold):

# \myclub_root\events\views.py

1  from django.shortcuts import render
2  from django.http import HttpResponse
3  from datetime import date
4  import calendar
5  from calendar import HTMLCalendar
6      
7      
8  def index(request,year,month):
9      year = int(year)
10     month = int(month)
11     if year < 1900 or year > 2099: year = date.today().year
12     month_name = calendar.month_name[month]
13     title = "MyClub Event Calendar - %s %s" % (month_name,year)
14     cal = HTMLCalendar().formatmonth(year, month)
15     return HttpResponse("<h1>%s</h1><p>%s</p>" % (title, cal))

Let’s look at the changes in detail:

  • Line 5. We import the HTMLCalendar class from the calendar module.
  • Line 14. We retrieve an HTML formatted calendar using the formatmonth() method and place the HTML content into the variable cal.
  • Line 15. We modify the string formatting to attach the calendar to the response.

To test the changes, navigate to http://127.0.0.1:8000/2020/1. Your browser should now look like Figure 5.4.

Figure 5-4: The completed dynamic view.

Try a few different years and dates to see how your dynamic view is working. Well done!

OK, so our event calendar is not very pretty, but you will learn how easy it is to style Django websites in the next chapter. In the meantime, there is still one thing to do.

A Note About the Site Root

Django does nothing special with the site root (/)—it’s just another URL. In fact, if you go to your browser and try to navigate to http://127.0.0.1:8000/ now, Django will throw an HTTP 404 (Not found) error.

This is because when we updated our events app’s urls.py file, we removed the mapping to the root directory (''). Let’s fix that by adding the root mapping back to urls.py (change in bold):

# \myclub_root\events\urls.py

1  from django.urls import path, re_path
2  from . import views
3  
4  urlpatterns = [
5      path('', views.index, name='index'),
6      re_path(r'^(?P<year>[0-9]{4})/(?P<month>0?[1-9]|1[0-2])/', views.index, name='index'),
7  ]

This is simple enough—I’ve added the same URL configuration for the root URL we used back at the beginning of this chapter. But, we’re not finished: if you navigate to the root now, your browser is going to look like Figure 5.5.

Figure 5-5: The index view requires the year and month arguments.

This error occurs because the index view requires the year and month arguments to render the calendar. As there are no arguments to capture from the root URL, the call fails.

We solve this problem by using default arguments. Make the following small change (in bold) to the index function definition:

# \myclub_root\events\views.py

# ...
def index(request, year=date.today().year, month=date.today().month):
    # ...

In this change, we are setting the default values for year and month to the current year and month. This way, if you don’t pass the year and month to the index function, Django will default to showing the current month. You can try this out in your browser—if you navigate to http://127.0.0.1:8000/, your browser will display the current month in the calendar.

Chapter Summary

In this chapter, we took a detailed look at the basics of Django’s views, without getting into any detail on how views interact with models and templates. To help you grasp how views work at a fundamental level, I showed you how they work using simple HTML responses.

In the next chapter, we’ll cover the fundamentals of Django’s templates.