File Under: Frameworks, Programming

Use URL Patterns and Views in Django

Last time around, we installed Django and started building a blog application. We got Django’s built-in admin system up and running and explored some third-party libraries like the django-tagging project.

So far we have some cool administrative tools, but no website for the rest of the world to see. This time around, we’ll work on displaying our content to the world by building the URL patterns and constructing some “views” — a term with a very specific meaning within the Django framework.

Everything we’re going to do will make more sense if you understand how Django processes your visitor’s request. We went over some of this in our introduction, but here’s a quick refresher course.

Contents

  1. How content is presented
  2. Working with urls.py
  3. Thoughts on URLs
  4. Being Generic is Good
  5. Go your own way
  6. Conclusion

How content is presented

The Django flow goes something like this:

1) Visitor’s browser asks for a URL.
2) Django matches the request against its urls.py files.
3) If a match is found, Django moves on to the view that’s associated with the URL. Views are generally found inside each app in the views.py file.
4) The view generally handles all the database manipulation. It grabs data and passes it on.
5) A template (specified in the view) then displays that data.

Designer and coder Jeff Croft has put together a visual representation of this flow that makes it even easier to understand:

See the full size original (along with explanatory text) at Flickr.

With that in mind, let’s start building our public site by creating some URL patterns.

Working with urls.py

Remember the urls.py file where we set up our admin URLs in the last lesson? Open that file in your favorite text editor.

Now, we could define all our URLs in this file. But then what happens if we want to reuse this blog in an entirely separate project? We’d get a mess.

A far better approach is to define all your app-specific URLs in a file that lives inside the app itself. In this case, we’re going to use a file inside the blog app, which will also be named urls.py.

However, before we start with that file, we need to tell Django about it. So add this line to the project-wide urls.py file, just below the line that defines the admin URLs, like so:

from django.conf.urls.defaults import *

from django.contrib import admin



urlpatterns = patterns('',

    (r'^admin/(.*)', admin.site.root),

	(r'^blog/', include('djangoblog.blog.urls')),

)



OK, now head into the blog app folder and create a new urls.py file, which we’ll use to hold all the URLs related to our blog application.

Thoughts on URLs

One of the nice things about Django is that it forces you to think about your URL designs, something many people don’t spend much time considering. If, perchance, you’ve never spent too much time thinking about URLs, now is good time to read the W3C guide on the subject.

As the W3C points out, good URLs never change. In fact, even bad URLs never change — people change them. But when you change your URLs, you break everyone’s bookmarks and inbound links. So, spend a bit of time designing a good URL scheme from the beginning and you shouldn’t need to change things down the road.

I would actually add one item to the W3Cs guidelines: good URLs are hackable. What do I mean by hackable? Let’s say our blog has URLs like:

http://mysite.com/blog/2008/jul/08/post-slug/

That URL would display the blog post with the slug named “post-slug” which was published on July 8, 2008.

Note: “Slug” is an old newspaper term. An article about fires in Nevada would probably be slugged “nv-fires” for easy identification. In this context, the slug refers to the last bit of the URL and can contain letters and dashes rather than spaces.

Ideally, if the user heads up to their browser’s location bar and chops off the post-slug/ bit, they would see all the blog entries from July 8, 2008. If they were to chop off 08/, they would see all the posts from July 2008, and so on.

In other words, the URL is hackable. Now, most people probably won’t do that. But in addition to making your site easier to navigate for the hands-on types, this rule also enforces an easy-to-use structure around which to build your site. In this case, the date-based structure was probably already obvious. But what about tags?

http://mysite.com/blog/tags/tag-slug/

This URL structure uses the same idea, but one-ups it. Not only can you hack the URL to get to a list of all tags (provided you create such a page), it should be obvious that you could plug just about any word into the “tag-slug” part of the URL and it effectively functions like a tag-based search engine.

So, how do we actually build the URLs?

Being Generic is Good

Let’s get started. Paste this code into your blog/urls.py file:

from django.conf.urls.defaults import *

from djangoblog.blog.models import Entry

