Django’s Views - Python Django Tutorials

Django’s Views

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 properly formatted JSON data.

The view makes decisions on 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, however, as Django has grown over the years, Django’s developers added class-based views.

Class-based views add extensibility to Django’s views, as well as built-in 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 be covering both in more detail later in the book.

We covered the basics of Django’s models in Chapter 4. In this chapter, we’re going to 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 am going to demonstrate 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 [TODO].

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. Imports the render() method. This is added to the file automatically by startapp. render() is used when rendering templates, which we will be covering in Chapter [TODO].
  • 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 be able to pass view information back to the browser. Request and response objects are covered in Chapter [TODO].
  • Lines 4 and 5. This is your view function. This is 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 that it still displays the welcome page. This is because, in order for Django to use your new view, you need to tell Django this is the view you want displayed 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 be routed to the myview function in the application’s views.py file. Don’t worry if this is a bit confusing right now, it will make a lot more sense once you have written a couple of views.

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

The path() function statements are kept 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 a good 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 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 that are accessible via URL. A utility program that does background tasks, for example, would not need a urls.py file. Remember, Django doesn’t assume anything, so it lets you decide whether your app needs its own urls.py file.

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

# \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.

Now let’s look at the changes to our site urls.py file:

# \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 a couple of 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 simply including the urls.py file from the events app. The empty string ('') will match everything after the domain name. NOTE: This pattern must be the last entry in the urlpatterns list. The reason for this will become apparent in a later chapter.

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

A Simple Django Homepage

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 that matches 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 pages app. Basically this include says “go look in the pages 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 that is sent 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 a good 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 content of the page is always the same.

For a modern interactive site, you want the content of your pages to be dynamic, i.e. the content changes based on interaction with the user and program logic.

Take our event calendar for example. It would be good for the page heading to include the month and year. Obviously, 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’s not going to be much of an event calendar without you being able to see the calendar!

Before we start modifying 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(2019, 5, 6)

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
2019
>>> date.strftime(t, '%b')
'May'
>>> month = date.strftime(t, '%b')
>>> year = t.year
>>> title = "MyClub Event Calendar - %s %s" % (month,year)
>>> title
'MyClub Event Calendar - May 2019'
>>>

The first line should be straight forward – we’re accessing the year attribute of the datetime object t we created a moment ago.

The second 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 documentation1.

In the last bit of test code, we’re putting the month and year into a couple of 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 that the homepage now shows the event calendar title with the current date appended (Figure 5-2).

Homepage With a Dynamic Title

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

Your Third View: Dynamic URLs

We’ve updated our index view to the current month in the title (we’ll get to the calendar bit shortly), 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 its own URL, like /widgets/green and /widgets/blue.

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

<type:variable>

There are 5 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 (changes in bold):

urlpatterns = [
    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.

Of course, 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 have 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 only 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/2019/jan/ in your browser. You should see something that looks like Figure 5.3.

Title from dynamic URL

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 way to capture URL arguments, but when you need a more robust option for validating URLs, Django also provides regular expression (regex) matching for URLs.

Let’s modify 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, you should 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’re going to peform 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 that we are no longer capturing a string here. This is because validating a string is too difficult – you would have to check for all variations of English spelling, plus additional checks for localization.

Try a few more URLs to test the new URL configuration. You should see that any URL that doesn’t match either of the arguments will throw a HTTP 404 (‘not found’) error, which is the behavior we want.

You should also note that 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 incredibly easy with the HTMLCalendar() class from the calendar module. If you want to explore the HTMLCalendar() class and the calendar module in more detail, you should check out the Python documentation2.

In this lesson, we’re simply going to 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 following 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  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/2019/03. Your browser should now look like Figure 5.4.

The completed dynamic view

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 mean time, there is still one thing to do…

A Note About the Site Root

Django doesn’t do anything special with the site root (/) – it’s just another URL like any other. In fact, if you go to your browser and try to navigate to http://127.0.0.1:8000/ now, Django will throw a ‘Page not found (404)’ 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, your browser is going to look like Figure 5.5.

The index view requires the year and month arguments

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 be able 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 to the index function definition:

# \myclub_root\events\views.py

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

In this change, we are setting the default values for year and month to the current year and month. This way, if the year and month are not passed 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

[TODO]