This is part 7 of a series of posts on James Bennett's excellent
Practical Django Projects. The table of contents and explanation can be found
here
Firstly, the comments framework has been re-written for 1.0. The documentation for it can be found
here if you need to refer to it. That out of the way lets get into the meat of the chapter.
There is now only one model for comments instead of two separate models - one for free comments and one for registered comments. I assume the new combined model will cover both use-cases.
On p124 in the URLConf file the pattern you should use is ( r'^comments/', include('django.contrib.comments.urls')) dropping off the extra '.comments'. On p125 the free_comment_form is used. Replace that with the simplest comment form {% render_comment_form for object %}, or you can be more explicit and use {% render_comment_form for coltrane.entry object.id %}, which makes it clearer that you're referring to the entry model and passing the primary key for the entry table which is the field id. I'm unsure as to whether these are functionally equivalent, but they appear to be.
Next up on p125 you build a free_preview.html template, but you might have already discovered that the new comments gives you one for free! So to modify it you copy the preview.html template from the django.contrib.comments.templates.comments directory into your templates.comments directory. Modify it so that it {% extends "base.html"} and use the block title from the book example. All content in the {% block content %} should come from the copied template, but you can add back in the poster's name like this:
<h2>Post a comment</h2>
Here's how your comment will look:
{{ form.cleaned_data.name }} said:
...
Cleaned_data references a post validation version of the form data. There will be more about form validation in later chapters. You could also reference the pre-cleaning version {{ form.data.name }}. Because your comment hasn't been posted you can't display the submit date. There is a timestamp available but no corresponding template filter to display it nicely, so we'll have to leave that out.
On p127 add markup to your installed apps and modify your custom preview.html template
{{ comment|markdown:"safe" }}
to make it work. Also on p127 is a posted.html template. Again there is a default template for this you can use. Modifying it to include the example <a href="{{ object.get_absolute_url }}"> doesn't work as expected, as the url only references the comment itself, so change it to <a href="{{ comment.content_object.get_absolute_url }}"> which will link back to the weblog entry. On p128 the variable {{ comment.person_name }} is now {{ comment.name }} and on p129 the tag get_free_comment_count is now just get_comment_count.
Comment Moderation
The new comments app includes some anti-spam techniques such as a honeypot formfield that is hidden to the viewer by css but not to a prospective spam bot, but the other techniques described still seem worth implementing. I managed to implement the Akismet code but it fails to verify my key (Note: 17-Nov-08 ldm616 provided a solution, I have amended the code example). The code that is added to your models.py has a fair amount of changes to it so I'll include all the added code to coltrane.models rather than just the changed bits.
#coltrane.models.py
from akismet.akismet import Akismet
from django.contrib.comments.signals import comment_will_be_posted
from django.contrib.sites.models import Site
from django.db.models import signals
from django.utils.encoding import smart_str
...
def moderate_comment(sender,**kwargs):
comment = kwargs['comment']
if not comment.id:
entry = comment.content_object
delta = datetime.datetime.now() - entry.pub_date
if delta.days > 30:
comment.is_public = False
else:
akismet_api = Akismet(key=settings.AKISMET_API_KEY,
blog_url="http://%s/" % Site.objects.get_current().domain)
if akismet_api.verify_key():
akismet_data = { 'comment_type': 'comment',
'referrer': '',
'user_ip': comment.ip_address,
'user_agent': '',
'comment_author': comment.user_name }
if akismet_api.comment_check(smart_str(comment.comment), akismet_data, build_data=True):
comment.is_public = False
comment_will_be_posted.connect(moderate_comment)
The changes to the code are due to the re-factor of signals in Django and also the implementation of signals in the new comments app. A signal receiver function should take just the sender, and then an arbitrary list of keyword arguments (sender, **kwargs). I could have just added instance = kwargs['comment'] and left the other references to instance alone, but seeing as the comments app passes it in to the function as 'comment' I prefer to change them. Where the code refers to the actual blog entry that the comment attaches to (entry = instance.get_content_object() ) I did two things. Firstly I looked at the code for the comment class and noticed that there is an attribute content_object available in the new class. Secondly I altered my code to load up the debugger to have a look at the object when actually posting a comment to see what is actually in the comment object when it is used in this context.
#coltrane.models.py
def moderate_comment(sender,**kwargs):
comment = kwargs['comment']
import pdb
pdb.set_trace()
...
The debugger pdb will drop your runserver into a commandline when you post a comment where you can introspect the comment object. From the commandline you can do dir(comment) to see what functions and attributes the comment object has. So dir(comment) revealed a list of attributes and methods of which content_object is one of them. When I typed comment.content_object it prints the object that the comment is attached to so I know that is what I want for the reworked moderate_comment function. You can distinguish the functions from the attributes in the dir list by the fact that they will have action verbs such as get, save, set etc in the name. If you want help with the debugger, watch
Eric Holscher's screencasts on the topic which is a really good overview of pdb and debugging in general.
Lastly, the new comments app defines its own signal functions and the most appropriate one for us to use is
comment_will_be_posted so we connect that to the receiver function with
comment_will_be_posted.connect(moderate_comment).
Email Notification of Comments
As per the admonition on p136 I tested my email settings and used a gmail account which requires a secure TLS connection.
>>> from django.core import mail
>>> mail.send_mail('Subject','Message','from@address',['to@address'])
After determining that my email worked I added the mail_managers code replacing instance with comment, and get_content_object() with content_object
from django.core.mail import mail_managers
email_body = "%s posted a new comment on the entry '%s'."
mail_managers("New comment posted",
email_body % (comment.user_name,
comment.content_object))
Adding Feeds
No modification of code is needed in this section. Everything worked as per the books examples. Once you've implemented the LatestEntries and Categories feed you can view them in your browser at http://localhost:8000/feeds and http://localhost:8000/feeds/categories/your_category_here/. Of course you need to have populated some entries with [your_category_here] to see them in the feed.
So that's it for the weblog. Of course there is a fair bit to do still, like fill out some of the other standard templates, and design a css for it - but really the hard work is done, so it's on to Chapter 8 and the social code sharing site.