from tagging.views import tagged_object_list



info_dict = {

	'queryset': Entry.objects.filter(status=1),

	'date_field': 'pub_date',

}



urlpatterns = patterns('django.views.generic.date_based',

	(r'(?P<year>d{4})/(?P<month>[a-z]{3})/(?P<day>w{1,2})/(?P<slug>[-w]+)/$', 'object_detail', dict(info_dict, slug_field='slug',template_name='blog/detail.html')),

	(r'^(?P<year>d{4})/(?P<month>[a-z]{3})/(?P<day>w{1,2})/(?P<slug>[-w]+)/$', 'object_detail', dict(info_dict, template_name='blog/list.html')),

	(r'^(?P<year>d{4})/(?P<month>[a-z]{3})/(?P<day>w{1,2})/$','archive_day',dict(info_dict,template_name='blog/list.html')),

	(r'^(?P<year>d{4})/(?P<month>[a-z]{3})/$','archive_month', dict(info_dict, template_name='blog/list.html')),

	(r'^(?P<year>d{4})/$','archive_year', dict(info_dict, template_name='blog/list.html')),

	(r'^$','archive_index', dict(info_dict, template_name='blog/list.html')),

)





Now, remember when I said that the URL patterns determine which view Django will use to grab data from our database? In that scheme, we would write our regular expressions and then point each pattern to a function in views.py.

But we’re cheating a little bit here by taking advantage of some built in views that Django provides. These are known as “generic views.”

Django’s developers wisely figured that date-based archives were likely to be a common problem that just about every site has at least some use for, so they baked in some generic data-based views.

What we’ve done here is take advantage of the built in views to construct our URLs.

Let’s start with info_dict, which is just a Python dictionary that holds two things: a queryset containing all of our public blog entries and the name of our date field in the database.

It’s important to realize that Django querysets are lazy — Django only hits the database when the queryset is evaluated, so there’s no performance penalty for defining a queryset that looks for everything, then filtering it on a case-by-case basis. This happens to be essentially what we’ve just done.

Passing the queryset to the generic view enables Django to automatically do whatever date sorting we need, for instance, to show all the entries from a single month or year. For more info on querysets, check out the database API docs on the Django site.

That’s all the URL patterns list is: a regular expression that looks at the URL and figures out what view to use. The view then determines which entry or list of entries to show.

Let’s break it down and go through each part of the URL pattern. We’ll use the first line as an example:

(r'(?P<year>d{4})/(?P<month>[a-z]{3})/(?P<day>w{1,2})/(?P<slug>[-w]+)/$', 'object_detail', dict(info_dict, slug_field='slug',template_name='blog/detail.html')),

The first bit:

(?P<year>d{4})/(?P<month>[a-z]{3})/(?P<day>w{1,2})/(?P<slug>[-w]+)/$



is the regular expression. In this case, the expression will match our permalink URLs and capture the year, month, day and slug of a particular entry. That information will then be passed to the next bit, object_detail, which is the name of the generic view that will pull out a single entry.

The full path to object_detail is actually django.views.generic.date_based.object_detail, but since we started our urlpattern definition by including the django.views.generic.date_based bit, there’s no need to retype it every time, we just need to call the individual function, in this case object_detail.

After we grab the URL info and pass it to the object_detail function, we also pass along a dictionary of data. Most of the time you can just pass info_dict here. The object_detail generic view is something of an exception because it needs to pass along the slug_field variable as well.

I wanted to show some of the other data you can include as well, so I wrapped it in the dict your see above. In this case, we’ve passed info_dict, the slug_field and the name of the template Django should use to display the data.

The rest of the patterns just work their way back up the URL using ever-shortening regular expressions until we get to nothing, which would be the URL: http:/mysite.com/blog/.

We’ll be using that URL as the location of our archive page. So, I guess you can think of this as a tumblelog rather than a traditional blog, which would probably have separate archive and home pages. Naturally, you can tweak the URL patterns to fit whatever design you’d like.

