Building A Blog with Django #3

Posted by Ross Poulton on Fri 17 February 2006 #geeky #django #programming

NOTE: The Python code in this tutorial no longer works with Django! Please read my new article, a Django Blog Redux, for code that works on newer versions of Django. The rest of this article, such as the theory, is still very much applicable and should be read alongside my newer code.


In my two previous posts on this topic (Part 1, Part 2), I walked through creating a simple Blog application using the utilities provided by Django.

In this, the third part of the guide, I will show you how to let your visitors leave comments on each of your postings using the Django comments framework. Then, I'll introduce a quick template and urlconf to let you see all of the posts associated with a given tag, so that your users can pick a subject and see what other posts are related to it.

I'm going to just assume you've already got Django running, and the previous two parts of this guide completed and working. This section just builds on what has already been done.

Adding Comments To Your Blog

Django has a very cool (but undocumented, other than a line saying "We have a comments system that's easy to use!") comments system, that really is quite easy to use, it's just difficult to figure out to begin with. The comments system is pretty much plug-and-play, and has two modes to run in. The first mode only allows registered users to leave comments. The second mode allows anybody to enter their name and leave comments. In this case, I don't use public user accounts, so will not be able to use the first option. This leaves me with the 'free comments' - an option that is very easy to use, but beware of spammers leaving dud links (you can easily delete them via the Admin screens).

Open up your urls.py file, and somewhere in the urlpatterns section add the following line:

    (r'^comments/', include('django.contrib.comments.urls.comments')),

