Code


Version 20 (modified by seemant, 7 years ago) (diff)

grammar fix

Using Django's Free Comments

Django includes some basic commenting functionality that can save you a ton of time if you want to allow users to add comments to objects such as blog entries or photos. Django's free comments are flexible enough that they can be used on pretty much anything.

Set Up

The first thing you'll need to do is to add Django's comment packages to your INSTALLED_APPS in "settings.py":

INSTALLED_APPS = (
    [...]
    'django.contrib.comments',
)

If you're using custom views and not generic views, you'll need to add the following to the top of the relevant "urls.py" file. Generic views already include FreeComment, so you don't have to import it yourself.

from django.contrib.comments.models import FreeComment

Add the following URL pattern to your site-wide "urls.py" file:

urlpatterns = patterns('',
        [...]
        (r'^comments/', include('django.contrib.comments.urls.comments')),
}

You will need to update your database with the comments model. Stop your webserver if it's running, then use the django admin tool with the syncdb command.

In any template file in which you want to access comments, you'll have to load the comments packages before you access them with:

{% load comments.comments %}

Below that, you're free to access the comments.

Adding Comment Counts to List and Archive Pages

The only data you need to access an object's comments is that object's id. If you have that, you can get the comments themselves, comment count, and other info.

django.views.generic.list_detail.object_list

In an object_list template, you can do the following to add the comment count to each of the blog entry listings. The following example assumes you have an app named "blog" with a class called "entry", which has fields called "title", "summary", and has a method called "get_absolute_url" which returns an absolute path to that entry's detail page.

Note: The class name should be always be referred to in all lowercase. For example, if the class contained inside the app named blog were actually named Entry, it would still be referred to as blog.entry.

<ul>
	{% for object in object_list %}
		{% get_free_comment_count for blog.entry object.id as comment_count %}
		<li>
			<h2><a href="{{ object.url }}">{{ object.title }}</a></h2>
			<p class="description">{{ object.summary}}</p>
			<p class="details"><a href="{{ object.get_absolute_url }}">{{ comment_count }} Comments</a></p>
		</li>
	{% endfor %}
</ul>

django.views.generic.date_based.archive_index

An archive_index generic view works almost exactly the same, except you're iterating through objects from the "latest" collection instead of the "object_list" collection:

<ul>
	{% for object in latest %}
		{% get_free_comment_count for blog.entry object.id as comment_count %}
		[...]
	{% endfor %}
</ul>

In the date-based archives such as archive_year or archive_month, the collection you iterate through is called "object_list". archive_index is the only generic view with "latest".

Adding Comments to Detail Pages

Typically, you'll allow users to add comments through the detail page for an object. It's possible to allow them to do it elsewhere, but for simplicity's sake, this example only shows how to do it on detail pages. For any of the "detail" generic views such as django.views.generic.list_detail.object_detail or django.views.generic.date_based.object_detail, you'll have the object's id as "object.id", so getting the comment count is the same:

{% get_free_comment_count for Blog.entry object.id as comment_count %}

To get the list of comments, you call the following, which puts the list of comments in "comment_list":

{% get_free_comment_list for blog.entry object.id as comment_list %}

Each comment object in comment_list has the following bits of data:

  • comment.person_name - The comment poster's name.
  • comment.submit_date - The date and time the poster submitted the comment. You can pipe this through the "date" filter to format the date. (Shown in the example below)
  • comment.comment - The actual comment text. Don't forget to escape this to prevent code-injection attacks with the "escape" filter. (Shown in the example below)
  • comment.is_public - Whether or not the comment is public. (TODO: How do we set a comment's public or non-public status?)
  • comment.ip_address - The comment author's IP address.
  • comment.approved - Whether or not this comment has been approved by a staff member. (TODO: Where is this set or modified?)

And the following built-in methods:

  • comment.get_absolute_url - Returns an absolute URL to this comment by way of the content object's detail page. If a comment is attached to a blog entry located at "/blog/some-slug", this URL will look something like "/blog/some-slug/#c4", where "4" is the comments id number.
  • comment.get_content_object - Returns the object that this comment is a comment on.

As of this writing, the free comments don't allow for you to specify other bits of data to be included, such as the comment poster's e-mail address or URL. This may be changed in the future.

Example

{% get_free_comment_count for blog.entry object.id as comment_count %}

<h2><a href="{{ object.url }}">{{ object.title }}</a></h2>

<em>{{ object.description }}</em>

<div class="article_menu">
	<b>Added on {{ object.add_date|date:"F j, Y" }}</b> 
	<a href="{{ object.get_absolute_url }}#comments">{{ comment_count }} Comment{{ comment_count|pluralize }}</a>
</div>

{% get_free_comment_list for blog.entry object.id as comment_list %}

<h2 id="comments">Comments</h2>
{% for comment in comment_list %}
	<div class="comment_{% cycle odd,even %}" id="c{{ comment.id }}">
		<span class="comnum"><a id="c{{ comment.id }}" href="#c{{ comment.id }}">#{{ forloop.counter }}</a></span>
		<p><b>{{ comment.person_name|escape }}</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.entry object.id %}

