Nick Carroll

Metabolising caffeine into code

Improving Django Comments’ User Experience with AJAX

with 19 comments

I really like Django. It is not bloated like a lot of other frameworks, and it has a healthy balance between convention and configuration. As a developer I want to be able to use the tools that I want to use, and not be forced into a specific way of doing things. An example of this in Django is the comments framework that is part of django.contrib. The comments framework provides the infrastructure for attaching comments to any domain model in your Django project through content types. It also provides a few spam prevention features that you should consider leveraging, such as a security hash or a hidden honeypot field. However, the default comments form is rather bare, and the workflow for posting a comment requires too many clicks and redirects.

There are some solutions that try to improve the user experience, and do a good job of it, but require hacking the comments framework. I personally don’t like hacking the internals of any framework. Not because I’m scared to, but because I don’t want to inherit any unnecessary maintenance overheads.

Besides, I believe the comments framework does the right thing. It doesn’t try to do too much, and by that I mean the HTML that it generates for the default rendered form and corresponding responses can easily be mashed up as part of the submitting pages DOM. So with a bit of JQuery and AJAX knowhow you can post comments without refreshing or being redirected away from the submitting page.

First I use the utility methods from the comments framework to list all comments for a particular discussion content type, and to display the default comment form immediately after. Note that the comment form is wrapped in div tags with the id “comment_form”. The div will be used to override the block with responses from the server after an ajax post.

Comment

{% load comments %} {% get_comment_form for discussion as form %}
{% render_comment_form for discussion %}

In my template (discussion.html) in which I want to allow people to comment on something I add the following to a script block that will insert the JavaScript into the head element of the base template (base.html).


In line 2 I define a method called bindPostCommentHandler() which performs the ajax call to post a comment and to handle the response when the form submit event is triggered.

In line 3 I remove the preview button from the form as I do not want this functionality. Rich text plugins generally have a preview mode, so I don’t see the need for the server to process a preview request.

In line 4 I bind the ajax post call to the submit event of the comments form. This means when I click on the Post button to submit the form I will trigger an ajax post instead of a normal post to the server.

Line 7 serialises the data from the form input fields using the JQuery serialize() function.

Line 8 specifies the URL to post to. I use the utility method from the comments framework to retrieve this for me, so I don’t need to hard code it to a specific URL. The comment_form_target should retrieve the correct URL for the comments application that you should have configured in your urls.py file. If not then add the following URL route to your urls.py file.

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

Line 10 specifies that the response will be HTML. This is necessary so that JQuery knows how to parse and handle the response.

Line 11 details the success callback, which is the function that calls when the response is successful. The callback simply replaces the form in the div wrapper with the response. A successful response could either be a “thank you” message for posting a comment, or a form displaying fields with invalid fields. Line 13 is necessary to rebind the bindPostCommentHandler() function to any new form that appears in the div wrapper. If you omit this rebinding, then you will lose ajax posts on successive submits.

Line 15 defines the callback that gets called when the server responds with an http error code. I just override the div wrapper block with an apologetic message.

Finally, line 23 binds the bindPostCommentHandler() function when the page is ready.

As you can see from the above, all you need is some JavaScript to improve the user experience for posting comments using Django’s comments framework. No framework hacking necessary. Note that if you want to further improve this solution, then you can add sliders, fade ins, and fade outs for displaying the responses from the server. I left these out for brevity. I also left out appending the submitted comment to the list of comments, as the above solution will only display the submitted comment after the user performs a GET request after the submit.

Written by Nick

May 10th, 2009 at 8:20 pm

Posted in Programming

Tagged with , , ,

19 Responses to 'Improving Django Comments’ User Experience with AJAX'

Subscribe to comments with RSS or TrackBack to 'Improving Django Comments’ User Experience with AJAX'.

  1. This has been extremely helpful, and it’s easy to see how you can extend this. Thank you!

    Doug

    13 May 09 at 7:06 am

  2. Hi Nick,
    It’s not working in safari at all, it does in Firefox, but I get the “Your comment was unable to be posted at this time. We apologise for the inconvenience.” error. Any suggestions where I might be going wrong?

    I request the .js file in the head like this

    and in the html I have this
    {% get_comment_form for entry as form %}

    {% render_comment_form for entry %}

    The rest is a direct copy and paste from your source.
    Kind regards,
    Philip

    Philip

    19 May 09 at 7:28 pm

  3. also, how do you filter the most recent comments? I want to filter the latest three comments on my homepage.

    Philip

    19 May 09 at 7:29 pm

  4. Hi Philip,

    get_comment_list returns a list of Comment objects. You can limit the comments returned by using a slice, eg [0:3].

    {% get_comment_list for event as comment_list %}
    {% for comment in comment_list[0:3] %}

    {% endfor %}

    As for your Safari question, I think you need to bind to the $(document).load() event rather than the $(document).ready() event. I don’t have a Mac handy to test this with.

    Cheers,
    Nick.

    Nick

    19 May 09 at 10:34 pm

  5. Thanks man, this is a very well written tutorial.

    Do you have any suggestions on making the {% render_comment_form %} look any better without hand coding the form?

    Jeff Ammons

    2 Jul 09 at 5:03 pm

  6. That was nice article, Thanks

    zdmytriv

    7 Jul 09 at 7:42 am

  7. This seems to only work if I keep the entire script in the header instead of using it as an include. I also have to put the “load comments” tag in the head for comment_form_target to work.

    Is there a cleaner way to implement this?

    Peter

    4 Aug 09 at 2:04 am

  8. Oops – I was missing the charset=”utf-8″ line in my script tag. Adding it took care off all the issues.

    Thanks for the excellent tutorial.

    Peter

    4 Aug 09 at 2:11 am

  9. how to customise comment form for ajax use

    anand

    2 Sep 09 at 9:00 pm

  10. just testing

    John

    23 Sep 09 at 8:04 pm

  11. Thanks, this article is really helpful.

    Johny Meow

    12 Oct 09 at 12:49 am

  12. Are there any drawbacks with this method? I mean, you’re sending the entire generated template to client together with head part of the html. Would it be better to separate that code or filter it with regex?

    Zlatko

    18 Oct 09 at 10:30 pm

  13. This was superb, Nick. The jquery script works flawlessly. Thank you for this generous work.

    Carl Hu

    30 Oct 09 at 5:03 pm

  14. Thanks for the code. Just testing :)

    Brian

    7 Nov 09 at 2:13 pm

  15. Thanks! This really helped improve the workflow on my personal blog. However, as a newbie to jquery, I did run into a little hitch. I’m developing a blog that shows several posts per screen, and found that the example given doesn’t handle more than a single comment form. I modified the django code to generate an id based on the post count (ie., comment_form_3), then used a for loop to call bindPostCommentHandler(idx) for each post. It works like a champ. Thanks again for the great tutorial.

    Liam Chasteen

    16 Feb 10 at 2:20 pm

  16. do you moderate comments manually or use akismet or anything else….

    anand

    17 Feb 10 at 9:31 pm

  17. Testing. Looks like a nice solution.

    Josh

    26 Feb 10 at 4:22 am

  18. Thanks! This info helped me while writing my own comments/ajax on my django blog.

    Chris Robison

    17 Mar 10 at 12:56 pm

  19. Thanks, very helpful.

    Ale

    7 May 10 at 1:27 am

Leave a Reply