All this does is tell Django's URL parser that any requests for 'comments/*' should be sent to another url configuration file, in django/contrib/comments/urls/comments.py. Don't worry, this file is a part of Django, there is nothing else you have to do to 'turn it on'. Now open up your settings.py file, and add 'django.contrib.comments' into your INSTALLED_APPS. From the command-line, run 'django-admin.py install comments' to initialise the comments application. Note: Newer versions of Django don't use django-admin.py, instead type 'python manage.py install comments'.

Next, a number of templates need to be created to be used by the comments system. In your templates directory, create a new directory named 'comments' - this is the name Django will expect to find, and will be searching for. Inside this folder you need two templates: One to show a preview of a new comment, and one to thank the user for posting their comment (along with a link to take them back to where they came from). The first of these, the comment preview, has to be named free_preview.html. In my case this contains the following:

    {% extends "base" %}
    {% block content %}
    <h1>Preview your comment</h1>
    <form action="../postfree/" method="post">
    {% if comment_form.has_errors %}
        <p><strong style="color: red;">Please correct the following errors.</strong></p>
    {% else %}
        <div class="comment">
        {{ comment.comment|escape|urlizetrunc:"40"|linebreaks }}
        <p>Posted by <strong>{{ comment.person_name }}</strong></p>
        </div>

        <p><input type="submit" name="post" value="Post public comment" /></p>

        <h1>Or edit it again</h1>
    {% endif %}

    {% if comment_form.person_name.errors %}
        {{ comment_form.person_name.html_error_list }}
    {% endif %}
    <p><label for="id_person_name">Your name:</label> {{ comment_form.person_name }}</p>

    {% if comment_form.comment.errors %}
    {{ comment_form.comment.html_error_list }}
    {% endif %}

    <p><label for="id_person_name">Comment:</label><br />{{ comment_form.comment }}</p>
    <input type="hidden" name="options" value="{{ options }}" />
    <input type="hidden" name="target" value="{{ target }}" />
    <input type="hidden" name="gonzo" value="{{ hash }}" />
    <p><input type="submit" name="preview" value="Preview revised comment" /></p>
    </form>

    {% endblock %}

This template looks slighty busy but it really isn't. If the user has posted a valid comment (eg, they have included a name and comment with no errors), the comment is displayed to them with a button saying 'Post public comment'. Clicking this button saves the comment and shows them the 'thank you' page. Underneath the comment preview is another form used to let the user edit their comment, and if there are any errors in their submission, they'll be included in here. Clicking 'Preview revised comment' takes them back to the preview page, along with the updated comment.

Next is the 'thank you' page that confirms their comment has been posted. This page will have access to an 'object' variable for the actual content that prompted the comment to be posted, in this case the blog posting. We simply pick up the object and use it to send the user back to where they came from. Create a file named posted.html in your templates/comments/ folder and drop the following into it:

    {% extends "base" %}

    {% block content %}

    <h1>Comment posted successfully</h1>

    <p>Thanks for contributing.</p>

    {% if object %}
    <ul>
    <li><a href="{{ object.get_absolute_url }}">View your comment</a></li>
    </ul>

    {% endif %}

    {% endblock %}

Simple, huh? That's the behind the scenes pages setup and ready to go. Now all we need to do is give the user a way to add a comment to a particular post.

Doing this just requires editing your existing blog templates. Right now, your posts_detail.html templete simply shows the body of the post, and an image if one exists. Underneath the detail of the blog post in the template (eg under the block that displays the tags used for this post, but before the end of the block), add the following code:

    {% load comments %}

    {% get_free_comment_list for blog.posts object.slug as comment_list %}
    <h2 id="comments">Comments</h2>
    {% for comment in comment_list %}
            <div class="comment" id="c{{ comment.id }}">
                    <p><b>{{ comment.person_name }}</b> commented, on {{ comment.submit_date|date:"F j, Y" }} at {{ comment.submit_date|date:"P" }}:</p>
                    {{ comment.comment|escape|urlizetrunc:40|linebreaks }}
            </div>
    {% endfor %}

    <h2>Post a comment</h2>
    {% free_comment_form for blog.posts object.slug %}
    {% endblock %}

What this will do, is firstly load the comments module into your template, then fetch a list of comments for the current page, saving the list into a variable called comments_list. Then, using a for loop, each of the comments is printed with some nice formatting. Lastly, right at the end of a page, we use a django tag called free_comment_form to display a form to the user, letting them enter a comment. That's all there is to do, Django does the rest. Fire up your blog, look at a post, and you'll see the comment posting form. Enter something in there, confirm it, and return to that post - wallah, your comment has appeared!

Listing Posts by Tag

This last part of the article shows you how you can quickly list all the articles for a given tag. I've got this in use here on my site, you can see for example all posts related to Django.

To do this, we'll use a Django Generic View. We will be doing a 'detail' view of a tag, and then in the template, we can fetch a list of associated posts. To get started, open up your urls.py and, near the top of the file, add in the following dictionary:

    tag_dict = {
            'app_label': 'blog',
            'module_name': 'tags',
            'slug_field': 'slug',
    }

Next, further down the urls.py, add the following to your urlpatterns:

    (r'^tag/(?P[A-Za-z-_]+)/$', django.views.generic.list_detail.object_detail', dict(tag_dict, slug_field='slug')),

What this will do is tell Django that any request for /tag/django/ should be sent to the generic object detail view, using application 'blog', module 'tags', and slug field 'slug' within that module. All that's left now is to create a template for this: In your templates/blog/ directory, create a file named tags_detail.html and paste into it the following:

    {% extends "base" %}

    {% block content %}
    <h1>Posts with tag '{{ object.title|escape }}'</h1>
    {% for post in object.get_post_list %}
            <h2><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h2>

            {% if post.image %}<img align="right" src="{{ post.get_image_url }}" /> {% endif %}

            <p>{{ post.body|truncatewords:80 }}</p>

            <div class="article_menu"><b>Posted on {{ post.date|date:"F j, Y" }}</b> <a href="{{ post.get_absolute_url }}#comments">View/Add Comments</a><br />Tags: {% for tag in post.get_tag_list %}{% if not forloop.first %}, {% endif %}{{ tag.title }}{% endfor %}</div>

    {% endfor %}
    {% endblock %}

What this does is pretty straightforward: it gets a list of posts with the selected tag, and displays them in the familiar format. Clicking a post title will display that post (including, if you completed the steps above, the comments for that post).

That's all there is to it! In the next post, I'll cover the addition of RSS feeds to your blog (including a site-wide feed, as well as a per-tag feed). After that, I'll post an article on using Django's template tags to do a sidebar-style menu that contains a list to each of your tags, as well as a link to each month that contains posts. Best of luck until then!