summaryrefslogtreecommitdiff
path: root/old/published/Webmonkey/Frameworks/django-templates.txt
blob: 8ded612b3cec753abac90be8390bb58607a45780 (plain)
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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
When we left off last time we had defined some URLs for our blog and even wrote a custom view to handle displaying posts by tag. But if you point your browser to our development url (http://127.0.0.1:8000/blog/) you'll still 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.

It's time to tackle the last aspect of Django -- the template syntax.

If you look back through the code we've written so far you'll find that we've point Django to a number of templates (look in the urlpatterns code and the custom view we wrote). The templates we've defined need to be created as follows:

djangoblog
	- templates
		-blog
			list.html
			detail.html
		-tags
			list.html

You can go ahead and create that directory structure -- just create a folder in your main djangoblog folder and name it templates. Then inside that create two folders, blog and tags. Then create your list and detail.html files (note that the .html extension is totally optional, I use it because it turns on the syntax highlighting in my text editor, but you can use any extension you like).

== Inheritance ==

If we step back for a minute and think about our blog and what our templates need to display, the first thing that jumps out at you is that there's a whole bunch of stuff that's common to every page -- a header, site navigation, sidebar, footer, etc.

It would be silly (and a egregious violation of the DRY principle) if we wrote that code more than once. Luckily, like most good template languages, Django provides a way to extend a single template file. We can define our site-wide components once and then simple inherit from that file, adding in the aspects of the site that do change.

So before we dive into the detail pages, let's first create a base file. Being the creative type I generally call the file base.html. So inside the templates folder you created above, add a new file, base.html.

Now open that file in your text editor and let's sketch out some of the site-wide html we might need. For the sake of this tutorial I've kept things pretty simple, but feel free to get as fancy as you want with your HTML. Here's a basic starting point:

<pre>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="en">
	<head>
		<title>My Site - {%block pagetitle %}{% endblock %}</title>
	</head>
	<body>
		<div id="header">My Site</div>
		<div id="nav">
			<ul>
				<li><a href="/">home</a></li>
				<li><a href="/blog/">Blog</a></li>
				<li><a href="/links/">Links</a></li>
				<li><a href="/about/">About</a></li>
			</ul>
		</div>
		<div id="content">
			<div id="primary">
				<h1>{% block title %}{% endblock %}</h1>
				{% block primary %}{% endblock %}
			</div>
			<div id="secondary">
				<h3>Recent Entries:</h3>
			<div>
		</div>
	</body>
</html>
</pre>

That should be pretty self explanatory, save perhaps the curious things enclosed in curly brackets, the <code>{% %}</code> bits. What up with that stuff?

That is Django's template syntax for creating blocks which other templates can plug data into. In this case we're creating a base template with a few blocks (think of them as holes) and our other templates will extend this and fill in the holes.

To see what I mean let's create the blog/list.html template. Add a new blog folder in the templates folder (if you haven't already) and create a file list.html. Now open that up and past in the line <code>{% extends 'base.html' %}</code>. 

