Threaded Commenting System In Django (Posted on December 29th, 2012)

The goal of this project is to store a tree (comment thread) in a database. There are several ways to do this and a good list of references can be found at Stack Overflow. The algorithm I'll be using to create our threaded commenting system is called a Materialized Path.

With the Materialized Path algorithm each comment will store the path to itself. For instance, if we post a comment to an empty thread the path to the comment is {1}. Since there is only a single node in the path this also signifies that it is a parent node. If we post a reply to the parent node that comment will have the path {1, 2}. If another reply is posted to the parent node it will have the path {1, 3}. Finally we can post a comment to our second reply (first child) and it will have the path {1, 2, 4}. Here is a visual example of what the output should look like:

  • Comment 1 - Path: {1}
    • Comment 2 - Path: {1, 2}
      • Comment 4 - Path: {1, 2, 4}
    • Comment 3 - Path: {1, 3}

Requirements

  • Django 1.4+
  • Postgresql 8.x or 9.x (7.x may work as well but I haven't tested)
  • DBarray (Enables Postgresql arrays in Django)

The Setup

First things first, let's create a Django project with a core app:

django-admin.py startproject comments_tutorial && cd coments_tutorial
python manage.py startapp core

Now that our file structure is all setup lets update the settings file to include our database information. If you haven't created a blank database for this project yet go ahead and create one now. Here is a copy of my settings file for reference. I've added django.contrib.humanize and core to my installed apps and also told Django where it can find my templates. If you have a certain way you like to setup your settings feel free to do so but make sure to add the proper installed apps.

The Model

Download DBarray and place it inside the comments_tutorial/core app folder. I chose to download the init file and place it in a file called dbarray.py. Then open up comments_tutorial/core/models.py and edit it to look like mine:

from django.db import models
from django import forms
from dbarray import IntegerArrayField

class Comment(models.Model):
    content = models.TextField()
    date = models.DateTimeField(auto_now_add=True)
    path = IntegerArrayField(blank=True, editable=False)
    depth = models.PositiveSmallIntegerField(default=0)
    
    def __unicode__(self):
        return self.content
    
class CommentForm(forms.ModelForm):
    #Hidden value to get a child's parent
    parent = forms.CharField(widget=forms.HiddenInput(
                            attrs={'class': 'parent'}), required=False)
    
    class Meta:
        model = Comment
        fields = ('content',)

I have set the path field to be uneditable, otherwise you'll run in to errors should you decide to update comments from inside the Django admin panel. You'll notice a hidden parent field in the form. This is used when replying to comments. This field will be populated with the parent comment's ID so that we can store the proper path of the child node. This idea may make more sense once we've implemented our view function.

Since this is the only model we will be using it is safe to run a syncdb and create the tables for our app

python manage.py syncdb

The View

Go ahead and open up comments_tutorial/core/views.py and edit it to look like mine:

from core.models import Comment, CommentForm
from itertools import ifilter
from django.shortcuts import render

def home(request):
    form = CommentForm(request.POST or None)
    
    if request.method == "POST":
        if form.is_valid():
            temp = form.save(commit=False)
            parent = form['parent'].value()
            
            if parent == '':
                #Set a blank path then save it to get an ID
                temp.path = []
                temp.save()
                temp.path = [temp.id]
            else:
                #Get the parent node
                node = Comment.objects.get(id=parent)
                temp.depth = node.depth + 1
                temp.path = node.path
                
                #Store parents path then apply comment ID
                temp.save()
                temp.path.append(temp.id)
                
            #Final save for parents and children
            temp.save()
    
    #Retrieve all comments and sort them by path
    comment_tree = Comment.objects.all().order_by('path')
                
    return render(request, 'index.html', locals())

First up we tell Django to fill out or create a blank CommentForm. If the request to the method is POST that means that we are creating a comment and need to handle that accordingly. If the comment form is valid then we create a python object but do not save it to the database just yet (this is where commit=False comes in to play).

The next step is to check if the comment that is being created is a parent comment or a reply to another comment. If no value was sent in to our hidden field that means the comment is a parent comment so we give it a blank path and save it. If a value was sent then grab that node's information and apply it to the newly created comment. The database will return the ID of that comment which we then add to the comment's path. You'll notice that this step will require two calls to the database. If you drop down to raw sql you can manage to do it one by prepending the row ID to the path array when selecting comments. However, to keep things simple and use the ORM, we will make two calls to the database.

The great part about this setup is that to get the full comment tree we simply need to order by "path". This will make it really easy to output in HTML. This works because every path is unique since the final element in the array is the comment's primary key.

The URL

Open comments_tutorial/comments_tutorial/urls.py and route the homepage to our home view we just created like so:

from django.conf.urls import patterns, include, url

urlpatterns = patterns('',
    url(r'^$', 'core.views.home', name='home'),
)

The Template

In my settings file I told Django to look for the templates in a folder located at comments_tutorial/comments_tutorial/templates. So I just created the templates folder and have added a file called index.html to match our view's return method. Here is what your index.html file needs to look like (I have added some CSS to make it pretty):

{% load humanize %}
<!DOCTYPE html>
<html>
<head>
    <title>Comments Tutorial by Max Burstein</title>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
    <script>
        $(document).ready(function(){
            $("#commenters").on("click", ".reply", function(event){
                event.preventDefault();
                var form = $("#postcomment").clone(true);
                form.find('.parent').val($(this).parent().parent().attr('id'));
                $(this).parent().append(form);
            });
        });
    </script>
    <style>
        a {
            font-weight: bold;
            color: #ff982c;
            text-decoration: none;
        }

        a:hover {
            text-decoration: underline;
        }
        
        #commenters {
            padding-left: 0px;
        }
        
            #commenters li {
                list-style-type: none;
            }
        
        .poster {
            font-size: 12px;
            color: #AAAAAA;
        }
        
        #postcomment ul {
            padding-left: 0px;
        }
        
            #postcomment ul li {
                list-style-type:  none;
                padding-bottom: 5px;
            }
                
        #postcomment label {
            width: 74px;
            display: inline-block;
        }
        
        .c {
            font-size: 14px;
            background: #0E0E0E;
            -webkit-border-radius: 10px;
            -moz-border-radius: 10px;
            border-radius: 10px;
            color: #FFFFFF;
            padding: 10px;
            margin-bottom: 10px;
        }
    </style>
