1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
|
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 look at building the url patterns and some "views" to sort and display our content to the world.
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.
The flow is 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.
With that in mind, let's start building our public site by creating some url patterns. 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?
A far better idea 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:
<pre>
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^admin/', include('django.contrib.admin.urls')),
(r'^blog/', include('djangoblog.blog.urls')),
)
</pre>
Okay, 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 [http://www.w3.org/Provider/Style/URI read the W3C guide on the subject].
As the W3C points out, good URLs don't change, in fact, URLs never change, people change them. But change your URLs and you break everyone's bookmarks. 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 add one thing to the W3Cs guidelines, good URLs are hackable. So what do I mean by hackable? Let's say our blog has urls like:
<pre>
http://mysite.com/blog/2008/jul/08/post-slug/
</pre>
That would display the post whose slug is "post-slug" and was published on July 8, 2008. Ideally, if the user heads up to their url bar and chops off the <code>post-slug/</code> bit they would see all the entries from July 8, 2008. If they chop off <code>08/</code> 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 being useful for those that do, it also creates 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?
<pre>
http://mysite.com/blog/tags/tag-slug/
</pre>
This url accomplishes the same idea, but one ups it. Not only can you hack the url to get to list of all tags (provided you create such a page), it should be obvious that you could plug just about any word into the url and it effectively functions like a tag-based search engine.
Okay, that's all good and well, how do we actually build the URLs?
==Being Generic is Good==
Let's get started, paste this code into your blog/urls.py file:
<pre>
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')),
)
</pre>
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, known as generic views.
The developers of Django 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 <code>info_dict</code>, which is just a Python dictionary that holds two things: a queryset that contains all our public blog entries and the name of our date field in the database.
It's important to realize that Django querysets are lazy, that is Django only hits the database when the queryset is evaluated, so there's no performance penalty for defining a queryset that looks for everything and then filtering it on a case by case basis, which is 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 [http://www.djangoproject.com/documentation/db-api/ database API docs] on the Django site.
That's all the URL patterns list is, a regular expression that looks at the URL, figures out what view to use and then the view 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:
<pre>
(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')),
</pre>
The first bit:
<pre>
(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\w{1,2})/(?P<slug>[-\w]+)/$
</pre>
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 <code>object_detail</code> which is the name of the generic view that will pull out a single entry.
The full path to object_detail is actually <code>django.views.generic.date_based.object_detail</code>, but since we started our urlpattern definition by including the <code>django.views.generic.date_based</code> 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 <code>object_detail</code> function, we also pass along a dictionary of data. Most of the time you can just pass <code>info_dict</code> here. The <code>object_detail</code> 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 <code>dict</code> your see above. In this case we've passed <code>info_dict</code>, the slug_field and the name of the template that 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 as 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 [http://www.djangoproject.com/documentation/generic_views/ documentation on the Django website] and also have a look at [http://www.b-list.org/weblog/2006/nov/16/django-tips-get-most-out-generic-views/ 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 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. Now 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:
<pre>
urlpatterns = patterns('',
(r'^admin/', include('django.contrib.admin.urls')),
(r'^blog/', include('djangoblog.blog.urls')),
(r'^tags/(?P<slug>[a-zA-Z0-9_.-]+)/$', 'djangoblog.tag_views.tag_detail'),
)
</pre>
Okay, now 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 <code>tag_detail</code>. Note that in the tag URL, we're capturing the slug param; 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, so open up that file in your text editor and paste in this code:
<pre>
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')
</pre>
Okay, so 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 <code>tag_detail</code> function, which is just an ordinary Python function that take two parameters. The first is <code>request</code> which Django passes to all view functions and the second it the slug param we defined in our URL pattern above.
Now because we're using a slug (a slug is an old newspaper term, which is this context refers to the last bit of the URL and can contain letters and dashes rather than space) for our tag URLs, but words with spaces for our tags we need to get rid of the dashes and replace them with 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 <code>objects</code> manager. Then we take advantage of django-tagging's built in function <code>get_by_model</code> 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 -- <code>object_detail</code> from the generic list views. Object detail requires a few things, first of the request object, then the queryset of results and then 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. And then the last item simple tells Django which template to use.
Now we haven't created a URL for http://mysite.com/blog/tags/ to list all our tags, but that's a good way 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 <code>slug</code> param. And instead of looking up TaggedItems, just grab all the tags (i.e. <code>qs = Tag.objects.all()</code>)
== 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, which 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 well dive into Django's template system and explore all the cool things we can do, including how to write custom template filters and more.
|