Back in February, I posted an entry about adding Ajax functionality to the built-in comments app. I always intended to go back and improve this method, but didn't get the chance until now. Yesterday I decided to take another look, and I found a much better way of achieving this functionality without violating the DRY principal like I did originally. I've posted the code on Bitbucket as django-ajaxcomments, and I'm releasing it under the same BSD license that Django uses.
The Problem With Copy & Paste
In my original post, I copied the django.contrib.comments.views.comments.post_comment
view and then modified certain pieces of it to return JSON instead so that the jQuery callback function could interpret the results. The problem with this is that the post_comment
view has changed since then, and it will continue to change. If I continued to maintain the project as it was, I would have to watch post_comments
for changes and update my project accordingly. If I missed an important change, then my project would instantly become unusable with the latest version of Django. This goes against DRY, one of the core principals that Django written with.
Decorators to the Rescue
One of the great features of the Python programming language is its decorator syntax, which allows a programmer to wrap a function around another function in order to enhance the function without changing its original source code. Since Django views are Python functions, they can be decorated like any other Python function.
Using this feature I was able to intercept any calls that the post_comment
view made to its instance of the render_to_response
or next_redirect
functions. My wrapper checks to see if the request was made through Ajax (using Django 1.0's HttpRequest.is_ajax()
method) and encodes the needed details of the response in JSON before returning it back to the Javascript function that made the request. If the request isn't done through Ajax, then it merely triggers the original function.
Including the Current Request Object
In order to make this decorator work, I needed to provide it with the current HttpRequest object each time it is called. To do this, I created another decorator to wrap the post_comment
view itself. This decorator re-wraps the instances of render_to_response
and next_redirect
with the current request object each time the post_comment
view is called.
Putting it all Together
To finish up, I altered my original jQuery function to interpret errors from the JSON response and highlight the errors inline on the form. I added my decorators and the necessary imports to an ajaxcomments/utils.py
file, and then imported this file from a blank ajaxcomments/models.py
file so that it would be invoked when Django loads installed applications.
To take a look at the project's source code, and to get instructions on how to install it within your own project, head to the Bitbucket page and take a look at my wiki entry. If you have problems, please submit issues on Bitbucket and I'd be glad to help out!