integrating recaptcha into a contact form in a django project Apr
15
1
0

I finally had to add a spam blocker on my contact page. Those insidious bots are disturbingly effective at sniffing out forms.

I knew this was going to happen eventually, but I feel captchas are an inconvenience to users so I was trying to delay the inevitable, but it's like attempting to hold back the tide. Only a month after launch I'm receiving over 10 spams a day from my site.

I went with reCAPTCHA. It seemed like it was going to be easy to integrate, it has a Python Plugin, and users are already familiar with how captcha's work. It turns out I was right on all fronts. It's been the right decision.

Here's how I integrated reCAPTCHA into my site. This is also how I wrote my contact application.

First I added two variables to settings.py: RECAPTCHA_PUBLIC_KEY and RECAPTCHA_PRIVATE_KEY and set the values accordingly. Actually, I added these to local_settings.py and imported them into settings.py because I have a policy to never commit passwords and keys into the repository, but you get the idea.

If you want you can write a custom context processor to include these variables in every template, but since I only had one view I needed this for I didn't bother.

I then installed the recapcha-client python library into my virtual environment using pip (you can also use easy_install). Unfortunately I couldn't find any documentation on how to use the recaptcha client, but luckily there's not much to the package and it's easy enough look at the source to figure out what to do.

pip install recaptcha-client

or

easy_install recaptcha-client

Following Seek Nuance's tutorial I then modified my view to import captcha and to check reCAPTCHA before processing the form.

from recaptcha.client import captcha

from django.shortcuts import render_to_response
from django.template import RequestContext
from django.core.mail import send_mail, EmailMessage
from django.http import Http404, HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.contrib.sites.models import Site
from django.conf import settings

from contact.models import Contact
from contact.forms import ContactForm


# Create your views here.
def contact(request):
    template_vars = {
        'section': 'contact',
    }

    template_vars['RECAPTCHA_PUBLIC_KEY'] = settings.RECAPTCHA_PUBLIC_KEY

    if request.POST:
        contact_form = ContactForm(request.POST)
        template_vars['contact_form'] = contact_form

        # Check the form captcha.  If not good, pass the template an error code
        captcha_response = captcha.submit(request.POST.get("recaptcha_challenge_field", None),
                                          request.POST.get("recaptcha_response_field", None),
                                          settings.RECAPTCHA_PRIVATE_KEY,
                                          request.META.get("REMOTE_ADDR", None))

        if not captcha_response.is_valid:
            template_vars['captcha_error'] = "&error=%s" % captcha_response.error_code

        elif contact_form.is_valid():
            send(contact_form)
            return HttpResponseRedirect(reverse('contact_success'))

    else:
        contact_form = ContactForm()
        template_vars['contact_form'] = contact_form

    return render_to_response('contact/contact.html', template_vars, context_instance=RequestContext(request))

Copying straight from the reCAPTCHA client documentation, I then added their Challenge API code into my template. You can put this anywhere in the form you want to apply reCAPTCHA to like so:

<form id="contact-form" action="." method="post" />
<table id="contact">
    {{ contact_form.as_table }}

    <!-- recaptcha -->
    <tr>
        <td></td>
        <td>
          <script type="text/javascript">var RecaptchaOptions = {theme : 'white'};</script>
          <script type="text/javascript" src="http://api.recaptcha.net/challenge?k={{ RECAPTCHA_PUBLIC_KEY }}{{ captcha_error}}">
          </script>

          <noscript>
            <iframe src="http://api.recaptcha.net/noscript?k={{ RECAPTCHA_PUBLIC_KEY }}{{ captcha_error}}"
                    height="300" width="500" frameborder="0"></iframe><br>
            <textarea name="recaptcha_challenge_field" rows="3" cols="40">
            </textarea>
            <input type="hidden" name="recaptcha_response_field" 
                   value="manual_challenge">
          </noscript>
        </td>
    </tr>
    <!-- recaptcha -->

    <tr>
        <td></td>
        <td><input class="button" type="submit" name="submit" value="send"></td>
    </tr>
</table>
</form>

And that was it. It was pretty fast to set up. Now if only I could read that thing, but that's a different problem.

For completeness on how I wrote the contact mail application here's the rest of the views, models, forms, and urls.

# the rest of views.py

def send(contact_form):
    site = Site.objects.get(id=settings.SITE_ID)
    contact = Contact.objects.all()[0]

    # construct email
    from_email = '"%s" <%s>' % (contact_form.cleaned_data['name'], contact_form.cleaned_data['email'])
    send_to = '"%s" <%s>' % (contact.user.first_name + ' ' + contact.user.last_name, contact.user.email)
    subject = '[' + site.name + ' contact] ' + contact_form.cleaned_data['subject']

    if contact_form.cleaned_data['website']:
        message = 'website: %s\n\n' % (contact_form.cleaned_data['website'])
    else:
        message = ''

    message += contact_form.cleaned_data['message']

    #send email
    email = EmailMessage(subject, message, from_email, (send_to,), headers = {'Reply-To': from_email})
    email.send(fail_silently=False)


def success(request):
    template_vars = {
        'section': 'contact',
    }

    return render_to_response('contact/success.html', template_vars, context_instance=RequestContext(request))

models.py

from django.db import models
from django.contrib.auth.models import User


# Create your models here.
class Contact(models.Model):
    user = models.ForeignKey(User, help_text='This is the person who will be receiving emails from the website contact.')

    def __unicode__(self):
        return self.user.username

    # for now set up as a singleton
    def save(self):
        self.id = 1
        super(Contact, self).save()

    def delete(self):
        pass

forms.py

from django import forms
from django.contrib.auth.models import User


class ContactForm(forms.Form):
    name = forms.CharField(max_length=100)
    email = forms.EmailField()
    website = forms.URLField(required=False)
    subject = forms.CharField(max_length=100)
    message = forms.CharField(widget=forms.Textarea)

urls.py

from django.conf.urls.defaults import *

urlpatterns = patterns('digitaldreamer.contact.views',
    url(r'^success/, 'success', name='contact_success'),
    url(r'^, 'contact', name='contact'),
)
Bookmark and Share
blog comments powered by Disqus