A very simple, yet powerful, Django captcha application for adding CAPTCHA image challenges to forms
—
Django Simple Captcha provides dedicated serializers for integrating captcha validation into Django REST Framework APIs. These serializers enable captcha protection for API endpoints while maintaining the same validation logic as form-based implementations.
Standalone serializer for API endpoints that need captcha validation without being tied to a specific model.
class CaptchaSerializer(serializers.Serializer):
captcha_code = serializers.CharField(max_length=32, write_only=True, required=True)
captcha_hashkey = serializers.CharField(max_length=40, write_only=True, required=True)
def run_validation(data=empty):
"""
Validate captcha data (captcha fields remain in validated data).
Parameters:
- data: dict, input data containing captcha_code and captcha_hashkey
Returns:
dict: Validated data including captcha fields
Raises:
ValidationError: If captcha validation fails or fields are missing
"""Model serializer that includes captcha validation for API endpoints that work with Django model instances.
class CaptchaModelSerializer(serializers.ModelSerializer):
captcha_code = serializers.CharField(max_length=32, write_only=True, required=True)
captcha_hashkey = serializers.CharField(max_length=40, write_only=True, required=True)
def run_validation(data=empty):
"""
Validate captcha data (captcha fields remain in validated data).
Parameters:
- data: dict, input data containing model fields plus captcha fields
Returns:
dict: Validated data including captcha fields
Raises:
ValidationError: If captcha validation fails or model validation fails
"""from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from captcha.serializers import CaptchaSerializer
from captcha.models import CaptchaStore
class ContactAPIView(APIView):
def get(self, request):
"""Generate new captcha for the form."""
captcha_key = CaptchaStore.generate_key()
return Response({
'captcha_key': captcha_key,
'captcha_image_url': f'/captcha/image/{captcha_key}/',
'captcha_audio_url': f'/captcha/audio/{captcha_key}.wav',
})
def post(self, request):
"""Process contact form with captcha validation."""
# Create a combined serializer
class ContactSerializer(CaptchaSerializer):
name = serializers.CharField(max_length=100)
email = serializers.EmailField()
message = serializers.CharField()
serializer = ContactSerializer(data=request.data)
if serializer.is_valid():
# Captcha was validated automatically
contact_data = serializer.validated_data
# Process contact form (send email, save to database, etc.)
return Response({'status': 'success'}, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)from rest_framework import generics
from django.contrib.auth.models import User
from captcha.serializers import CaptchaModelSerializer
class UserRegistrationSerializer(CaptchaModelSerializer):
password = serializers.CharField(write_only=True)
class Meta:
model = User
fields = ['username', 'email', 'password', 'captcha_code', 'captcha_hashkey']
def create(self, validated_data):
# Captcha fields are already removed by run_validation
password = validated_data.pop('password')
user = User.objects.create_user(**validated_data)
user.set_password(password)
user.save()
return user
class UserRegistrationView(generics.CreateAPIView):
serializer_class = UserRegistrationSerializer
queryset = User.objects.all()from rest_framework import viewsets, decorators
from rest_framework.response import Response
from captcha.models import CaptchaStore
from captcha.serializers import CaptchaModelSerializer
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
def get_serializer_class(self):
if self.action == 'create':
# Use captcha protection for comment creation
class CommentCaptchaSerializer(CaptchaModelSerializer):
class Meta:
model = Comment
fields = ['content', 'captcha_code', 'captcha_hashkey']
return CommentCaptchaSerializer
return CommentSerializer
@decorators.action(detail=False, methods=['get'])
def captcha(self, request):
"""Get new captcha for comment form."""
captcha_key = CaptchaStore.generate_key()
return Response({
'captcha_key': captcha_key,
'captcha_image_url': f'/captcha/image/{captcha_key}/',
})from captcha.serializers import CaptchaSerializer
from captcha.validators import captcha_validate
from django.core.exceptions import ValidationError
class CustomCaptchaSerializer(serializers.Serializer):
email = serializers.EmailField()
captcha_code = serializers.CharField(max_length=32)
captcha_hashkey = serializers.CharField(max_length=40)
def validate(self, data):
"""Custom captcha validation with additional logic."""
try:
captcha_validate(data['captcha_hashkey'], data['captcha_code'])
except ValidationError as e:
raise serializers.ValidationError({'captcha': e.message})
# Remove captcha fields from validated data
data.pop('captcha_code', None)
data.pop('captcha_hashkey', None)
# Additional custom validation
email = data.get('email')
if User.objects.filter(email=email).exists():
raise serializers.ValidationError({'email': 'Email already exists'})
return data// JavaScript for API captcha integration
class CaptchaAPI {
constructor() {
this.captchaData = null;
}
async loadCaptcha() {
const response = await fetch('/api/contact/', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
this.captchaData = await response.json();
// Update UI with new captcha
document.getElementById('captcha-image').src = this.captchaData.captcha_image_url;
document.getElementById('captcha-key').value = this.captchaData.captcha_key;
}
async submitForm(formData) {
// Add captcha data to form
formData.captcha_hashkey = this.captchaData.captcha_key;
formData.captcha_code = document.getElementById('captcha-input').value;
const response = await fetch('/api/contact/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCookie('csrftoken'),
},
body: JSON.stringify(formData),
});
if (response.ok) {
alert('Form submitted successfully!');
} else {
const errors = await response.json();
if (errors.captcha) {
// Refresh captcha on validation error
await this.loadCaptcha();
alert('Captcha validation failed. Please try again.');
}
}
}
}
// Usage
const captchaAPI = new CaptchaAPI();
captchaAPI.loadCaptcha();from rest_framework import serializers
from captcha.serializers import CaptchaSerializer
class BulkCreateSerializer(CaptchaSerializer):
items = serializers.ListField(
child=serializers.DictField(),
min_length=1,
max_length=10
)
def create(self, validated_data):
# Captcha already validated, process bulk creation
items_data = validated_data['items']
created_items = []
for item_data in items_data:
item = MyModel.objects.create(**item_data)
created_items.append(item)
return created_itemsWhen captcha validation fails, the API returns standard DRF validation error format:
{
"captcha_code": ["This field is required."],
"captcha_hashkey": ["Invalid captcha."]
}Successful validation removes captcha fields from the response, returning only the relevant model/business data.
write_only=Truevalidated_data after successful validationInstall with Tessl CLI
npx tessl i tessl/pypi-django-simple-captcha