How to stop spam on a Django blog

Don’t rely on the honey pot to attract, and hold at bay, spam comments for your Django blog.

Stopping spam completely for a blog built using Django takes a three-pronged approach of Akismet, date-based closing of comments and a cron-run script that removes spam comments from your database. And fortunately, all three steps are quite easy to implement.

Note: I’m using Python 2.5 and Django 1.0.2

Install and enable Akismet

The best front-line defense for stopping spam to your Django blog is to use Akismet via the Python API.

Here are the steps:

First, download akismet.py and put it on your PYTHONPATH. Then get a Akismet API key by signing up for a free WordPress blog.

Put the API key in the apikey.txt located in your Akismet directory. You’ll also need to add the URL for your blog. Add this line to your Django project settings.py: AKISMET_API_KEY = '<your_api_key>'

Open up your blog models.py and add the following code snippet (this will also enable e-mail notification for new, public comments):

from django.contrib.comments.signals import comment_was_posted
from django.utils.encoding import smart_str
from django.core.mail import mail_managers
import akismet
from django.conf import settings
from django.contrib.sites.models import Site
...
def moderate_comment(sender, comment, request, **kwargs):
    ak = akismet.Akismet(
        key = settings.AKISMET_API_KEY,
            blog_url = 'http://%s/' % Site.objects.get_current().domain
)
    data = {
        'user_ip': request.META.get('REMOTE_ADDR', ''),
        'user_agent': request.META.get('HTTP_USER_AGENT', ''),
        'referrer': request.META.get('HTTP_REFERRER', ''),
        'comment_type': 'comment',
        'comment_author': smart_str(comment.user_name),
    }
    if ak.comment_check(smart_str(comment.comment), data=data, build_data=True):
        comment.is_public = False
        comment.save()

    if comment.is_public:   
        email_body = "%s"
        mail_managers ("New comment posted", email_body % (comment.get_as_text()))

comment_was_posted.connect(moderate_comment)`

Save all changes and restart your Apache server.

Close comments after 60 days

Even with Akismet installed and working, I still saw a small amount of spam comments leaking through the filter. I also experienced a number of comments making their way into my database, albeit with the is_public field set to False.

To resolve this, I decided to remove the ability to add comments after 60 days from a given entry’s publication.

Here’s how to do this:

Add the following snippet to your blog models.py:

    @property
    def comments_expired(self):
        delta = datetime.datetime.now() - self.pub_date
        return delta.days < 60

Modify your blog entry_detail.html to include the following markup (assumes you also have a Boolean on your model to enable comments) :

{% if object.enable_comments %}
    {% if object.comments_expired %}
    <div id="comments_open">
        <h2>Post a comment</h2>
        <p id="comment_policy">Please use <a href="http://daringfireball.net/projects/markdown/syntax">Markdown</a> syntax for formatting. No <abbr title="hypertext markup language">HTML</abbr> is allowed. By using this comment form, it's assumed that you agree with the terms of <a href="http://patrickbeeson.com/about/comments/" title="Comment policy for this Web site">my comment policy</a>.</p>
        {% render_comment_form for object %}
    </div>
    {% else %}
    <div id="comments_closed">
        <h2 style="clear: both;">Comments no longer accepted for this entry.</h2>
        <p id="comment_policy">To prevent spam, comments are no longer allowed after 60 days.</p>
    </div>
    {% endif %}
{% else %}
<div id="comments_closed">
    <h2 style="clear: both;">Comments are closed for this entry.</h2>
</div>
{% endif %}

Save the changes.

At this point, you’ve probably stopped 99 percent of spam comments from getting published on your Django blog entries, or registered in your database.

Delete all non-public comments from the database using cron

As a final comments management measure, you can create a cron to run a script that will automatically remove comments with is_public set to False at a regular interval.

Here’s how to do this:

First, add the following script to a directory called “bin” inside your blog application directory (on your PYTHONPATH):

import os
import time
import optparse
import datetime

def delete_spam_comments(verbose=False):
    from django.contrib.comments.models import Comment
    spam_comments = Comment.objects.filter(is_public=False)
    deleted_count = spam_comments.count()

    for Comment in spam_comments:
        Comment.delete()
    if spam_comments:
        print "Removed %s spam comments from database" % deleted_count

if __name__ == '__main__':
    parser = optparse.OptionParser()
    parser.add_option('--settings')
    parser.add_option('-v', '--verbose', action="store_true")
    options, args = parser.parse_args()
    if options.settings:
        os.environ["DJANGO_SETTINGS_MODULE"] = options.settings
    delete_spam_comments(options.verbose)

Create a crontab that includes the following command: 0 12 * * 0 /usr/local/bin/python2.5 /home/pbeeson/path/to/your/project/blog/bin/delete_spam_comments.py

You can adjust the interval and add logging if needed. You might need to define your PYTHONPATH and Django environment settings depending on your server configuration.

Your Django blog should now be free from spam both public facing and in your database.

Read full article at “Entries for Django tag | patrickbeeson.com”

Leave a comment