CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-django-simple-captcha

A very simple, yet powerful, Django captcha application for adding CAPTCHA image challenges to forms

Pending
Overview
Eval results
Files

rest-framework.mddocs/

Django REST Framework Integration

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.

Capabilities

CaptchaSerializer

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
        """

CaptchaModelSerializer

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
        """

Usage Examples

Basic API View with Captcha

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)

Model-Based API with Captcha

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()

ViewSet with Captcha Protection

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}/',
        })

Custom Validation Logic

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

AJAX Frontend Integration

// 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();

Batch Operations with Captcha

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_items

API Response Format

When 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.

Integration Notes

  • Captcha fields are automatically marked as write_only=True
  • Captcha validation occurs before model validation
  • Failed captcha validation prevents the request from proceeding
  • Captcha fields are removed from validated_data after successful validation
  • Both serializers work with the same captcha generation and validation backend as form-based views

Install with Tessl CLI

npx tessl i tessl/pypi-django-simple-captcha

docs

configuration.md

form-integration.md

index.md

models-validation.md

rest-framework.md

views-urls.md

tile.json