1
=============================================
3
=============================================
4
Creating a Threaded Comment System for a Blog
5
---------------------------------------------
10
Many modern websites have the need for a threaded commenting system. Places
11
like Digg_ and Reddit_ have great systems for responding not only to an item on
12
the site, but also for responding to comments on that item as well.
14
.. _Digg: http://www.digg.com/
15
.. _Reddit: http://www.reddit.com/
17
*threadedcomments* is a Django application which allows for the simple creation
18
of a threaded commenting system. It is flexible as well, partly due to its use
19
of the same facilities that any other Django application would use. For
20
instance, it would be trivial to integrate django-voting_ to rank each comment.
21
That, however, is a tutorial for another day.
23
This tutorial will get you up and running with a threaded comment system for the
26
.. _django-voting: http://code.google.com/p/django-voting/
28
Goals of This Tutorial
29
======================
31
There are three main goals that this tutorial sets out to meet. After having
32
completed this tutorial, you should:
34
1. Know how to use *threadedcomments* to implement a basic threaded comment
36
2. Have a very good idea on how to integrate this comment system with your blog.
38
3. Be able to simply and easily migrate an existing ``django.contrib.comments``
39
installation to use *django_threadedcomments* instead.
41
Step 1: Defining our Blog Model
42
===============================
44
Before we can even begin to use *django_threadedcomments*, we must first create
45
a simplistic "blog" model. This should be very familiar to anyone who has ever
46
written a basic blog model in Django before.
48
We'll define our simplistic blog model as follows::
50
from django.db import models
51
from datetime import datetime
53
class BlogPost(models.Model):
54
title = models.CharField(max_length=128)
55
slug = models.SlugField(prepopulate_from=('title',))
56
body = models.TextField()
57
published = models.BooleanField(default=True)
58
date_posted = models.DateTimeField(default=datetime.now)
60
def __unicode__(self):
66
There shouldn't be anything that's too tricky so far in this ``BlogPost`` model.
67
If you're having a hard time understanding what's going on here, you may want to
68
first check out the `django tutorial`_ or the `django book`_.
70
If you do understand how this model works, go ahead and use the admin or shell
71
to create some sample blog posts. We'll be needing them in the following steps
74
.. _`django tutorial`: http://www.djangoproject.com/documentation/tutorial01/
75
.. _`django book`: http://www.djangobook.com/en/1.0/
77
Step 2: Creating a View for the Sample Blog
78
===========================================
80
There are many different choices when it comes to displaying and creating
81
comments. Some people choose to show comments in a separate window. Others
82
choose to display them on the same page as the content object. Others still
83
require users to go to a special comments page to view and create comments.
85
Our approach here is going to be to display the comments directly below the blog
86
post, and directly below the comments will be a form for submitting a new
89
Our example will be for the "latest" blog post. The view is as follows::
91
from models import BlogPost
92
from django.template import RequestContext
93
from django.shortcuts import render_to_response
94
from django.http import Http404
96
def latest_post(request):
98
post = BlogPost.objects.latest('date_posted')
99
except BlogPost.DoesNotExist:
101
return render_to_response(
102
'blog/latest_post.html', {'post' : post},
103
context_instance = RequestContext(request)
106
Step 3: Configuration
107
=====================
109
First, make sure that django_threadedcomments is installed and is located
110
somewhere on your pythonpath. Then, in your ``settings.py`` file, append
111
the string ``'threadedcomments'`` to the end of your ``INSTALLED_APPS`` tuple.
113
In the end, your ``INSTALLED_APPS`` should look something like this::
116
'django.contrib.sessions',
117
'django.contrib.auth',
118
'django.contrib.contenttypes',
119
'django.contrib.sites',
120
'django.contrib.admin',
125
Next, make sure that in one of your included url configuration files (preferably
126
your ``ROOT_URLCONF``) you include 'threadedcomments.urls'.
128
In the end, your ``urls.py`` file may look something like this::
130
from django.conf.urls.defaults import *
132
urlpatterns = patterns('',
133
(r'^blog/', include('blog.urls')),
134
(r'^admin/', include('django.contrib.admin.urls')),
135
(r'^threadedcomments/', include('threadedcomments.urls')),
138
Make sure to run ``python manage.py syncdb`` to finish off this step.
140
Step 4: Template Creation
141
=========================
143
Believe it or not, in this step we're going to not only create a template with
144
which we can view our latest post, but we're also going to completely integrate
145
*django_threadedcomments*. Note also that in our sample template, we are not
146
using any of the extremely advisable feature of template inheritance. The only
147
reason that we are not doing so is to simplify the tutorial itself. In
148
practice, please take advantage of this feature.
150
Now with that said, let's begin to create our template. We'll start like so::
152
{% load threadedcommentstags %}
155
<head><title>{{ post.title }}</title></head>
158
<h1>{{ post.title }}</h1>
159
<h3>Posted On: {{ post.date_posted|date }}</h3>
160
{{ post.body|linebreaks }}
162
<h3>Comments on This Post:</h3>
163
{% get_free_threaded_comment_tree for post as tree %}
164
{% for comment in tree %}
165
<div style="margin-left: {{ comment.depth }}em;" class="comment">
166
<a href="{{ comment.website }}">{{ comment.name }}</a> said:<br/>
167
{% auto_transform_markup comment %}
170
<p>Reply to Original:</p>
171
<form method="POST" action="{% get_free_comment_url post %}">
173
{% get_free_threaded_comment_form as form %}
175
<li><input type="submit" value="Submit Comment" /></li>
181
There are three main parts to this template:
183
1. **Displaying the blog post.**
185
This part is nothing tricky: the title, date, and body are displayed.
187
2. **Getting and displaying the comment tree.**
189
This part is much more interesting. A new template tag,
190
``get_free_threaded_comment_tree`` is called on the ``post`` object, and
191
then the results are stored in a context variable called ``tree``. We
192
iterate over that tree using a simple ``for`` tag and for each comment we
193
provide a link to the commenter's website and display their name.
195
We then call a tag named ``auto_transform_markup`` on the comment. This tag
196
looks at the markup type and calls the appropriate filter on the body of the
197
post before displaying it.
199
4. **Providing a form with which the user can reply to the original post.**
201
We use ``get_free_threaded_comment_form`` to provide an unbound form to
202
display to the user. Because of that, we can simply call write
203
``{{ form.as_ul }}`` and we've got a beautiful, accessible form displayed.
205
The ``get_free_comment_url`` template tag gets the appropriate URL to post
206
to, given an object. All appropriate form submissions to that URL will then
207
be attached to that given object. In this case, we want to attach the
208
comments to the blog post, so that's the argument that we provide.
210
There's still a **crucial** piece missing: the ability to reply to other
211
people's comments! This is where some javascript will help us out a lot.
213
Let's decide on some Javascript functions which will do the trick:
215
``function show_reply_form(comment_id, url, person_name){ ... }``
216
This takes in the ID of the comment for which to add a reply form, the URL
217
to POST to, and the person's name to whom you are replying.
219
It dynamically inserts into the HTML DOM a FORM element which will POST to
222
``function hide_reply_form(comment_id, url, person_name){ ... }``
223
This takes in the same parameters and instead of dynamically creating a FORM
224
element, it dynamically removes the element.
227
Unfortunately there was not enough space here to display the full Javascript
228
functions, but you can view `the full template here`_ to see how one might
229
implement this functionality.
231
.. _`the full template here`: http://django-threadedcomments.googlecode.com/svn/trunk/examples/tut1/blog/templates/blog/latest_post.html
233
Now that we've decided on some Javascript functions to create our response
234
forms, we'll have to call those functions from our HTML. We'll do so by adding
235
a link directly underneath ``{% auto_transform_markup comment %}``. It'll look
238
<a id="c{{ comment.id }}" href="javascript:show_reply_form('c{{ comment.id }}','{% get_free_comment_url post comment %}','{{ comment.name }}')">Reply</a>
240
That's one long link right there! What we're doing here is dynamically
241
creating a Javascript function call to ``show_reply_form`` using Django's
242
context variables as parameters for ``show_reply_form``. Note that
243
``get_free_comment_url`` takes a second argument--the parent comment. In this
244
case, we're creating a response form, so the parent comment to the reply will
245
be the comment that we're currently displaying. Once Django has processed this
246
template, the result will look something like this::
248
<a id="c1" href="javascript:show_reply_form('c1','/threadedcomments/freecomment/12/1/1/','Tony Hauber')">Reply</a>
250
In this case, we're calling ``show_reply_form`` for comment ``c1``, the URL to
251
POST to is ``/threadedcomments/freecomment/12/1/1/``, and the person's name
252
that we'd like to respond to is ``Tony Hauber``.
254
Step 5: Upgrading from ``django.contrib.comments``
255
==================================================
257
*django_threadedcomments* provides a simple management command to allow for
258
the simple upgrading of the built-in ``django.contrib.comments``. To use it,
259
simply open a console, navigate to your project directory, and type::
261
python manage.py migratecomments
263
Running this will convert all ``django.contrib.comments.models.Comment``
264
comments to ``threadedcomments.models.ThreadedComment`` comments, and all
265
``django.contrib.comments.models.FreeComment`` comments to
266
``threadedcomments.models.FreeThreadedComment`` comments.
268
Obviously, there can be no hierarchy data associated with those legacy comments,
269
but future commenters can reply to those legacy comments just fine.
274
We've finished! Now it's up to you to style the page according to your wishes,
275
or even better, apply the techniques from this tutorial on your own blog or web
276
application. For a live example of this, `visit this link`_. Try it out for
277
yourself and even leave comments on that page, if you wish.
279
.. _`visit this link`: http://www.eflorenzano.com/threadedcomments/example/
b'\\ No newline at end of file'