Welcome to part 6, the final post on the development of the new shoutbox. In case you missed the earlier parts, here they are: part 1, part 2, part 3, part 4, part 5. Here we are going to discuss the final feature: deleting a shout in place off the shout history page.
The idea here is to add a delete link on the shouts that the user created as she is viewing the shout history. We will use the jQuery library to dynamically add click handlers to these links. The click handler functions will make an AJAX post to the server and request that a shout be deleted. The server view function will verify that the user sending the request actually created the shout in question. If so, the shout will be deleted from the database and then a response sent back to the client. This response will be then be used by our Javascript to hide the deleted shout. Thus we will have deleted a shout without refreshing the entire page.
The first thing I did was modify the shout history template to output a link underneath a shout if the current user authored that shout. This template (shoutbox/view.html) was presented in part 5. I’ll just show the new code inside the for loop that adds these links:
{% ifequal user shout.user %}
| <a href="#" class="shout-del" id="shout-del-{{ shout.id }}">Delete</a>
{% endifequal %}
The important thing to note is our links don’t go anywhere, they have the CSS class “shout-del”, and we give them an id of “shout-del-xx” where xx is the id of the shout from the database.
I then modified the shoutbox_app.js document ready function to look for all links that have the class “shout-del” and I attach a click function to them. The click function asks the user via the Javascript confirm() function if they really want to delete the shout. If they confirm, I then use javascript regular expression magic to pull out the shout’s primary key from the <a> tag’s id, and send a POST request to the server to delete that shout. The relevant snippet is shown below.
$('.shout-del').click(function () {
if (confirm('Really delete this shout?')) {
id = this.id;
if (id.match(/shout-del-(\d+)/)) {
$.post('/shout/delete/', { id : RegExp.$1 }, function(id) {
id = '#shout-del-' + id
$(id).parents('tr').hide();
$('div.shoutbox-history table tr:visible:even').removeClass('odd');
$('div.shoutbox-history table tr:visible:odd').addClass('odd');
}, 'text');
}
}
return false;
});
We are posting to the URL /shout/delete, and passing along the shout ID in the id parameter. We also supply a callback function that gets called when the delete happens successfully. More on that later. Finally the click function returns false so that the default action of clicking on a link is not executed.
We now turn to the server, and examine the view function that handles the URL /shout/delete. You’ll notice similar code as presented in part 5′s edit view function. This time I have discovered Django’s classes that derive from HttpResponse, and I use them in the error cases.
def delete(request):
"""This view deletes a shout. It is called by AJAX from the shoutbox history view."""
if request.user.is_authenticated():
id = request.POST.get('id', None)
if id is None:
return HttpResponseBadRequest()
try:
shout = Shout.objects.get(pk=id)
except Shout.DoesNotExist:
return HttpResponseBadRequest()
if request.user != shout.user:
return HttpResponseForbidden()
shout.delete()
return HttpResponse(id)
return HttpResponseForbidden()
We check to see if the user is authenticated before proceeding. We then retrieve the shout ID from the POST QueryDict. From the ID we retrieve the actual shout from the database using the Django ORM. And finally, if and only if the user making the request is the same user that created the shout, we delete it. If all of that went well, we send back a positive HTTP response that includes the ID of the shout that was deleted.
Now refer back to our Javascript, posted above. In the callback function we receive the ID as text from the server. We then convert the numeric ID to the CSS ID and use jQuery to select the parent <tr> of our <a> tag, and hide it. Wow! That was a very compact way to do all of that work.
The remaining logic takes care of the table presentation. I used CSS to style every other row in the shout history table with a class called “odd”. Since we just hid one row, that scheme got messed up. Luckily, the compact notation of jQuery again comes to the rescue and we can quickly restyle all odd visible rows with our class “odd”, taking care to remove the style from the new even rows first.
And there we have a shoutbox written in Django and Javascript for the new version of my site. Not bad for my first attempt, and it certainly was much easier than I imagined. All in all, there is a lot less Python code than the existing PHP code, and we even get neat-o AJAX’y features that the old one didn’t have. So I think we will go with this for the first pass of the site. Once I get more comfortable with jQuery I may decide to redo the scrolling itself with it. We could also look into posting shouts to the database without a complete page refresh. There is always something to go back and refactor as you learn more about your tools and best practices. I hope someone found this quick series of posts useful.