- 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.5 Generating 404 Errors for Invalid Queries
As things stand, we can use the command line to start our development server (Example 5.11) and see the fruits of our labor.
Example 5.11: Shell Code
$ ./manage.py runserver
If you navigate to the address of a valid Tag, you will be greeted by a simple HTML page built from our template. For example, http://127.0.0.1:8000/tag/django/ will display a simple page about our Django tag. However, what happens if you browse to a URL built with an invalid tag slug, such as http://127.0.0.1:8000/tag/nonexistent/?
You’ll be greeted by a page of Django debug information, as shown in Figure 5.4.
Figure 5.4: Django Error Message
Django is displaying a page informing you that Python has thrown an exception. The title of the page, “DoesNotExist at /tag/nonexistent/,” tells us that the URL we asked for does not exist. The subtitle, “Tag matching query does not exist” tells us that the database query for our Tag could not find a row in the database that matched what we desired (in this case, we queried Tag.objects.get(slug--iexact='nonexistent')). What’s more, below the initial readout presented in Figure 5.4, you’ll find a Python traceback, shown in Figure 5.5, where Django informs us that the Python exception type being raised is DoesNotExist.
Figure 5.5: Django Error Traceback
Of the four functions in the traceback, three are in Django’s source code and therefore (most likely) are not the problem. The second item in the traceback, however, is in /organizer/views.py and reveals that the code throwing the exception is tag = Tag.objects.get(slug--iexact=slug), on line 17. This does not mean the code is wrong (it isn’t!), simply that the problem originates there. The problem, as stated in the top half of the page, is that there is no Tag object with slug “nonexistent” in the database.
This message is obviously not what we want users to be greeted with in the event of a malformed URL. The standard return for such in websites is an HTTP 404 error. Let us return to our function view in /organizer/views.py and augment it so that it returns a proper HTTP error rather than throwing a Python exception.
Django supplies two ways to create an HTTP 404 error. The first is with the HttpReponseNotFound class, and the second is with the Http404 exception.
The HttpReponseNotFound class is a subclass of the HttpResponse class. Like its superclass, HttpReponseNotFound expects to be passed the HTML content it is asked to display. The key difference is that returning an HttpResponse object results in Django returning an HTTP 200 code (Resource Found), whereas returning a HttpReponseNotFound object results in an HTTP 404 code (Resource Not Found).
The Http404 is an exception and as such is meant to be raised rather then returned. In contrast to the HttpReponseNotFound class, it does not expect any data to be passed, relying instead on the default 404 HTML page, which we build in Chapter 29: Deploy! when we deploy our site.
Consider that our code is currently raising a DoesNotExist. We therefore have to catch this exception and then proceed with an HTTP 404 error. It is thus more appropriate and more Pythonic to use an exception, meaning our code in Example 5.12 will use the Http404 exception. Start by importing this in the file, by adding Http404 to the second import line (the one for HttpResponse). The import code should now read as shown in Example 5.12.
Example 5.12: Project Code
organizer/views.py in 294dabd8cc
1 from django.http.response import (
2 Http404, HttpResponse)
To catch the DoesNotExist exception, we surround our model manager query with a Python try...except block. Should the query raise a DoesNotExist exception for a Tag object, we then raise the newly imported Http404. This leaves us with the code shown in Example 5.13.
Example 5.13: Project Code
organizer/views.py in 294dabd8cc
17 def tag_detail(request, slug):
18 try:
19 tag = Tag.objects.get(slug--iexact=slug)
20 except Tag.DoesNotExist:
21 raise Http404
22 template = loader.get_template(
23 'organizer/tag_detail.html')
24 context = Context({'tag': tag})
25 return HttpResponse(template.render(context))
Had we opted to use HttpReponseNotFound, we might have coded as in Example 5.14.
Example 5.14: Python Code
# this code not optimal!
try:
tag = Tag.objects.get(slug--iexact=slug)
except Tag.DoesNotExist:
return HttpReponseNotFound('<h1>Tag not found!</h1>')
Raising an exception rather than returning a value is considered better Python practice because raise() was built explicitly for this purpose. What’s more, it allows us to create a single 404.html page in Chapter 29, further maintaining the DRY principle.
Note that some developers might try to use the code shown in Example 5.15.
Example 5.15: Python Code
# this code is incorrect!
tag = Tag.objects.get(slug--iexact=slug)
if not tag:
return HttpReponseNotFound('<h1>Tag not found!</h1>')
The code is incorrect: it will not catch the DoesNotExist exception raised by the model manager query. A try...except block is required.
If you browse to http://127.0.0.1:8000/tag/nonexistent on the development server now, you will be treated to an HTTP 404 page, which is our desired behavior.
The error in this section is different from a nonmatching URL. If you browse to http://127.0.0.1:8000/nopath/, Django will tell you it couldn’t match the URL to a URL pattern and, in production, will return a 404 error automatically. The issue we solved here was when the URL did match but the view did not behave as expected.