- 5.1 Introduction
- 5.2 The Purpose of Views and URL Configurations
- 5.3 Step-by-Step Examination of Django's Use of Views and URL Configurations
- 5.4 Building Tag Detail Webpage
- 5.5 Generating 404 Errors for Invalid Queries
- 5.6 Shortening the Development Process with Django View Shortcuts
- 5.7 URL Configuration Internals: Adhering to App Encapsulation
- 5.8 Implementing the Views and URL Configurations to the Rest of the Site
- 5.9 Class-Based Views
- 5.10 Redirecting the Homepage
- 5.11 Putting It All Together
5.4 Building Tag Detail Webpage
To reinforce what we already know and expand our knowledge of URL patterns, we now create a second webpage. Our webpage will display the information for a single Tag object. We call our function view tag_detail(). Let’s begin by adding a URL pattern.
In Chapter 3, we specifically added SlugField to our Tag model to allow for the simple creation of unique URLs. We intend to use it now for our URL pattern. We want the request for /tag/django/ to show the webpage for the django Tag and the request for /tag/web/ to show the webpage for the web Tag.
This is the first gap in our knowledge. How can we get a single URL pattern to recognize both /tag/django/ and /tag/web/? The second gap in our knowledge is that we have no easy way to use the information in the URL pattern. Once we’ve isolated django and web, how can we pass this information to the view so that it may request the data from the database?
To make the problem more concrete, let’s start with the tag_detail() view.
5.4.1 Coding the tag_detail() Function View
Open /organizer/views.py and program the bare minimum functionality of a view (accept an HttpRequest object, return an HttpResponse object), as shown in Example 5.5.
Example 5.5: Project Code
organizer/views.py in f0d1985791
16 def tag_detail(request):
17 return HttpResponse()
Our first task is to select the data for the Tag object that the user has selected. For the moment, we will assume that we have somehow been passed the unique slug value of the Tag as the variable slug, and we use it in our code (but Python will yell at you if you try to run this). We use the get() method of our Tag model manager, which returns a single object. We want our search for the slug field to be case insensitive, so we use the iexact field lookup scheme. Our lookup is thus Tag.objects.get(slug--iexact=slug), as shown in Example 5.6.
Example 5.6: Project Code
organizer/views.py in ba4f692e00
16 def tag_detail(request):
17 # slug = ?
18 tag = Tag.objects.get(slug--iexact=slug)
19 return HttpResponse()
We may now load the template we wish to render, organizer/tag_detail.html. When we wrote the template, we wrote it to use a variable named tag. We thus create a Context object to pass the value of our Python variable named tag to our template variable tag. Recall that the syntax is Context({'template_variable_name': Python_variable_name}). We thus extend our view code as shown in Example 5.7.
Example 5.7: Project Code
organizer/views.py in 2fdb78366f
16 def tag_detail(request):
17 # slug = ?
18 tag = Tag.objects.get(slug--iexact=slug)
19 template = loader.get_template(
20 'organizer/tag_detail.html')
21 context = Context({'tag': tag})
22 return HttpResponse(template.render(context))
We have what would be a fully working function view if not for the problem we are now forced to confront: the slug variable is never set. The value of the slug will be in the URL path. If Django receives the request for /tag/django/, we want the value of our slug variable to be set to 'django'. Django provides two ways to get it.
The first way is terrible and inadvisable: we can parse the URL path ourselves. The request variable, an HttpRequest object, contains all the information provided by the user and Django, and we could access request.path_info to get the full path. In our example above, request.path_info would return 'tag/django/'. However, to get the slug from our URL path, we would need to parse the value of request.path_info, and doing so in each and every view would be tedious and repetitive, in direct violation of the Don’t Repeat Yourself (DRY) principle.
The second method, the recommended and easy solution, is to get Django to send it to us via the URL configuration, as we shall discover in the next section. To accommodate this solution, we simply add slug as a parameter to the function view.
Our final view is shown in Example 5.8.
Example 5.8: Project Code
organizer/views.py in 84eb438c96
16 def tag_detail(request, slug):
17 tag = Tag.objects.get(slug--iexact=slug)
18 template = loader.get_template(
19 'organizer/tag_detail.html')
20 context = Context({'tag': tag})
21 return HttpResponse(template.render(context))
5.4.2 Adding a URL Pattern for tag_detail
With our tag_detail() function view fully programmed, we now need to point Django to it by adding a URL pattern to the URL configuration. The pattern will be in the form of url(<regular_expression>, tag_detail), where the value of <regular_expression> is currently unknown. In this section, we need to solve two problems:
- We need to build a regular expression that allows for multiple inputs. For example, /tag/django/ and /tag/web/ must both be valid URL paths.
- We must pass the value of the slug in the URL path to the detail view.
The answer to both of these problems is to use regular expressions groups.
To solve the first case, we first begin by building a static regular expression. Remember that our regular expressions patterns should not start with a /. To match /tag/django/ we can use the regular expression r'^tag/django/$'. Similarly, r'^tag/web/$' will match /tag/web/. The goal is to build a regular expression that will match all slugs. As mentioned in Chapter 3, a SlugField accepts a string with a limited character set: alphanumeric characters, the underscore, and the dash. We first define a regular expression character set by replacing django and web with two brackets: r'^tag/[]/$'. Any character or character set inside the brackets is a valid character for the string. We want multiple characters, so we add the + character to match at least one character: r'^tag/[]+/$'. In Python, \w will match alphanumeric characters and the underscore. We can thus add \w and - (the dash character) to the character set to match a valid slug: r'^tag/[\w\-]+/$'. This regular expression will successfully match /tag/django/, /tag/web/, and even /tag/video-games/ and /tag/video_games/.
This regular expression matches all of the URLs we actually want, but it will not pass the value of the slug to the tag_detail() function view. To do so, we can use a named group. Python regular expressions identify named groups with the text (?P<name>pattern), where name is the name of the group and pattern is the actual regular expression pattern. In a URL pattern, Django takes any named group and passes its value to the view the URL pattern points to. In our case, we want our named group to use the pattern we just built—[\w\-]+—and to be called slug. We thus have (?P<slug>[\w\-]+).
Our full regular expression has become r'^tag/(?P<slug>[\w\-]+)/$'. This regular expression will match a slug and pass its value to the view the URL pattern points to. We can now build our URL pattern.
We are building a URL pattern for our tag_detail() view, which exists in the views.py file in our organizer app. We first import the view via a Python import and then create a URL pattern by calling url() and passing the regular expression and the view. Example 5.9 shows the resulting URL configuration in suorganizer/urls.py.
Example 5.9: Project Code
suorganizer/urls.py in 5b18131069
16 from django.conf.urls import include, url
. ...
19 from organizer.views import homepage, tag_detail
. ...
24 url(r'^tag/(?P<slug>[\w\-]+)/$',
25 tag_detail,
26 ),
If we request http://127.0.0.1:8000/tag/django or the Django runserver, Django will select our new URL pattern and call tag_detail(request, slug='django').
The regular expression pattern and view pointer are not the only parameters we can pass to url(). It is possible, and highly recommended, to specify the keyword argument name for URL patterns. The utility of specifying name is the ability to refer to a URL pattern in Django, a practice we discuss in Chapter 6: Integrating Models, Templates, Views, and URL Configurations to Create Links between webpages. This practice not only is useful in Django but also allows me to refer to URL patterns in the book without ambiguity.
It is possible to name a URL pattern whatever you wish. However, I strongly recommend you namespace your names, allowing for easy reference without conflict across your site. In this book, I use the name of the app, the name of the model being used, and the display type for the object type. We thus name the URL pattern organizer_tag_detail. Our final URL pattern is shown in Example 5.10.
Example 5.10: Project Code
suorganizer/urls.py in 79c8d40d8a
24 url(r'^tag/(?P<slug>[\w\-]+)/$',
25 tag_detail,
26 name='organizer_tag_detail'),
Consider all the code we have avoided writing by writing our URL pattern intelligently. At the end of Section 5.4.1, we were considering parsing the raw URL path string (passed to the view via request.path_info) to find the slug value of our tag. Thanks to Django’s smart URL configuration, simply by providing a named group to our regular expression pattern, we can pass values in the URL directly to the view.