Django¶
Django is sync-first, so the adapter bridges CaptchaManager's async API
through asgiref.sync.async_to_sync.
Setup¶
Keep a single module-level manager — reuse it across views and forms:
# app/captcha.py
from captchakit import (
CaptchaManager,
ImageRenderer,
MemoryStorage,
TextChallengeFactory,
)
manager = CaptchaManager(
factory=TextChallengeFactory(length=5),
renderer=ImageRenderer(),
storage=MemoryStorage(),
ttl=180.0,
)
Use
RedisStorageorPostgresStorageif you run more than one worker —MemoryStoragecan't share challenges across processes.
Image view¶
# urls.py
from django.urls import path
from captchakit.adapters.django import captcha_image_view
from app.captcha import manager
urlpatterns = [
path("captcha/<str:challenge_id>.png", captcha_image_view(manager)),
]
Form field¶
from django import forms
from captchakit.adapters.django import CaptchaField
from app.captcha import manager
class RegisterForm(forms.Form):
username = forms.CharField()
captcha = CaptchaField(manager=manager)
- Issues a fresh challenge on unbound form construction.
clean()verifies the submitted answer; raisesValidationErroron wrong / expired / exhausted attempts.- The widget produces
<img>+ hiddencaptcha_id+ text input — render with{{ form.captcha }}.
Customising the image URL¶
If your URL pattern isn't /captcha/<id>.png, pass the template:
Async views¶
If your project already runs on ASGI, you can skip the sync bridge and
call the manager directly from async views. The form field still works —
async_to_sync is safe inside an ASGI loop.