Whenever learning a new Web technology, I like to write a bulletin board application. It's easy, I have it memorized, and it gives me a chance to compare and contrast the new technology with what I already know. See here for the application written in Aquarium.
The thing that was hardest about using Django templates to write this application was the lack of functions and recursion in the templating engine. Recursion is really suitable for generating a hierarchy of messages. This post is about how I had to work around the problem. I got it to work, but it wasn't what I'd call elegant.
To use Django to generate a page that looks like this, you break it up into a few steps. First, you create a template that has everything other than the hierarchy of messages. I called it "index.html":
When I did this in Cheetah, I just had the main template, and another function in the template that could call itself recursively. Nonetheless, I was able to get it to work in Django, albeit in a non-elegant fashion.
The thing that was hardest about using Django templates to write this application was the lack of functions and recursion in the templating engine. Recursion is really suitable for generating a hierarchy of messages. This post is about how I had to work around the problem. I got it to work, but it wasn't what I'd call elegant.
To use Django to generate a page that looks like this, you break it up into a few steps. First, you create a template that has everything other than the hierarchy of messages. I called it "index.html":
{% comment %}Secondly, you pass the pre-generated hierarchy of messages as a chunk of HTML to the first template. Hence, in my view, I have code:
Output the messages, and output a message input form.
{% endcomment %}
{% extends "bulletinboard/vertical" %}
{% block vertical_body %}
<div class="container">
<div style="float: right">[<a href="">Start a New Thread</a>]</div>
<p><b>List of Messages:</b></p>
{% if not messages %}
<p>There are no messages yet.</p>
{% else %}
{{ messages }}
{% endif %}
</div><br />
{% endblock %}
return render_to_response('bulletinboard/index',where "show_message_hierarchy()" generates the big chunk of HTML. Next, I created a template, "showmessagehierarchy.html", that gets called recursively (although, naturally, it can't call itself recursively in Django):
{'messages': show_message_hierarchy()})
{% comment %}Note, the recursion happens with "child.recurse", which is a callback that the view sets up. Each child has a different callback--it's not possible to say "recurse(child)" because there are no function arguments in Django (unless you use a custom tag). "child.recurse" is not a method of "child"; it's just a function that I've attached to "child". Now, let's look at the view that pulls everything together:
This template is called recursively to output the bulletin board messages in
a nice hierarchy.
{% endcomment %}
<ul>
{% for child in children %}
<li>
<a href="/messages/{{ child.id }}/">{{ child.subject|escape }}</a>
{{ child.recurse }}
</li>
{% endfor %}
</ul>
def index(request):Here you can see "child.recurse", i.e. "message.recurse", getting setup: "message.recurse = create_recurse_callback(message.id)". "create_recurse_callback" is a closure that is used to curry the "message.id" so that you can call "show_message_hierarchy" without needing to pass an argument. I got that trick from function programming ;) Notice that, basically, the "show_message_hierarchy" function and the "showmessagehierarchy" template recursively call each other.
"""Put the list of messages in a nice hierarchical format."""
def show_message_hierarchy(parent=None):
"""Show the bulletin board messages in a nice hierarchy."""
children = message_dict.get(parent, [])
if not children:
return ""
c = Context({"children": children})
return template.render(c)
def create_recurse_callback(id):
"""This is a closure.
It curries the id for show_message_hierarchy. The "partial" function
doesn't exist yet in Python 2.4.
"""
def inner():
return show_message_hierarchy(id)
return inner
message_list = messages.get_list()
message_dict = {}
for message in message_list:
message.recurse = create_recurse_callback(message.id)
message_dict[message.parent_id] = message_dict.get(message.parent_id, []) + [message]
template = loader.get_template('bulletinboard/showmessagehierarchy')
return render_to_response('bulletinboard/index',
{'messages': show_message_hierarchy()})
When I did this in Cheetah, I just had the main template, and another function in the template that could call itself recursively. Nonetheless, I was able to get it to work in Django, albeit in a non-elegant fashion.
Comments
You'll note that that tag came out two years and two months after my post ;)
Posted by Shannon -jj Behrens at 11:23 AM
No date -> looks just like it was written today! I say Google has a usability issue right there :)
render message:
apply template to message
(template iterates over children)
This forces you to perform that 'call' from within the template -- the problem.
Have you considered generating the html starting at the leafs? generate the html there and pass that as an argument for rendering the parent message. thus:
render message:
for msg in message children
child html += render msg
apply template to message passing child html
return html
problem solved (me thinks).
def render_message(message):
___child_html = []
___for msg in message.children:
______child_html.append(render_message(msg))
___return render_to_string("message.html", {responses:child_html})
Sorry for the ugly dashes but I cannot post code.
What I have learned is that Django and I disagree on design. I tend to let my templates control their own flow, pulling in things as necessary--that's why I'm such a big fan of template functions. In Django, the Python view really needs to be in control of everything, and templates should only be used for mostly dumb chunks of HTML.