I have begun incorporating ajax techniques using the script.aculo.us and prototype libraries and thought I'd share a basic example that combines ajax with django. The snippets shown here have been taken and simplified for the purpose of this article. Making good use of ajax can significantly improve the usability of a website as it introduces a more user friendly experience. Granted sites can go overboard especially with all the effects. In this article I thought I would demonstrate a simple commenting system that uses the Prototype library to create asynchronous calls necessary for submitting a form while remaining integrated with the django framework.
Model
Lets start with the model describing the commenting class. It contains the basics for user submitted comments, name, email, website, content, reference, etc. The key represents an attribute for retrieving a set of comments. For example, combining the comments with articles the key could store the article's title or slug. When an article is loaded the comments can easily be retrieved based on their title. The name, email and website contain information on the user submitting a comment. The email and website fields are optional in this case. The text refers to the comment being submitted by the user and created date is a timestamp automatically assigned when the user performs a submission.
class Comment(models.Model):
key = models.CharField(max_length=200)
name = models.CharField(max_length=50)
email = models.CharField(max_length=100, blank=True)
website = models.CharField(max_length=200, blank=True)
text = models.TextField()
created_date = models.DateTimeField(auto_now_add=True)
Form
Extending the ModelForm provides some built in features when processing comments that are submitted. In this case several fields are excluded to construct a form only displaying the name, email website and text fields of the comment. Notice the key and created_date fields have been left out. The created_date will be automatically filled when the record is added to the database and the key field will be filled by the view prior to committing the record.
class CommentForm(ModelForm):
class Meta:
model = Comment
fields = ('name', 'email', 'website', 'text')
URL
The URL used to direct comment submissions to the correct callback method.
(r'^comment/(?P<key>\d+)/$','comments.views.add_comment')
HTML
The HTML used to display the comment form. In this case the key being associated with comments is the article's slug which is described in the URL used to submit the comment.
<div id="response">
<form id="comment_form" action="/comment/{{ article.slug }}/"
method="post" onSubmit="ajaxSendComment(); return false;">
<p>{{ comment_form.name }} {{ comment_form.name.label_tag }}(Required)</p>
<p>{{ comment_form.name }} {{ comment_form.email.label_tag }}(Optional)</p>
<p>{{ comment_form.name }} {{ comment_form.website.label_tag }}(Optional)</p>
<p>{{ comment_form.name }} </p>
<p><input id="submit" type="Submit" value="Submit" /></p>
</form>
</div>
This is basic HTML to display the list of comments. It can be encapsulated in an HTML file which is then called by the View to return the updated comments.
<div id="comment_texts">
<h1>Comments</h1>
{% if object.get_comments %}
{% for comment in object.get_comments %}
<div id="comment">
<div id="comment-title">
<p class="comment-name">
{{ comment.name }} <span>{{ comment.created_date|date:"M j" }}</span>
</p>
</div>
<div id="comment-body">
<p class="comment-text">{{ comment.text|safe }}</p>
</div>
</div>
{% endfor %}
{% else %}
<p> No comments have been added </p>
{% endif %}
</div>
Javascript
The javascript used in this example makes use of the prototype library. The html headers should import the prototype library.
Here is the meat of the code running on the client side to initiate the asynchronous operations. The form contains an attribute that onSubmit calls the ajaxSendComment method. The method obtains the form object using the $('form_id') notation provided by the prototype library. Using the form object the request method can be invoked. The request submits the form; the onSuccess callback processes the response. The callback updates the existing user submitted comments to include the user's comment. The code also disables the submit button while processing the request and on successful completion clears the form allowing a new comment to be entered.
<script type="text/javascript">
function ajaxSendComment() {
var form = $('comment_form');
form.request({
onSuccess: function(transport) {
var result_field = $('comment_texts');
var submit_button = $('submit');
submit_button.disabled = true;
submit_button.value = "Processing. Please Wait...";
if(transport.status == 200) {
form.reset();
}
result_field.update(transport.responseText);
submit_button.value = 'Submit';
submit_button.disabled = false;
},
});
}
</script>
View
Now the server side meat. When the comment is posted, part of the URL describes the key that should be associated with the comment. The key is captured by the URL parser and passed into the add_comment view. The form is loaded from the post arguments and validated. The comment is initially saved with the parameter commit=false to generate the Comment object but not actually saving it into the database. This is done to obtain the object but still allows post processing of the object prior to database insertion. Once the object is obtained, the key is set to allow retrieval. The comment is then saved again committing it to the database. The set of new comments is then passed back as the response allowing the user to see the comment that was just submitted.
def add_comment(request, key):
if request.method == 'POST':
form = CommentForm(request.POST, request.FILES)
if form.is_valid():
cmt = form.save(commit=False)
cmt.key = key
cmt.save()
comments = Comment.objects.filter(key__exact=key)
return render_to_response('comments/comment_listing.html', {'comments-all': comments})
Comments
dorian May 10
Ivan May 10
??? May 10
Steve May 11
Chris Kopec May 12
elus May 12
Doug May 18
Robert Jun 12
VennerCorp Jun 16
angel Jul 12
testing one two three