Simple Django Commenting System with Ajax May 10

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})

tags: ajax django javascript prototype

Comments

dorian May 10

wow! simple and clear!

Ivan May 10

Nice tutorial, thanks-)

??? May 10

Yes, nice article!

Steve May 11

Cool.

Chris Kopec May 12

Thanks for the comments. I've been learning python and django in my free time so I'm by no means an expert in the language. But my hope with writing this type of article is to receive feedback from more experienced users on improvements that can be made to my designs and code.

elus May 12

Let me test too :)

Doug May 18

Just seeing how it works. Looks nice!

Robert Jun 12

Looks great, just testing the comment.

VennerCorp Jun 16

excellent, just what i was looking for

angel Jul 12

testing one two three