Now if you revisit [http://127.0.0.1:8000/blog/ http://127.0.0.1:8000/blog/] the template not found error should be gone and in its place you should see everything we just put in base.html -- not much I'm afraid.

But fear not, now that we're extending base.html we can start plugging in some values for those blocks we created earlier.

Add this below the extends statement:

<pre>
{% block pagetitle %}Tumblelog{% endblock %}
{% block title %}My Page Headline{% endblock %}
{% block primary %}
{% for object in latest %}
	<h2>{{ object.title }}</h2>
	<p>{{ object.pub_date }}</p>
	 {{ object.body_html|truncatewords_html:"20"|safe }}
	<p>tags: {% for tag in object.get_tags%}<a href="/blog/tags/{{tag.name|slugify}}/">{{tag}}</a>{% endfor %}</p>
	<p><a href="{{object.get_absolute_url}}">read more</a></p>
{% endfor %}
{% endblock %}
</pre>

Okay, first off we fill in our page title block in the head tags and then we do the same for the displayed title. The next block we fill in is the primary content block. Here's where we display the data that our generic view grabbed for us. 

The Django template syntax is fairly Pythonic in that your define loops using the same <code>for x in dataset</code> syntax. In this case the generic view function object_detail passes in a variable named latest (by default the fifteen latest entries, though you can go back to the urls.py and increase that number using the num_latest param). 

So all we do is construct a loop using the <code>latest</code> variable. Then within that loop we pull out some of our data -- again accessing an objects attributes uses the python-like dot accessor methods. 

The only part that requires additional explaining is the <code>object.body_html</code> section where we've applied two built-in Django template filters, <code>truncatewords_html</code> and <code>safe</code>.

Truncatewords_html should be fairly obvious, this clips the the body of our post after twenty words, but also preserves the structure of the html by appending any closing tags to make sure the html is intact. 

The <code>safe</code> filter simply tells Django that it's okay to display HTML. Without the safe filter Django will automatically escape all HTML entities and tags. Autoescaping is a nice feature for avoiding nefarious XSS attacks and the like, but in this case, since we trust the source, we'll let the HTML through.

Okay, cool, but what about tags? We do after all have tags on our entries, might as well display them which is what we do in the next line. Here we have a loop within a loop. Remember when we created our models we added a get_tags method to return all the tags for each entry? Well here it is in action.

That will loop through all the tags for each entry and display them along with a link to that tag's permalink page. And note that we've used the slugify filter to make sure than any multiword tags will be hyphenated in the URL (if you remember back when we wrote our custom tag view we used a string replace function to "unslugify" the url for lookup in the datebase).

The last line calls the <code>get_absolute_url</code> function that we defined when we built our model in the last lesson. This provides a link to the permalink page for each entry in the list.

So click that link and what happens? Error page. You need to define the detail.html template. That's not to hard, just create the file, add the extends base.html instruction at the top and fill in the blank blocks like title and primary. This time there's no need to loop through anything since there's only one object. Just call it directly like we did inside the loop on the archive page: <code>{{object.title}}</code> etc.

The code might look something like this:

<pre>
{% block pagetitle %}{{object.title}}{% endblock %}
{% block title %}{{object.title}}{% endblock %}
{% block primary %}
	<ul class="page-nav">
		{% if object.get_previous_published%}
		<li>
			<a href="{{ object.get_previous_published.get_absolute_url }}" title=" {{object.get_previous_published.title}}">&laquo; previous</a>
		</li>
		{%endif%}
		{% if object.get_next_published%}
			<li>
				<a href="{{ object.get_next_published.get_absolute_url }}" title=" {{object.get_next_published.title}}">next &raquo;</a>
			</li>
		{%endif%}
    </ul>
	<h2>{{ object.title }}</h2>
	<p>{{ object.pub_date }}</p>
	 {{ object.body_html|safe }}
	<p>tags: {% for tag in object.get_tags%}<a href="/blog/tags/{{tag.name|slugify}}/">{{tag}}</a>{% endfor %}</p>
	<p><a href="{{object.get_absolute_url}}">read more</a></p>
{% endblock %}
</pre>

Note that I've made use of those <code>get_next_published</code> and <code>get_previous_published</code> functions that we defined way back when wrote our models. That way, users have some handy next and previous links for navigating through your permalink pages.

Naturally you can get much more sophisticated with your HTML than this simple example.

To create the templates for the tag pages you'll essentially do the same thing. In our custom tag view we returned a list of all the entries in an object named <code>object_list</code>. So in order to display them, just loop through <code>object_list</code> like we did with the <code>latest</code> variable above.

==Built-in Template Filters==

Before we move on, it's worth paying a visit to the [http://www.djangoproject.com/documentation/templates/ Django documentation on template filters and tags]. There's a whole bunch of useful stuff built in. You can use <code>{% if %}</code> tags to narrow and filter results, and there's also <code>{% else %}</code>, <code>{% ifequal var1 var2 %}</code>, <code>{% ifnotequal var1 var2 %}</code> and most other common programming structures.

Then there's a host of template filters, like the truncatewords_html and safe filters we used above. For instance there's a handy date filter if you'd like to display your post date in something a little sexier than a UNIX timestamp.

Here's what that would look like using the "date" filter:

{{object.pub_date|date:"D d M Y"}}

Another great filter is the "timesince" filter which will automatically convert a date into syntax like "1 week, 2 days ago" and so on.

There are also filters for escaping ampersands, escaping javascript, adding linebreaks, removing HTML, converting to upper/lowercase and dozens more. In fact in two and a half years of building sites with Django I've only needed to write a custom filter once. Naturally your mileage may vary somewhat.

==Roll Your Own Template Tags==

One thing I do frequently do is write custom template "tags." Template tags are perhaps one of the more confusing aspects of Django since they still have a little bit of "magic" in them, but luckily it isn't too hard to work with.

Template tags are a way of extending the Django template system to use it in project specific ways. For instance, custom template tags are a perfect way to handle things that don't make sense in a view, but do require some database logic. 

Perhaps the best example is something like a sidebar. So far ours is empty, but we can easily add a list of our most recent blog posts. 

Now we could write a filter that specifically fetches blog entries, but then what happens when we add links in the next lesson and want to display the most recent links? Write another template filter? That's not very DRY so let's just write a filter that fetches the most recent entries from any model with a date field.

In fact we don't even need to really write it. James Bennett has already [http://www.b-list.org/weblog/2006/jun/07/django-tips-write-better-template-tags/ written some great reusable code] so we'll just use that. I strongly recommend that you have read through James' tutorial so you can see how and why this code works.

Open a new file and paste in this code:

<pre>
from django.template import Library, Node
from django.db.models import get_model
     
register = Library()
     
class LatestContentNode(Node):
    def __init__(self, model, num, varname):
        self.num, self.varname = num, varname
        self.model = get_model(*model.split('.'))
    
    def render(self, context):
        context[self.varname] = self.model._default_manager.all()[:self.num]
        return ''
 
def get_latest(parser, token):
    bits = token.contents.split()
    if len(bits) != 5:
        raise TemplateSyntaxError, "get_latest tag takes exactly four arguments"
    if bits[3] != 'as':
        raise TemplateSyntaxError, "third argument to get_latest tag must be 'as'"
    return LatestContentNode(bits[1], bits[2], bits[4])
    
get_latest = register.tag(get_latest)

</pre>

Now save that file in a new folder within the blog app, named templatetags. The folder name and location are important since Django only looks up template tags in specific locations.

One thing to note about this code, if you look closely you'll notice that our template tag is going to fetch all entries, not just the public ones. In other words, our model allows for draft posts, but our template tag doesn't.

This is the line in question:

<pre>
self.model._default_manager.all()
</pre>

There are two ways around this, one is quick and dirty: just change that line to filter only published entries. In other words:

<pre>
self.model._default_manager.filter(status=1)
</pre>

The better and cleaner way to do it would be overwrite the default manager for our Entry model. However, that's a little beyond the scope of this article, so for now we'll just use the quick and dirty method.

Now open up base.html again and add this line at the top:

<pre>
{% load get_latest %}
</pre>

That tells Django to load the template tag, but then we need to grab the actual data, so head down to the sidebar section and replace it with this code:

<pre>
<div id="secondary">
	<h3>Recent Entries:</h3>
	{% get_latest blog.Entry 5 as recent_posts %}
    <ul class="archive">
    	{% for obj in recent_posts %}
		<li>
			<a href="{{ obj.get_absolute_url }}" title="Permanent Link to {{ obj.title}}">{{ obj.title}}</a>
		</li>
	</ul>
<div>
</pre>

Now if you want to override that in templates that are inheriting from base.html, just wrap that code in a {% block %} tag and then replace it with something else in the new template.

== Conclusion ==

The Django template system is quite vast in terms of capabilities and we've really just scratched the surface. Make sure you [http://www.djangoproject.com/documentation/templates_python/ read through the documentation for Python programmers] as well as [http://www.djangoproject.com/documentation/templates/ the documentation for template authors] and familiarize yourself with all the various built in tags and filters. Another great article is Jeff Croft's [http://jeffcroft.com/blog/2006/feb/21/django-templates-an-introduction/ Django Templates: An Introduction].

It's worth noting that there's an extensive collection of useful filters and template tags on the [http://www.djangosnippets.org/ Django Snippets site], so if you ever find yourself needing a custom tag or filter, have a look through there and see if anyone has already written something that works for your project.

I should also point out that if you just don't for whatever reason like Django's template system, it's possible drop in your template language, like [http://cheetahtemplate.sourceforge.net/ Cheetah] or others. 

Be sure to stop by for our next installment when we'll tackle a plan to import, store and display our del.icio.us bookmarks.