Free Comment Templates

Django has internal default templates for the various bits of comments-related code. (NOTE: Actually, I lied. Once these patches are applied, it will. Until then, you'll have to specify your own templates for "free_preview.html" and "posted.html".)

You can override any of these built in templates by creating a "comments/" folder in your templates folder with any or all of the following files:

Post Comment Form (freeform.html)

This template holds the form code that is used by the user to post a comment. In the above example, this is included like so:

<h2>Post a comment</h2>
{% free_comment_form for blog.entry object.id %}

Example

{% if display_form %}
	<form action="/comments/postfree/" method="post">
		<p>Your name: <input type="text" id="id_person_name" name="person_name" /></p>
		<p>Comment:<br /><textarea name="comment" id="id_comment" rows="10" cols="60"></textarea></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 comment" /></p>
	</form>
{% endif %}

In this Example change name="preview" to name="post" if you dont want to be redirected to a preview page

Preview (free_preview.html)

This template is called when a user previews their comment.

Example

<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 class="date small">Posted by <strong>{{ comment.person_name|escape }}</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|escape }}</p>

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

	<p>
		<label for="id_comment">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>

Posted Message (posted.html)

This template is shown after a user successfully posts a comment. You can access the object that the comment was posted to as the context variable "object".

Example

<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 %}

Wrapping post_free_comment to change the redirect url

At this time django.contrib.comments.views.comments has hard-coded, on its post_free_comment method, a redirection address to posted.html. If you want your comments to redirect to the same page you can wrap the post_free_comment method and add a redirect to another after the comment creation. The url can be passed as a hidden parameter in freeform.html To do this you first need to create a wrapper in views.py

from django.contrib.comments.views.comments import post_free_comment
from django.http import HttpResponseRedirect

def my_post_free_comment(request):
    if request.POST.has_key('url'):
        url = request.POST['url']
    else:
        url = '/comments/posted.html'
    response = post_free_comment(request)
    return HttpResponseRedirect(url)

After you have your view you need to point the requests to that view instead of the default comments view To do this add the following line to urls.py

(r'^comments/post/', 'views.my_post_free_comment'),

And change the form to include the url as a hidden field. The problem is: How do I get the current url in a template variable?. To do this you need to activate the template context processor called request adding the following code to settings.py

from django.conf.global_settings import TEMPLATE_CONTEXT_PROCESSORS
TEMPLATE_CONTEXT_PROCESSORS += (
     'django.core.context_processors.request',
) 

Once your request context processor is activated you will have the variable request available in your templates. With it you can get request.get_full_path to print the current url as follows:

{% if display_form %}
	<form action="/comments/post/" method="post">
		<p>Your name: <input type="text" id="id_person_name" name="person_name" /></p>
		<p>Comment:<br /><textarea name="comment" id="id_comment" rows="10" cols="60"></textarea></p>
		<input type="hidden" name="options" value="{{ options }}" />
		<input type="hidden" name="target" value="{{ target }}" /> 
		<input type="hidden" name="gonzo" value="{{ hash }}" />
		<input type="hidden" name="url" value="{{ request.get_full_path }}" />
		<p><input type="submit" name="post" value="Post" /></p>
	</form>
{% endif %}

This should be it. Enjoy

--NL

Other Examples

List Recent Comments

The following code lists all the recent comments on your site, regardless of app.

<h1>Recent comments</h1>

<p>
	{% if has_previous %}
		<a href="?page={{ previous }}">Previous</a> | 
	{% endif %}
	
	Page {{ page }} of {{ pages }} 
	
	{% if has_next %}
		| <a href="?page={{ next }}">Next</a>
	{% endif %}
</p>

{% for comment in object_list %}
	<div class="comment" id="c{{ comment.id }}">
	    <h3>
			<a href="{{ comment.get_absolute_url }}">
				{{ comment.person_name|escape }} 
				<span class="small quiet">
					{{ comment.submit_date|date:"F j, Y" }} at {{ comment.submit_date|date:"P" }}
				</span>
			</a>
		</h3>
	    {{ comment.comment|escape|urlizetrunc:"40"|linebreaks }}
	</div>
{% endfor %}

<p>
	{% if has_previous %}
		<a href="?page={{ previous }}">Previous</a> | 
	{% endif %}
	
	Page {{ page }} of {{ pages }} 
	
	{% if has_next %}
		| <a href="?page={{ next }}">Next</a>
	{% endif %}
</p>