Part 2: Django Comments for Authenticated Users
Thu 20 November 2008 by Thejaswi PuthrayaPart 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>
Contents of photo_gallery_list.html
{% extends "test_app/base.html" %}
{% block content %}
{% load comments %}
<h1>Comment on your favourite photo</h1>
<ul>
{% for a in object_list %}
{% get_comment_count for test_app.photo_gallery a.pk as cc %}
<li><a href="{{ a.pk }}/">{{ a }}</a> ({{ cc }} comment{{ cc|pluralize }})</li>
{% endfor %}
</ul>
{% endblock %}
The get_comment_count tag fetches the number of comments for each object (photo) of the photo_gallery model.
Contents of photo_gallery_detail.html
{% extends "test_app/base.html" %}
{% block content %}
{% load comments %}
<h1>Give feedback/comments for your favourite photo</h1>
<p><strong>{{ object.name }}</strong></p>
<img src='{{ SITE_MEDIA }}/images/{{ object.pic.url }}' alt='{{ object.name }}' width="30%" />
{% get_comment_list for test_app.photo_gallery object.pk as comment_list %}
{% if comment_list %}
<h2>Comments</h2>
{% for comment in comment_list %}
<h4>{{ comment.name|escape }} at {{ comment.submit_date|date:"r"}}</h4>
<p>{{ comment.comment|escape|urlizetrunc:"100"|linebreaks }}</p>
<hr>
{% endfor %}
{% endif %}
{% if request.user.is_authenticated %}
<h2>Leave a comment</h2>
{% render_comment_form for test_app.photo_gallery object.pk %}
{% endif %}
{% endblock %}
The get_comment_list tag fetches all the comments bound to a particular object (photo) based on it's primary key (pk). Most of this is almost stock comment based except for the request.user.is_authenticated.
Since we added TEMPLATE_CONTEXT_PROCESSORS in our settings.py, it gives us the functionality to access the request object in our templates.
We check if the user is authenticated and display the comment form accordingly. The render_comment_form tag displays the form by rendering the comments/form.html.
Note
Without the request.user.is_authenticated functionality, we can accept comments from anonymous users also.
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.