And now we continue on with the shoutbox development. If you missed the earlier posts, here they are: part 1, part 2.
After getting the model and admin parts working (discussed in part 2), I turned to the view and template. I decided to create an inclusion tag for the shoutbox. My site’s base template will then use the tag to display the shoutbox on every page of the site.
Writing the inclusion tag itself was pretty easy. All I need to do is retrieve the last 10 shouts and render a template with the results. So, after studying the Django docs, I created my templatetags sub-directory, the obligatory empty __init__.py file, and my shoutbox_tags.py file, which contained this code:
from django import template
from shoutbox.models import Shout
register = template.Library()
@register.inclusion_tag('shoutbox/shoutbox.html', takes_context=True)
def shoutbox(context):
shouts = Shout.objects.all()[:10]
return {
'shouts': shouts,
'user': context['user'],
}
I’m using the newer Python decorator syntax here. The first argument to the inclusion_tag decorator function is the name of the template I am going to render. The needs_context argument set to True says that I will want to access the context. The reason for this is I want to display a form for authenticated users, so I will need to query the user variable from the context for that.
All I do here is retrieve the last 10 shouts, then return a dictionary that will be fed to my template.
Now to write the actual template itself. I wrote this template in a series of “baby-steps”, which is my usual mode of operation. My steps were as follows:
I should show you the template at each step, but I’m writing this after the fact, and it really didn’t change that much from beginning to end. I’ll present the final version here and discuss it.
{% extends 'side_block.html' %}
{% block block_title %}Shoutbox{% endblock %}
{% block block_content %}
{% if shouts %}
<div id="marqueecontainer" onmouseover="copyspeed=pausespeed" onmouseout="copyspeed=marqueespeed">
<div id="vmarquee" style="position: absolute; width: 98%;">
{% for shout in shouts reversed %}
<p>
<span class="shoutbox-user">{{ shout.user.username }}:</span>
<span class="shoutbox-shout">{{ shout.shout|urlizetrunc:15 }}</span>
<span class="shoutbox-date">{{ shout.shout_date|date:"D M d Y H:i:s" }}</span>
</p>
{% endfor %}
</div>
</div>
{% endif %}
<center><a href="{% url shoutbox-view page=1 %}">Shout History</a></center>
{% if user.is_authenticated %}
<center>
<form action="{% url shoutbox-shout %}" method="post">
<input type="text" maxlength="2048" size="20" name="msg" value="" />
<br />
<input type="submit" value="Shout" />
</form>
</center>
{% else %}
<p>Please login or register to shout.</p>
{% endif %}
{% endblock %}
First of all, this template extends another called side_block.html. I decided that all my “blocks” will have common styling and features, and I will collect that information there. This template will provide values for the block title and block content.
Lines 4-16 display the 10 latest shouts. The two divs are needed for the javascript scrolling, which I’ll explain in a minute, so you can ignore them for now. The for loop is the real action here. I simply loop over the shouts, producing a paragraph for each one. The ordering we setup for the shouts in the model is reverse chronological order. On the current SG101 site, the legacy shoutbox scrolls up, displaying the oldest of the 10 shouts first. To achieve this, we use the nifty reversed keyword on the for loop, which gives me exactly what I want.
The other interesting thing in the loop is the use of the urlizetrunc filter. As you may recall from part 1, one of my requirements is to minimize or truncate URL’s. I headed down the path of writing my own function to do this, and spent about half hour on it, trying to come to terms with Django’s escaping protocol, when it suddenly occurred to me that I can’t be the first person who had a use-case for this. So I quickly scanned the Django filter documentation, and sure enough, a function close enough to what I wanted was already in the can! The legacy shoutbox shortens URLs by simply displaying them as the text [URL]. The urlize filter doesn’t do this, but urlizetrunc is close enough to what I need that I used it as-is. It simply truncates URLs after a certain length.
The last half of the template decides whether to display a shout form to the user, depending on whether she is authenticated or not. If not authenticated, I simply display a reminder to login or register (hmm, I need to make those links to the appropriate pages). If authenticated, I display the shout form. I could have used a Django form here; perhaps the inclusion tag could have passed it to the template. However, the form is so simple I didn’t even bother. All I need is one text input and a submit button, which I would have had to write anyway.
I will now show you the view function that this form posts to. It is quite simple.
@login_required
def shout(request):
if request.method == 'POST':
msg = request.POST.get('msg', '')
if msg != '':
shout = Shout(user=request.user, shout=msg)
shout.save()
return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
The first thing to notice is I have decorated my view function with the login_required decorator. This ensures only registered users can shout. Sure, we only display the form to registered users, but that doesn’t stop someone from crafting their own form and posting to the same URL. I then pull the msg out of the POST Querydict, and let it default to the empty string if it isn’t there. If the msg isn’t empty, I create and save a new Shout object, recording the user that made it and the msg value. As you may recall, the shout_date field will be automatically initialized to the current date and time by Django when I save the new shout. I then redirect to the same page the user was looking at (recall that the shoutbox will appear on every page of the site). If this can’t be determined (which normally should not happen) I simply go to the site root.
With this machinery in place, I was able to submit shouts just like the current (legacy) shoutbox on SG101. Sweet! This was fun, but now it gets even better. How to make it scroll?
I had no interest in writing the scrolling code, so I spent a little while googling. I did not consider reusing the legacy shoutbox javascript, as it required the javascript to be sent with the page (it was dynamic), and the code wasn’t properly escaped and broke XHTML compliance. I really want to learn more about the jQuery library, and I did look for a jQuery plugin for this task. However I decided to save jQuery for another day, and found some nice code on the venerable Dynamic Drive website. This code was similar to the legacy shoutbox code, so I was comfortable with it. I liked it because it simply scrolled a div upwards, and required no inline javascript. I think the legacy shoutbox script must have used an earlier version of this same script because it was so similar. To integrate this with my template code, I simply had to wrap my shouts inside 2 divs, and include the javascript in my site’s base template. And wow it worked on the first try, much to my surprise. I was smiling at how quickly this came together.
Wow, we have nearly all the functionality that I wanted completed already. All that is left is the ability to review the shout history, and let users edit and delete their shouts. I completed the shout history portion, so I will show that in the next post. For editing and deleting, I think I might want to do something AJAX-y and let the user edit the shout in-place. Hmmm, maybe I will learn some jQuery to make that easier. And maybe then I can rewrite the scrolling with jQuery. More to come!
[...] In case you missed the earlier parts, here they are: part 1, part 2, part 3. [...]