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:

  1. <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:

  1. /<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):

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

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

  1. # \myclub_root\events\views.py
  2. 1 def index(request, year, month):
  3. 2 # t = date.today()
  4. 3 # month = date.strftime(t, '%b')
  5. 4 # year = t.year
  6. 5 title = "MyClub Event Calendar - %s %s" % (month, year)
  7. 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.

Your Third View: Dynamic URLs - 图1

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

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

  1. # \myclub_root\events\views.py
  2. 1 from django.shortcuts import render
  3. 2 from django.http import HttpResponse
  4. 3 from datetime import date
  5. 4 import calendar
  6. 5
  7. 6 def index(request,year,month):
  8. 7 year = int(year)
  9. 8 month = int(month)
  10. 9 if year < 2000 or year > 2099: year = date.today().year
  11. 10 month_name = calendar.month_name[month]
  12. 11 title = "MyClub Event Calendar - %s %s" % (month_name,year)
  13. 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):

  1. # \myclub_root\events\views.py
  2. 1 from django.shortcuts import render
  3. 2 from django.http import HttpResponse
  4. 3 from datetime import date
  5. 4 import calendar
  6. 5 from calendar import HTMLCalendar
  7. 6
  8. 7
  9. 8 def index(request,year,month):
  10. 9 year = int(year)
  11. 10 month = int(month)
  12. 11 if year < 1900 or year > 2099: year = date.today().year
  13. 12 month_name = calendar.month_name[month]
  14. 13 title = "MyClub Event Calendar - %s %s" % (month_name,year)
  15. 14 cal = HTMLCalendar().formatmonth(year, month)
  16. 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.

Your Third View: Dynamic URLs - 图2

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.