A few days ago, I migrated my static website to this custom developed Django website. I did that because I wanted to get rid of my Wordpress site where I was selling the course. I am now selling the course on this site (which you can find here).
Previously, I used Jekyll (a static site generator) to create this site and used formspree to host the form. This worked fine, but since I can easily add in a contact form myself in this "app", I figured why not. I first had it without any catpcha, but that didn't last long, I started to get quite a few spam messages like this one:
Since I added captcha, spam has been massively reduced. So, let's jump right into it.
Go to hCpatcha.com and click on 'Signup' (pick the free version) and create an account.
When you are logged in, click on 'add site' and fill in the details there.
You will then get to see your sitekey. This is a public key, so there is no issue in sharing it. Here is mine:
You will need that, so keep note of that. Up next, we need one more thing. The secret key. This one should not be shared and should be saved as an environment variable on the server. You can find this secret key on the settings page: https://dashboard.hcaptcha.com/settings.
For this, I will use a normal MVT Django set up, to demonstrate how this works. Here is what I added to my urls.py
file to create the URL for the form:
path('contact/', user_views.contact, name="contact"),
Up next, this is how I render my form in the template:
<form
action="{% url 'contact' %}"
method="POST"
>
{% csrf_token %}
{% for message in form.email.errors %}
<div class="notification is-danger">{{ message }}</div>
{% endfor %}
<div class="field">
<label class="label">Your email</label>
<div class="control">
{{ form.email }}
</div>
</div>
{% for message in form.message.errors %}
<div class="notification is-danger">{{ message }}</div>
{% endfor %}
<div class="field">
<label class="label">Your message</label>
<div class="control">
{{ form.message }}
</div>
</div>
<div class="h-captcha" data-sitekey="e01397bb-3069-4769-b7e8-528affaa87e2"></div>
{% for message in form.captcha.errors %}
<div class="notification is-danger">{{ message }}</div>
{% endfor %}
<button class="button is-info" style="margin-top: 20px; width: 100%" type="submit">Send</button>
</form>
Please note the data-sitekey
part. That's my sitekey that I added there (which I got earlier from hCaptcha). Please replace that with your own if you are going to use that form (it's compatible with Bulma css framework).
Also add the captcha javascript file in the header
:
<script src="https://hcaptcha.com/1/api.js" async="" defer=""></script>
And here is the views.py
:
from django.core.mail import EmailMessage
from users.forms import ContactForm
def contact(request):
if request.method == 'POST':
data = request.POST.copy()
if 'h-captcha-response' in data:
data['captcha'] = data['h-captcha-response']
form = ContactForm(data)
if form.is_valid():
email = EmailMessage(
'New contact form message',
form.cleaned_data['message'],
'hello@example.com',
['hello@example.com'],
reply_to=[form.cleaned_data['email']]
)
email.send(fail_silently=False)
messages.success(request, 'Message sent successfully!')
return redirect('work-with-me')
else:
form = ContactForm()
return render(request, 'contact.html', {'form': form})
From the form you probably noticed that we copied the POST
data and then modify it. We do this, because our form won't be able to process a field that has dashes in it. Dashes are often seen as a -
symbol (for subtracting a value). Therefore, we copy it to a new key that we are able to process through our form (called captcha
). The goal of the form is to send a message back to us. In this case, we use EmailMessage
instead of just using send_email
as we want to set the reply_to
back to the person that filled in the form. So, if we want to react to a message, we can just hit reply without having to think about it.
In the form, we have to validate the captcha. If it's not valid, we have to return the form with an error back to the user.
Our form will look like this:
from django import forms
from django.core.exceptions import ValidationError
import requests
from django.conf import settings
def validate_captcha(value):
data = {'secret': settings.HCAPTCHA_SECRET_KEY, 'response': value}
response = requests.post('https://hcaptcha.com/siteverify', data)
if not 'success' in response.json() or not response.json()['success']:
raise ValidationError('hcaptcha is not correct')
class ContactForm(forms.Form):
email = forms.EmailField(max_length=200)
message = forms.CharField(widget=forms.Textarea(attrs={'class':'input', 'rows': 10, 'placeholder': 'Hi...'}), max_length=2000)
captcha = forms.CharField(max_length=10000, validators=[validate_captcha])
Let's check out the ContactForm
itself first. We added two fields. email
and message
. Nothing really crazy going on there. Up next, we need to validate the captcha
field to make sure everything goes through nicely. For that, we created a validator
. That's what the function above the form is for.
So, in the validator we do make a call to the hcaptcha
endpoint with the data that has been provide to us. If that call passes, then we get a JSON response back. Based on that, we can validate if everything was okay.
Hope this helps!
validators.py
.Stan is professional web developer working mainly with Django and VueJS. With years of experience under the belt, he is comfortable writing about his past mistakes and ongoing learnings.