Django’s comments framework

Django includes a simple, yet customizable comments framework. The built-in comments framework can be used to attach comments to any model, so you can use it for comments on blog entries, photos, book chapters, or anything else.

Quick start guide

To get started using the comments app, follow these steps:

  1. Install the comments framework by adding 'django.contrib.comments' to INSTALLED_APPS.

  2. Run manage.py syncdb so that Django will create the comment tables.

  3. Add the comment app’s URLs to your project’s urls.py:

    urlpatterns = patterns('',
        ...
        (r'^comments/', include('django.contrib.comments.urls')),
        ...
    )
    
  4. Use the comment template tags below to embed comments in your templates.

You might also want to examine Comment settings.

Comment template tags

You’ll primarily interact with the comment system through a series of template tags that let you embed comments and generate forms for your users to post them.

Like all custom template tag libraries, you’ll need to load the custom tags before you can use them:

{% load comments %}

Once loaded you can use the template tags below.

Specifying which object comments are attached to

Django’s comments are all “attached” to some parent object. This can be any instance of a Django model. Each of the tags below gives you a couple of different ways you can specify which object to attach to:

  1. Refer to the object directly – the more common method. Most of the time, you’ll have some object in the template’s context you want to attach the comment to; you can simply use that object.

    For example, in a blog entry page that has a variable named entry, you could use the following to load the number of comments:

    {% get_comment_count for entry as comment_count %}.
    
  2. Refer to the object by content-type and object id. You’d use this method if you, for some reason, don’t actually have direct access to the object.

    Following the above example, if you knew the object ID was 14 but didn’t have access to the actual object, you could do something like:

    {% get_comment_count for blog.entry 14 as comment_count %}
    

    In the above, blog.entry is the app label and (lower-cased) model name of the model class.

Displaying comments

To display a list of comments, you can use the template tags render_comment_list or get_comment_list.

Quickly rendering a comment list

The easiest way to display a list of comments for some object is by using render_comment_list:

{% render_comment_list for [object] %}

For example:

{% render_comment_list for event %}

This will render comments using a template named comments/list.html, a default version of which is included with Django.

Rendering a custom comment list

To get the list of comments for some object, use get_comment_list:

{% get_comment_list for [object] as [varname] %}

For example:

{% get_comment_list for event as comment_list %}
{% for comment in comment_list %}
    ...
{% endfor %}

This returns a list of Comment objects; see the comment model documentation for details.

Linking to comments

To provide a permalink to a specific comment, use get_comment_permalink:

{% get_comment_permalink comment_obj [format_string] %}

By default, the named anchor that will be appended to the URL will be the letter ‘c’ followed by the comment id, for example ‘c82’. You may specify a custom format string if you wish to override this behavior:

{% get_comment_permalink comment "#c%(id)s-by-%(user_name)s"%}

The format string is a standard python format string. Valid mapping keys include any attributes of the comment object.

Regardless of whether you specify a custom anchor pattern, you must supply a matching named anchor at a suitable place in your template.

For example:

{% for comment in comment_list %}
    <a name="c{{ comment.id }}"></a>
    <a href="{% get_comment_permalink comment %}">
        permalink for comment #{{ forloop.counter }}
    </a>
    ...
{% endfor %}

Warning

There’s a known bug in Safari/Webkit which causes the named anchor to be forgotten following a redirect. The practical impact for comments is that the Safari/webkit browsers will arrive at the correct page but will not scroll to the named anchor.

Counting comments

To count comments attached to an object, use get_comment_count:

{% get_comment_count for [object] as [varname]  %}

For example:

{% get_comment_count for event as comment_count %}

<p>This event has {{ comment_count }} comments.</p>

Displaying the comment post form

To show the form that users will use to post a comment, you can use render_comment_form or get_comment_form

Quickly rendering the comment form

The easiest way to display a comment form is by using render_comment_form:

{% render_comment_form for [object] %}

For example:

