Ajax Django Comments With jQuery
Posted by on February 16, 2009 in the Django category.
NOTE: This example is outdated, and it violates the DRY principal. For a revised look at this topic, check out my more recent post here: DRY Ajax Comments
I haven't posted here in awhile because of quite a few things going on in my personal and professional life lately. Things are finally beginning to calm down a bit, however, so I'm planning to return to the blogging keyboard on a regular basis going forward. The first thing I'd like to share with the Django community is my basic implementation of Ajax comments using the built-in Django comment system.
This is certainly not the only way to do this, so if you've got ideas or comments please share them at the bottom of this post. This is how I've done it on a few sites so far, and it's worked well for me.
To enable the posting of comments through Ajax, I utilized the jQuery JavaScript toolkit. You can still follow this post if you're using another toolkit, however, because the principles should remain the same.
Creating a Custom Ajax Comment Post View
The first thing to do is create a new subfolder under your project root called "ajax". Within the new ajax folder, create three files: __init__.py so that Python recognizes the folder as a module, urls.py for all Ajax related urls going forward, and views.py for all Ajax related views going forward.
Ajax Urls
In your ajax/urls.py file, add the following code:
from django.conf.urls.defaults import *
urlpatterns = patterns('myproject.ajax.views',
url(r'^comment/post/$',
view='ajax_comment_post',
name='ajax_comment_post'),
)
This establishes a url for the comment post view. After saving that file, go to your main urls.py file in your root project folder. Add the line below to hook your new ajax/urls.py file into your main urls:
(r'^ajax/', include('myproject.ajax.urls')),
Customizing the Standard Comment View
With that in place, it's time to move on to the view in ajax/views.py. Go to Django's code browser and copy the original view from /django/trunk/django/contrib/comments/views/comments.py. Paste the text into your ajax/views.py file, and get ready to slice it up for JSON serialization. Responses encoded as JSON objects can be easily parsed by most JavaScript toolkits. You will return two objects to the Ajax process that calls the page - "status" and "error". Status is set to "success", "error", or "debug". Error is set to a brief message describing an error. To use JSON serialization, add the following import line to the top of your view file:
from django.utils import simplejson
First, remove the CommentPostBadRequest method. This method renders error messages to the debug 400-error view. Since this page is going to be called through asynchronous JavaScript, it isn't particularly helpful to return a debug page because it wouldn't be seen. Then, go through the view and locate each call to CommentPostBadRequest. Replace each instance with code similar to the following:
if ctype is None or object_pk is None:
error = "Missing content_type or object_pk field."
status = simplejson.dumps({'status': 'debug', 'error': error})
return http.HttpResponseServerError(status, mimetype="application/json")
Set the status as "debug" because this isn't the type of error that you typically want to show up in production. This is not a user error, it's an error related to the code itself. In production, this should be displayed to the user as something similar to "The server has encountered an error, please contact an administrator."
The next section to alter is the block that deals with form errors. These are errors that you want displayed to the user because they indicate issues with the form that they have the ability to correct. In order to handle this, change the error handling block to join together any errors and return them in the "error" JSON object.
if form.errors or preview:
error = ""
for e in form.errors:
error += "Error in the %s field: %s" % (e, form.errors[e])
status = simplejson.dumps({'status': 'error', 'error': error})
return http.HttpResponse(status, mimetype="application/json")
The last change to make to the view is to return a "success" status if the comment is successfully created. To implement that, change the return line underneath the comment.save block to look like this:
status = simplejson.dumps({'status': "success"})
return http.HttpResponse(status, mimetype="application/json")
Implementing the Client-Side Interaction with jQuery
With the server-side code now complete, it's time to move on to the client-side JavaScript code. Create a postComment.js script in your static media directory, and add a line to the appropriate template to make sure the script is loaded on any page that allows the posting of a comment.
To start the script off, add a $(document).ready() section:
$(document).ready(function() {
debug = true;
$('div.comment-form form').submit(function() {
// The rest of the function will go here. . .
return false;
});
});
This block hijacks the form's submit event, allowing you to alter the behaviour through JavaScript. The debug = true lets the script know that you're currently in development and it should display the "debug" error messages. When you deploy, make sure to change that to false. Using return false at the end of the function ensures that the event will not continue to propagate and send the user through the standard comment post process.
Inside your submit function, the first thing to do is make sure any errors from a previous submit attempt are cleared away. I wrap my errors in a div with a class of "comment-error", so this line makes sure all "comment-error" elements are removed. You can alter this to suite your template as needed.
$('div.comment-error').remove();
Next up, read all of the comment form fields into variables.
name = $('#id_name').val();
email = $('#id_email').val();
url = $('#id_url').val();
comment = $('#id_comment').val();
content_type = $('#id_content_type').val();
object_pk = $('#id_object_pk').val();
timestamp = $('#id_timestamp').val();
security_hash = $('#id_security_hash').val();
honeypot = $('#id_honeypot').val();
Then, use jQuery's post function to hit the /ajax/comment/post/ view that you created earlier, making sure to include all of the values you read into variables above.
$.post('/ajax/comment/post/', {
name: name,
email: email,
url: url,
comment: comment,
content_type: content_type,
object_pk: object_pk,
timestamp: timestamp,
security_hash: security_hash,
honeypot: honeypot,
}, function(data) {
// The success or failure check will go here. . .
}, "json");
jQuery's post function takes the URL as the first argument, a list of key/value pairs for the POST data next, then a callback function that is triggered upon receiving a response, and finally a string indicating the type of encoding to interpret the response data with.
In the callback function, including the object that is created automatically from the response data. In the code above, I called it data. It operates as a typical JavaScript object. Attributes of the object correspond to the values that we encoded in the view above - data.status and data.error. In your callback function, check for success and then take a few actions depending on the status of the post:
if (data.status == "success") {
// If the post was a success, disable the Post button to
// prevent accidental duplication.
$('input.submit-post').attr('disabled', 'disabled');
// If this is the first comment, I add the "## comment(s) so far"
// banner that I use to introduce the comments section.
if ($('div#comments').children().length == 0) {
$('div#comments').prepend(
'<h2 class="comment-hd">1 comment so far:</h2>'
)
}
// Here I build the HTML to use for the comment. I set the
// style to "display:none" so that I can fade it in with an
// animation.
comment_html = '<div class="comment" style="display: none;">' +
'<div class="comment-body"><p>' + comment +
'</p></div><div class="clear"></div>' +
'<p class="posted-by">Posted by <a href="' + url +
'">' + name + '</a> 0 minutes ago.</p></div>'
// Then I add the comment at the bottom of the comments section
// and fade it in.
$('#comments').append(comment_html);
$('div.comment:last').show('slow');
// I hide the comment form to prevent duplication, and
// replace it with a success message for the user.
$('.comment-form').hide()
.html('<p class="comment-thanks">Comment successfully' +
' posted. Thank you!</p>')
.show(2000);
} else if (data.status == "debug") {
if (debug) {
// If the site is currently in development, list the debug
// errors.
$('div.comment-form').before('<div class="comment-error">' +
data.error + '</div>');
} else {
// Otherwise, display a generic server error message.
$('div.comment-form').before('<div class="comment-error">' +
'There has been an internal error with the server. ' +
'Please contact a site administrator.</div>');
}
} else {
// If there were errors with the form, I add them to the
// page above my comment form with a "comment-error" div.
$('div.comment-form').before('<div class="comment-error">' +
data.error + '</div>');
}
And with that, you've completed a basic implementation of Ajax comments using Django's built-in comments. This was just a quick overview of the core concepts, and there is a lot more that you can do with this. For example, I omitted previewing from this post for simplicity but you could easily implement a quick preview process that checks the form for errors but doesn't actually post the comment. Also, for the Adoleo blog, I used a jQuery md5 plugin to create hashes for Gravatar URLs, so that the user's picture is displayed next to their comment.
I hope my first post after my brief hiatus has been helpful! Make sure to check back soon because I'll be posting more in the weeks ahead.
Comments are closed.
Comments have been closed for this post.
Related tags: ajax, comments, development, jquery
Other posts:
« Automated Webfaction DNS Override Updates | Django Dev, Test, and Prod Environments Revisited »
Categories
By Month
- November, 2009
- October, 2009
- August, 2009
- April, 2009
- March, 2009
- February, 2009
- December, 2008
- November, 2008
- October, 2008
- September, 2008
By Year
Atom Feeds
Latest Comments
-
-posted by Zoomdontaitte on Easily Working With Pinax on Multiple Machines, 17 hours, 4 minutes ago
-
-posted by Heireeguibula on Easily Working With Pinax on Multiple Machines, 4 days ago
-
-posted by UsareeSab on Easily Working With Pinax on Multiple Machines, 5 days, 18 hours ago
-
-posted by Hileappoiff on Easily Working With Pinax on Multiple Machines, 6 days, 7 hours ago
-
-posted by test on DRY Ajax Comments, 1 week, 4 days ago
27 comments so far:
Hello, Good Tutorial Thanks. But Tutorial.zip not link download:)
Posted by umit 1 year ago.
Nice tutorial
Posted by Silas 1 year ago.
Umit - Thanks! I didn't actually make a downloadable version of this tutorial, but I'd consider creating a PDF if there was enough interest.
Silas - Thank you! I'm glad you found it useful!
Posted by Brandon Konkle 1 year ago.
Great example!
Posted by lukasz 1 year ago.
Pretty good, I just started with Ajax so this was all new to me.
The comments view had to modified a little bit more than was suggested in the article...A finished version of the view might've been helpful.
Posted by Evan 1 year ago.
Great tutorial! two things though: - When you modify the comment post view, you suggest that we replace each CommentPostBadRequest instance by your error handle code. I think that it is better to change CommentPostBadRequest behavior instead. Maybe like this:
def init(self, why): status = simplejson.dumps({'status': 'debug', 'error': why}) super(BadRequest, self).init(status, mimetype='application/json')var params = $('form').serialize(); $.post('/ajax/comments/post/', params, function(data) { ... }Cheers!
Posted by ViaToR 1 year ago.
Viator - thank you very much for the suggestions! Those are great enhancements to this method, and I really appreciate you sharing them. I'm planning on implementing them along with a couple other ideas as well later on. I'll edit this post to reflect the changes once I'm done.
Thanks!
Posted by Brandon Konkle 1 year ago.
Great tutorial thanks, I've been looking for something like this.
I took would be interested in a PDF or a downloadable end result.
Posted by Titus 1 year ago.
Typo, view should be post_comment instead of ajax_comment_post, or post_comment in ajax/views.py renamed to ajax_comment_post right?
Posted by Martin 11 months, 2 weeks ago.
Actually, I did that intentionally. The Ajax-specific comment post view that I created is altered from the original contrib.comment view, so it's a separate view. As such, I gave it a different name so that it wouldn't conflict with the original view.
Posted by Brandon Konkle 11 months, 2 weeks ago.
I agree with Martin's comment. when you copy the view, you need to change the function's name (on myproject.ajax.views) from comment_post to ajax_comment_post.
Great post! Cheers!
Posted by httpdss 11 months, 2 weeks ago.
honestly, it would've been nice to have the complete source for the solution. I am currently having an issue where jquery form hijack is not working, i have tried the auto render comment form as well as creating my own form with id='comment-form', still no luck. thanks for the tips though
Posted by par 11 months, 2 weeks ago.
I see, i just needed to add
<div class="comment-form">around my comment form, it works great! thanks! :)Posted by par 11 months, 2 weeks ago.
nice tutorial.
Posted by aj 11 months, 2 weeks ago.
@Martin & @httpdss: I totally misunderstood. You're both absolutely right - I changed the view name in
myproject.ajax.viewstoajax_comment_post.@par: I'm glad you found it helpful, and I agree - I need to expand this post with a source download, and a .pdf in response to other comments above. I'm going to plan to expand on this within the next month, so keep an eye on the blog.
Posted by Brandon Konkle 11 months, 1 week ago.
Thanks for your article and your effort! There is an online example ?
Cheers!
Posted by Bro 11 months, 1 week ago.
Any ideas on json security, django comments as a javascript application service ala disqus?
Posted by John 11 months, 1 week ago.
Hello, good example!
Posted by Reke 11 months ago.
Thanks for example.
Posted by monsun 10 months, 4 weeks ago.
Cheers for this, very well written....more please!
Posted by happycoder 10 months, 4 weeks ago.
Just one thing to add, in your project
urls.pyfile, make sure your(^ajax/...comes before your(^comments/...Posted by ghiotion 10 months, 2 weeks ago.
@ghiotion: Thanks for the addition! Can you share some more information on what the significance of this is? What issue do we avoid or benefit do we gain by making sure ajax is first?
Posted by Brandon Konkle 10 months, 2 weeks ago.
Good~~
Posted by cwjeon 10 months ago.
Groovy! Makes forms much more fun to fill out!
Posted by Tom 9 months, 4 weeks ago.
Does this use the ajax comments?
Posted by Ian 9 months, 1 week ago.
@Ian - Yep, as I'm sure you saw, Ajax Comments are up and running on the blog. By the way, if you're looking for a more complete example, check out Django-AjaxComments at Bitbucket. I've started a project there, and plan to go back and improve it when I get more time. Thanks!
Posted by Brandon Konkle 9 months, 1 week ago.
Its good for user if they found commenting easy they’ll for sure comment again on your blog and it looks interactive as well you write comment you click submit and ur comment it beautifully scroll up and its submitted, sweet!
Posted by ZK@Web Marketing Blog 9 months ago.