Django’s generic views are incredibly powerful, and there are quite a few other options beyond just the date-based ones we’ve used here. There’s also a super-handy built-in pagination system for some generic views. Be sure to read through the documentation on the Django website and also have a look at James Bennett’s excellent guide to the various ways you can wrap and extend generic views.

Go your own way

Django’s generic views can save you quite a bit of time, but you will probably encounter some situations where they don’t quite do exactly what you want. When that happens, it’s time to write your own views.

Fear not, writing a custom view isn’t hard.

We’ve pretty much covered our blogs URLs, from date-based archives to the detail pages, but what about the pages that display our entries by tag?

The tagging application actually includes some views that we could use. But for the sake of example, we’ll write some custom views. Rather than overwriting what’s already in the tagging application, we’re just going to create a views file that lives on its own in our main project folder.

So, inside the “djangoblog” folder create a new file named tag_views.py. Remember, before we get started there, we need to tell Django about the tag_views file, so open up djangoblog/urls.py and add the last line below what’s already there:

urlpatterns = patterns('',

	(r'^admin/(.*)', admin.site.root),

	(r'^blog/', include('djangoblog.blog.urls')),

	(r'^tags/(?P<slug>[a-zA-Z0-9_.-]+)/$', 'djangoblog.tag_views.tag_detail'),

)



Here, we haven’t included another url.py file like we did with the lines above. You could argue that we should, but just to show that you don’t have to, we’ll just point directly to our tag_views.py file which will soon have a function named tag_detail. Note that in the tag URL, we’re capturing the slug paramater. We’ll use that in just a minute to filter our blog entries by tag.

Now it’s time to create the tag_detail function in the tag_views.py file. Open up that file in your text editor and paste in this code:


from django.views.generic.list_detail import object_detail



from tagging.models import Tag,TaggedItem

from blog.models import Entry



def tag_detail(request, slug):

	unslug = slug.replace('-', ' ')

	tag = Tag.objects.get(name=unslug)

	qs = TaggedItem.objects.get_by_model(Entry, tag)

	return object_list(request, queryset=qs, extra_context={'tag':slug}, template_name='tags/detail.html')

What’s going on here? Well, ignore the first line for now, we’ll get to that in a minute. We import all the things we need — in this case, the Tag and TaggedItem classes from django tagging and then our own Entry class. Then we define the tag_detail function, which is just an ordinary Python function that takes two parameters. The first is request which Django passes to all view functions, and the second is the slug paramater we defined in our URL pattern above.

Now because we’re using a slug for our tag URLs, but strings of words with spaces for our tags, we need to get rid of the dashes and replace them with spaces (Remember, a slug can contain letters and dashes, but not spaces).

Because our slug parameter is just a string, we can use the normal Python string function to make that replacement.

In the next line, we look up our tag name in the database using the objects manager. Then we take advantage of django-tagging’s built in function get_by_model to grab all the entries with the given tag.

The last step is to return something so that Django can load our template and display the entries to our visitor. To do that we’re going to use another of Django’s generic view functions — object_detail from the generic list views.

Object_detail requires a few things. First is the request object, then the queryset of results. After that, I’ve added an extra context variable named tag, so our template will be aware not just what entries to display, but also the current tag. Finally, the last item simply tells Django which template to use.

We haven’t yet created a URL for http://mysite.com/blog/tags/ to list all our tags, but that’s a good stepping stone to practice writing a view function on your own. Here’s a hint: you can use pretty much the same code we used for the tag_detail function, but you don’t need to worry about the slug param. And instead of looking up TaggedItems, just grab all the tags (i.e. qs = Tag.objects.all())

Conclusion

And there you have it, a quick and dirty overview of how URL patterns and view work in Django.

If you point your browser to our development URL (http://127.0.0.1:8000/blog/) you should see a Django error page complaining that the template blog/list.html does not exist. This is true, since we haven’t created it yet (visiting the tag pages will give you “list index out of range” error, also due to the missing templates).

But don’t worry — in the next lesson, we’ll dive into Django’s template system and explore all the cool things we can do, including how to write custom template filters and more.

See you then!