Theju's tryst with life

Part 2: Django Comments for Authenticated Users

In the previous part, we had seen how to setup the basic portions of the project. In this part, we are going to build on that and achieve using the comments framework to accept comments from registered users only.

Step - 2: Getting templates ready

The comments are bound to different apps through the use of content_type and object_pk in the templates. If you check up the stock templates in the django.contrib.comments templates directory you will see how the binding is done.

Create a directory called test_app under your templates directory and create 3 files and name them base.html, photo_gallery_list.html and photo_gallery_detail.html.

Contents of base.html

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="en">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Comment demo</title>
    {% block head %}
    {% endblock %}
  </head>
  <body>
    {% block content %}
    {% endblock %}
  </body>
</html>

Step-3: Getting your admin ready to upload photos

Django's admin is one of the umpteen reasons it is so popular. It makes some CRUD part very easy.

Create a new file in test_app called admin.py.

Contents of admin.py

from django.contrib import admin
from test_app.models import Photo_Gallery

class Photo_Gallery_Admin(admin.ModelAdmin):
    list_display = ["name","pic"]

admin.site.register(Photo_Gallery, Photo_Gallery_Admin)

Step-4: Fire up your dev-server

You might have a question if we are done yet...and the answer to that would be a NO!!! We are just firing up the server to check if we have done everything right until here.

Make sure to sync your tables before you fire up your dev-server.

[theju@localhost comments_reg_users]$ python manage.py syncdb
Creating table auth_permission
Creating table auth_group
Creating table auth_user
Creating table auth_message
Creating table django_content_type
Creating table django_session
Creating table django_site
Creating table django_admin_log
Creating table django_comments
Creating table django_comment_flags
Creating table test_app_photo_gallery

You just installed Django's auth system, which means you don't have any superusers defined.
Would you like to create one now? (yes/no): yes
Username (Leave blank to use 'theju'): admin
E-mail address: admin@vce.ac.in
Password:
Password (again):
Superuser created successfully.
Installing index for auth.Permission model
Installing index for auth.Message model
Installing index for admin.LogEntry model
Installing index for comments.Comment model
Installing index for comments.CommentFlag model

and then fire your dev server

[theju@localhost comments_reg_users]$ python manage.py runserver
Validating models...
0 errors found

Django version 1.1 pre-alpha, using settings 'comments_reg_users.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Open your browser and direct it to http://127.0.0.1:8000/admin/. Open the Photo Gallery tab and add a few photos. Open a new tab (don't log out from the admin) on your browser and check out http://127.0.0.1:8000/names/. Then click on any photo object and you'll see the comments form. Log out from the admin tab and refresh this page and you'll see it vanish.

All happy till now??? But you might ask "Does the registered user have to go through the pain of entering his name and email id everytime?" and "What is the guarantee that this form cannot be spoofed?" My reply to these questions would be "Hey, I didn't tell this was done yet!!!"

The answer to the first question would be to override the comments/form.html.

Step-5: Slightly customize your comments form

Edit the base.html under templates/test_app to look like below:

 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
 <html lang="en">
   <head>
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
     <title>Comment demo</title>
     <script type="text/javascript">
       function disableReqdInputs(){
         var nameInput = document.getElementById('id_name');
         if (nameInput){
           nameInput.value = "{{ request.user.get_full_name }}";
           nameInput.readOnly=true;
         }
         var emailInput = document.getElementById('id_email');
         if (emailInput){
           emailInput.value = "{{ request.user.email }}";
           emailInput.readOnly=true;
         }
       }
     </script>
    {% block head %}
    {% endblock %}
  </head>
  <body onload="disableReqdInputs();">
  {% block content %}
  {% endblock %}
  </body>
</html>

The above javascript will make the name and email inputs readonly and also autofill the names as mentioned in the django.contrib.auth.

Note

You can also use jQuery to get most of your javascript work done too.

Now we need to answer the second question regarding form-spoofing. For this we need to write a wrapper around the default post_comment that will prevent invalid data or changed data (like name or email).

Step-6: Writing a wrapper around post_comment

When the comment gets posted we need to make sure that it is redirected first to our wrapper.