{% render_comment_form for event %}

This will render comments using a template named comments/form.html, a default version of which is included with Django.

Rendering a custom comment form

If you want more control over the look and feel of the comment form, you may use get_comment_form to get a form object that you can use in the template:

{% get_comment_form for [object] as [varname] %}

A complete form might look like:

{% get_comment_form for event as form %}
<table>
  <form action="{% comment_form_target %}" method="post">
    {% csrf_token %}
    {{ form }}
    <tr>
      <td colspan="2">
        <input type="submit" name="submit" value="Post">
        <input type="submit" name="preview" value="Preview">
      </td>
    </tr>
  </form>
</table>

Be sure to read the notes on the comment form, below, for some special considerations you’ll need to make if you’re using this approach.

Getting the comment form target

You may have noticed that the above example uses another template tag – comment_form_target – to actually get the action attribute of the form. This will always return the correct URL that comments should be posted to; you’ll always want to use it like above:

<form action="{% comment_form_target %}" method="post">

Redirecting after the comment post

To specify the URL you want to redirect to after the comment has been posted, you can include a hidden form input called next in your comment form. For example:

<input type="hidden" name="next" value="{% url 'my_comment_was_posted' %}" />

Providing a comment form for authenticated users

If a user is already authenticated, it makes little sense to display the name, email, and URL fields, since these can already be retrieved from their login data and profile. In addition, some sites will only accept comments from authenticated users.

To provide a comment form for authenticated users, you can manually provide the additional fields expected by the Django comments framework. For example, assuming comments are attached to the model “object”:

{% if user.is_authenticated %}
    {% get_comment_form for object as form %}
    <form action="{% comment_form_target %}" method="POST">
    {% csrf_token %}
    {{ form.comment }}
    {{ form.honeypot }}
    {{ form.content_type }}
    {{ form.object_pk }}
    {{ form.timestamp }}
    {{ form.security_hash }}
    <input type="hidden" name="next" value="{% url 'object_detail_view' object.id %}" />
    <input type="submit" value="Add comment" id="id_submit" />
    </form>
{% else %}
    <p>Please <a href="{% url 'auth_login' %}">log in</a> to leave a comment.</p>
{% endif %}

The honeypot, content_type, object_pk, timestamp, and security_hash fields are fields that would have been created automatically if you had simply used {{ form }} in your template, and are referred to in Notes on the comment form below.

Note that we do not need to specify the user to be associated with comments submitted by authenticated users. This is possible because the Built-in Comment Models that come with Django associate comments with authenticated users by default.

In this example, the honeypot field will still be visible to the user; you’ll need to hide that field in your CSS:

#id_honeypot {
    display: none;
}

If you want to accept either anonymous or authenticated comments, replace the contents of the “else” clause above with a standard comment form and the right thing will happen whether a user is logged in or not.

Notes on the comment form

The form used by the comment system has a few important anti-spam attributes you should know about:

  • It contains a number of hidden fields that contain timestamps, information about the object the comment should be attached to, and a “security hash” used to validate this information. If someone tampers with this data – something comment spammers will try – the comment submission will fail.

    If you’re rendering a custom comment form, you’ll need to make sure to pass these values through unchanged.

  • The timestamp is used to ensure that “reply attacks” can’t continue very long. Users who wait too long between requesting the form and posting a comment will have their submissions refused.

  • The comment form includes a “honeypot” field. It’s a trap: if any data is entered in that field, the comment will be considered spam (spammers often automatically fill in all fields in an attempt to make valid submissions).

    The default form hides this field with a piece of CSS and further labels it with a warning field; if you use the comment form with a custom template you should be sure to do the same.

The comments app also depends on the more general Cross Site Request Forgery protection that comes with Django. As described in the documentation, it is best to use CsrfViewMiddleware. However, if you are not using that, you will need to use the csrf_protect decorator on any views that include the comment form, in order for those views to be able to output the CSRF token and cookie.