</head>
<body>
    <h1>Comments Tutorial by Max Burstein</h1>
    <form id="postcomment" method="post" action="">
        {{form.as_p}}
        <p><input type="submit" value="Submit" /></p>
        {% csrf_token %}
    </form>
    <ul id="commenters">
    {% for c in comment_tree %}     
        <li id="{{c.id}}" class="c" style="margin-left:{{c.depth|add:c.depth}}em;">
            <p class="poster">Anonymous - {{c.date|naturaltime}}</p>
            <p>{{c.content}}</p>
            <p><a href="" class="reply">reply</a></p>
        </li>
    {% empty %}
        <li>There are currently no comments. You can be first!</li>
    {% endfor %}
    </ul>
</body>
</html>

The body of the HTML starts by loading our comment form so that we can post parent comments. Below that it loops through all comments currently in the database and outputs them in a hierarchical order based on the depth of a comment (a parent comment has a depth of 0). The Django Humanize module converts our stored time to how many seconds, minutes or hours ago it was posted, kind of like Reddit. If there are no comments then we simply print no comments.

At the top I've imported jQuery and wrote a simple script to place a comment box below any comment we want to reply to. When the reply button is clicked we prevent the page from going anywhere and then make a clone of the comment box from the top of the page. We then take the ID of the node we clicked reply on and put that value in the hidden parent field we created with our ModelForm up above. When we hit submit from this form the view will recognize that this comment has a parent and will store the proper path for this comment.

Source Code