For this add the following line in the urls.py and make sure this line always comes above the ^comments/ line. The reason is that django's urlresolvers match URLs from top-to-bottom.

(r'^comments/post/', 'test_app.views.comment_post_wrapper'),

Next open the test_app.views file and write the wrapper like below:

from django.contrib.comments.views.comments import post_comment
from django.http import HttpResponse

def comment_post_wrapper(request):
    # Clean the request to prevent form spoofing
    if request.user.is_authenticated():
        if not (request.user.get_full_name() == request.POST['name'] or \
               request.user.email == request.POST['email']):
            return HttpResponse("You registered user...trying to spoof a form...eh?")
        return post_comment(request)
    return HttpResponse("You anonymous cheater...trying to spoof a form?")

That's it, we are done...just test out your application now and it should work. If you don't like your inputs to be readonly, you can always make them hidden by altering the comments/form.html. The wrapper still remains the same.

Conclusions

There is a very interesting ticket #8630 that deals with the customization of comments. The ticket has some nice docs courtesy of Carljm and is slated to be in by Django 1.1.

With that ticket, the inbuilt comments should become much more easier to customize.

Comments for this article...

Marcel Chastain commented on 20th November 2008 22:48

Looking forward to using this! I like the automatic syntax highlighting in your code snippets too..

Joshua Works commented on 20th November 2008 23:34

I've found it completely sufficient to, instead of using the {% render_comment_form %} tag, to use {% get_comment_form for object as form %} and build the form myself, just leaving out the name, email and url fields, like this: {% get_comment_form for album as form %} <form action="{% comment_form_target %}" method="POST"> {{ form.comment }} {{ form.honeypot }} {{ form.content_type }} {{ form.object_pk }} {{ form.timestamp }} {{ form.security_hash }} <input type="submit" value="Add comment" id="id_submit" /> </form> where all but {{ form.comment }} are hidden fields.

Yashh commented on 21st November 2008 10:22

Nice work... I like the check for spoofing in wrapper.

Thejaswi Puthraya commented on 21st November 2008 11:36

@Joshua Works: That is exactly the second method that I wanted to suggest and made a passing reference to in the last few lines of the post (see hidden fields).

Joshua Works commented on 21st November 2008 18:43

My point was that the name, email and url fields need not even be a part of the form for authenticated users (not merely hidden), eliminating the need for both your Javascript and the wrapper view.

Yashh commented on 21st November 2008 23:00

@Joshua I tried something similar. But I diverted myself from the central idea. http://smallr.net/djcom

Paul commented on 29th November 2008 08:46

Very helpful! Thanks for posting it!

Peter commented on 25th December 2008 07:19

I am reading and studying this post. I'd like to add comment function to my website. This is useful. But I don't like the name,email and url fields, because I can get these from current user's profile. Nice work!

alan commented on 29th March 2009 06:20

great post. one question: is there a way to not enter the email at all? I hid both the email and the URL field on my form as joshua works suggested, onlyi problem now is if a user doesn't have an email, i get an error when trying to post a comment because the hidden email field is not populated. I want to turn off the check for the email field...

Thejaswi Puthraya commented on 25th April 2009 20:56

@alan: You have quite a few ways of not having to check for email... 1st: Write your custom models, form as mentioned in http://docs.djangoproject.com/en/dev/ref/contrib/comments/custom/#ref-contrib-comments-custom 2nd: In the wrapper around post_comment, you can check if the email is present. If it isn't, just enter some default. There are a lot of other ways but not as clean as the above two.

research paper commented on 23rd October 2009 19:24

Joshua Works I like your method. Yashh,Good Job! Thx for sharing it.

Rémi commented on 5th November 2009 21:52

Thx a lot ! great job.

Nici18 commented on 17th December 2009 02:03

Very perfect knowledge

David commented on 18th January 2010 01:24

I wanted to build a comment accepting system from registered users a while ago, but didnt manage to do so because lack of knowledge was an issue, now I already know how to build it and this is a quality post - many can learn from it!

Post a comment

Please do not resubmit comments if successful, comment moderation might be at work!!!