If you've followed along this far then you've reached the end. Congrats! As an exercise go ahead and add in a comment rating system, such as upvoting and downvoting. This article on how not to sort by average rating is a good read.

I have posted my source code for this project on Github. Feel free to ask me any questions. I'd be happy to help.

Tags: Django

Comments:

  • enigma - 2 years, 5 months ago

    Thanks for the great tutorial. I just like to ask one question: Is is possible to build it using mysql.

    reply

  • Max Burstein - 2 years, 5 months ago

    If you're using MySQL you can use the CommaSeparatedIntegerField model type for your path. Then when you pull the data you can just split by "," and you'll have your path array. You'll also need to do the sorting in python since the database will treat the column as a string I believe, so 10 will come before 2. I've never tried it though so if you do end up trying it let me know how it works out for you.

    reply

  • enigma - 2 years, 5 months ago

    Hello again and thanks for the help. After a lot of attempts and debugging, i have changed the code like this: # views.py def home(request): form = CommentForm(request.POST or None) if request.method == "POST": if form.is_valid(): temp = form.save(commit=False) parent = form['parent'].value() if parent == "": # set a blank path then save it to get an ID temp.path = [] temp.save() # converting ID to int because save() gives a long int ID id = int(temp.id) temp.path = [id] else: # get the parent node node = Comment.objects.get(id = parent) temp.depth = node.depth + 1 s = str(node.path) temp.path = eval(s) #store parents path than apply comment ID temp.save() id= int(temp.id) temp.path.append(id) temp.save() # here i have reversed the order comment_tree = Comment.objects.all().order_by("-path") return render(request, 'index.html', locals()) in models.py i have just replaced IntegerArrayField with CommaSeparatedIntegerField as you've told me. Now it looks working, but could you try it, because i feel that i missed some thing

    reply

  • enigma - 2 years, 5 months ago

    Sorry for the mess

    reply

  • Max Burstein - 2 years, 5 months ago

    Yea I really need to implement markup for comments on here. Sorry about that. Drop me your e-mail on my contact form. I'll definitely take a look at using MySQL for this. It would be awesome if it was that simple of a fix.

    reply

  • buy instagram comments and likes - 1 month ago

    hi was just seeing if you minded a comment. i like your website and the thme you picked is super. I will be back.

    reply

  • Schlüsseldienst Berlin - 3 weeks, 6 days ago

    i really like this article please keep it up.

    reply

  • Massage - 1 week, 3 days ago

    Essentially, massage remedy turns Over sympathetic stressed system, and turns Around the parasympathetic method. Thus, it relaxes our bodies and minimizes neuromuscular issues.

    reply

  • Men's Fashion - 1 week, 3 days ago

    Acquiring gents fashion tips are very important to increase any part of your attire. You is not going to believe the consequence what you might be wearing has on you and the ones around an individual, you can feel more confident and be appealing to be able to others.

    reply

  • Men’s Fashion - 1 week, 3 days ago

    In the recent write-up on gents fashion and also grooming no-no's at the job, they covered sets from backpacks to be able to comb-overs. Below are a few more gents fashion don'ts My partner and i thought have been missing from your list:

    reply

  • Shoes & Footwear - 1 week, 3 days ago

    Many mom and dad always choose the best garments and toys for babies. Nonetheless, most mom and dad neglect the feet. All babies if they start to be able to walk will be needing a couple of kid's child shoes.

    reply

  • Massage - 1 week, 3 days ago

    There are various types of massage, each making use of their own benefits to ease stress, pain or perhaps enhance general wellbeing. Massage can be an ancient fine art with several variations with regards to the country regarding origin.

    reply

  • Anonymous - 5 months, 3 weeks ago

    I bot

    reply

  • enigma - 2 years, 5 months ago

    Did you recieve my email

    reply

  • Max Burstein - 2 years, 5 months ago

    Yes I did. I sent off a response a few days ago. If you haven't received the response let me know and I'll resend it.

    reply

  • Andrew - 2 years ago

    Well I usually just grab code from blog posts and move on, but I have to comment here...

    This is some seriously good work and slick code. I ran into the issue of threaded comments last night and was unable to think of a good solution due to my coding inexperience. You sir have saved me a LOT of trouble with this code.

    Seriously. You're freaking awesome. Keep it up.

    • Andrew

    reply

  • Mahesh - 1 year, 10 months ago

    Thanks a lot

    reply

  • kannor - 1 year, 4 months ago

    Awesome post. Reading the source I have realised the application should work perfectly without the "path" model field. Since the comments can be arranged by "id" field. Can you please let me know some other use of the "path" field. Thank you.

    reply

  • Max Burstein - 1 year, 4 months ago

    You only need the path field if you want to make the comments threaded (can reply to specific comments). If you want to do Facebook style comments then you absolutely don't need the path field.

    You could get away with doing threaded comments without the path field by using Postgres' recursive calls to build out a path as long as you have the parent id. I know at one point Disqus used this technique. I find the path array much simpler as it also allows you to use the Django ORM and keeps your code base more uniform.

    reply

  • get more followers on twitter - 3 weeks, 2 days ago

    Good day I discovered your web blog around blunder after i ended up being wanting bing to get this acne dilemma, Most people must say your web blog is certainly handy Most people on top of that like the planning, it is really unbelievable!. Most people don’t have phase now to fully analysis your special site but I kept it and on top of that involve your special RSS OR ATOM AND ALSO ATOM rss or atom provides nourishment to. Soon we will be backside a day or even just couple of. thanks a lot to get a great webpage.

    reply

  • youtube subscriptions - 3 weeks, 2 days ago

    Hi I ran across your site by mistake when my partner and i continues to be searching for yahoo relating to this acne difficulty, My wife and i must say your site is totally helpful My wife and i moreover love the particular type, the particular great!. My wife and i don’t support the instant currently to be able to entirely analyze the particular site but We've publication noticeable it and moreover raise the ACTUALLY BASICALLY SYNDICATION nourishes. I will be once more in a evening or possibly a number of. many thanks to acquire a great internet site.

    reply

  • Threading - 1 year, 4 months ago

    just wanna comment on this one that its really well written and I enjoyed reading this

    reply

  • Anonymous - 1 year, 2 months ago

    I think you could save one DB operation by not saving the current comment in the path, for example:

    comment_1.path = (0) (its child) comment_2.path = (0,1) (just the get the parent's path and append the parent's id) (child of 2) comment_3.path = (0,1,2) (just the get the parent's path and append the parent's id)

    An item doesn't have to have itself in its own path, does it? Otherwise seems like a good solution I'm going to use with that little modification

    reply

  • Anonymous - 1 year, 2 months ago

    Oops, my bad! Gave it a little more thought and understood that without having yourself in your own path you will not be able to sort 1st level comments by path in the right order.

    Sorry, thanks!

    reply

  • Max Burstein - 1 year, 2 months ago

    You can definitely do it without having to do the extra save if you break out in to raw SQL. If you want to use the ORM it ends up being easier to just do the extra save. In the raw SQL you'd essentially just append the ID to the path array and then run the order by path sort. Not sure what the speed difference is like. I suppose if you were more write heavy then this could be a good approach.

    reply

  • Anonymous - 1 year, 1 month ago

    Just chanced upon this. You can use natsorted in python for sorting the code.

    reply

  • yolo - 6 months, 3 weeks ago

    you sir, are a bawse.

    pls keep making things

    get_money(me)

    swag

    yolo

    reply

  • Anthony Okafor - 5 months ago

    With this writeup you really saved me loads of time. Thanks a bunch!

    reply

  • Anonymous - 5 months ago

    I have a problem, when I hit the submit button the parent comment is copied to the children. From what I understood it's the javascript that does this. Can someone tell me how to remove that effect? Namely, just post child comments without reporting the content of the parent? Thanks in advance

    reply

  • Anonymous - 4 months, 4 weeks ago

    indeed

    reply

  • Anonymous - 4 months, 3 weeks ago

    not working for me

    forms are messed up

    reply

  • showbiz news - 3 months, 2 weeks ago

    I have read a few of the articles on your website now, and I really like your style of blogging. I added it to my favorites blog site list and will be checking back soon. Please check out my site as well and let me know what you think.

    reply

  • and - 4 months, 1 week ago

    Hello!

    reply

  • place - 4 months, 1 week ago

    Hello!

    reply

  • viagra - 4 months, 1 week ago

    Hello!

    reply

  • kyle - 1 month ago

    hello!

    reply

  • pou gratis - 3 months, 1 week ago

    i read a lot of stuff and i found that the way of writing to clearifing that exactly want to say was very good so i am impressed and ilike to come again in future..

    reply

  • hill climb racing hack - 3 months, 1 week ago

    It proved to be Very helpful to me and I am sure to all the commentators here!

    reply

  • Walter Goedecke - 2 months, 3 weeks ago

    From a beginner Django user, how would you simplify your nested comments system to using the default SQLite3? I want to understand the recursive aspect, then apply the method to my website, which is currently SQLite3, but I may move to MySQL.

    Thanks, Walter

    reply

  • best custom essay reviews - 2 months, 1 week ago

    You are given good points to us that is the good tips and most inspirational points to us so your blogs are very useful to all peoples best cutom essay reviews for showing throught my sites.

    reply

  • instagram buy followers - 1 month, 1 week ago

    Positive site, where did u come up with the information on this posting? I'm pleased I discovered it though, ill be checking back soon to find out what additional posts you include.

    reply

  • how to get comments on instagram - 1 month, 1 week ago

    Thanks for posting this info. I just want to let you know that I just check out your site and I find it very interesting and informative. I can't wait to read lots of your posts.

    reply

  • Mike - 1 month ago

    Hi, what is the price? We want to buy comments for out new django projects.

    reply

  • instant followers - 1 month ago

    Super-Duper site! I am Loving it!! Will come back again, Im taking your feed also, Thanks.

    reply

  • Software Testing Training in Chennai - 4 weeks ago

    informative One. Thanks.

    reply

  • dissertations.superiorpapers.com - 1 week, 4 days ago

    Who want this article to visit they can use this instantly, thank you very much I feel very happy to learn more and more keep it up in the same way. It is very good informative site I prefers it.

    reply

  • Currency Trading - 1 week, 3 days ago

    People from all walks of life have traded currencies and nobody's excused from it. Even if a person is unaware of it, he might have traded currencies without realizing.

    reply

  • Business Finance - 1 week, 3 days ago

    One of the reasons Canadian business owners and their mgmt teams wrestle with financing tech assets revolves around their belief that these assets both operate differently than other business assets, and in some cases, i. e. software, are even intangible.

    reply

  • Debt - 1 week, 3 days ago

    If you are wondering about the Best Ways to Get Rid of Debt then you must most efficiently go in for debt settlement programs provided by debt relief networks.

    reply

  • Business Finance - 1 week, 3 days ago

    Business finance in Canada seems to come with a lot of worrying these days. Is that really necessary? Not if you're financing cash flow needs properly. The benefit? Your business simply is now able to ' take off '. Let's dig in.

    reply

  • Advertising - 1 week, 3 days ago

    TV and Radio Media advertising have lately seen a huge upsurge in demand. Advertisers of all types, irrespective of being big or small are betting hard on at least one of these media types for their advertising campaigns.

    reply

  • green smart living - 6 days, 22 hours ago

    Such intelligent work on the subject and ideal way of writing here. I am really impressed! This post is a helpful overview of the particular topic and very actionable. Interesting approach!

    reply

  • read more - 19 hours ago

    Your current tunes can be remarkable. You've got a number of quite accomplished musicians. My spouse and i would like anyone the top involving good results. <a href="http://www.freedebtmanagementtips.com/">read more</a>

    reply