在 Django 项目中,为评论、注册、留言板等功能添加验证码可以有效防止垃圾内容。在本文中,我们将详细讲解如何使用 django-simple-captcha 进行验证码配置,并且结合 Ajax 实现验证码错误自动刷新与页面提示的完整方案。


1. 安装 django-simple-captcha

首先,我们需要安装 django-simple-captcha。在项目目录下运行以下命令:

pip install django-simple-captcha

安装完成后,在 settings.py 中添加:

INSTALLED_APPS = [
    ...
    'captcha',
]

然后,在主 URL 配置(如 urls.py)中引入 captcha 路由。这里我们提供两种方式,一种是使用内置视图,另一种是自定义刷新视图(推荐这样可以方便调试 Ajax 请求):

from django.urls import path, include
from your_app.views import custom_captcha_refresh  # 引入自定义刷新视图

urlpatterns = [
    ...
    path("captcha/refresh/", custom_captcha_refresh, name="captcha-refresh"), # 不限制目录
    path('captcha/', include('captcha.urls')), # 必须settings.pp那个目录
]

这样,当前端请求 captcha/refresh/ 时,就会调用我们的自定义视图生成新的验证码数据。


2. 自定义验证码刷新视图

有时内置的刷新视图对 Ajax 请求有严格要求(例如必须是 Ajax 请求,否则返回 404)。为了更灵活、方便,我们可以自定义一个刷新视图。在 views.py 中添加如下代码:

from captcha.models import CaptchaStore
from captcha.helpers import captcha_image_url
from django.http import JsonResponse

def custom_captcha_refresh(request):
    new_key = CaptchaStore.generate_key()
    new_image_url = captcha_image_url(new_key)
    return JsonResponse({'key': new_key, 'image_url': new_image_url})

自定义视图会生成新的验证码的 key 和图片 URL,并以 JSON 格式返回,这样可以在前端用 Ajax 来更新验证码。


3. 在表单中添加验证码字段

在 Django 的表单文件(通常是 forms.py)中,引入 CaptchaField 并进行配置:

from django import forms
from captcha.fields import CaptchaField

class CommentForm(forms.Form):
    content = forms.CharField(widget=forms.Textarea, label="评论内容")
    nickname = forms.CharField(max_length=50, label="昵称")
    email = forms.EmailField(label="邮箱")
    website = forms.URLField(required=False, label="网站")
    captcha = CaptchaField(
        error_messages={'invalid': '验证码输入错误,请重新输入'}
    )

这样,在用户提交表单时,验证码验证失败时将显示自定义错误提示。


4. 在模板中渲染验证码

在你的 HTML 模板中,通过 Django 模板标签渲染验证码,并显示验证码图片、输入框等。例如:

<div class="captcha-field">
    {{ form.captcha }}
    <img src="{% url 'captcha-image' form.captcha.key %}" alt="验证码" id="captcha-img">
    <p>点击图片可刷新验证码</p>
</div>

为验证码图片绑定点击事件,防止浏览器缓存并触发刷新:

<script>
document.getElementById('captcha-img').addEventListener('click', function() {
    this.src = this.src.split('?')[0] + '?' + new Date().getTime();
});
</script>

5. Ajax 提交表单及验证码错误处理

为了增强用户体验,我们通过 Ajax 提交评论表单,并根据返回的 JSON 数据判断是否提交成功。如果验证码错误,自动刷新验证码;提交成功则自动刷新页面,且不弹出 alert 提示成功消息,而是在页面中显示内联的成功消息。

下面是一份完整的前端代码示例:

document.getElementById('comment-form').addEventListener('submit', function(event) {
    event.preventDefault(); // 阻止默认表单提交,使用 Ajax 提交
    const formData = new FormData(this);

    fetch(this.action, {
        method: 'POST',
        headers: {
            'X-Requested-With': 'XMLHttpRequest'
        },
        body: formData
    })
    .then(response => response.json())
    .then(data => {
        if (data.success) {
            // 在页面上显示成功消息而不使用 alert
            let successMsg = document.createElement('div');
            successMsg.className = 'success-message';
            successMsg.textContent = '提交成功!即将刷新页面…';
            document.getElementById('comment-form').prepend(successMsg);
            
            // 2秒后自动刷新页面
            setTimeout(() => {
                window.location.reload();
            }, 2000);
        } else {
            // 如果存在验证码错误,优先检查 errors.captcha
            if (data.errors && data.errors.captcha) {
                let captchaError = data.errors.captcha[0];
                // 如果返回的是对象,则取出 message 字段,否则使用直接的错误提示信息
                let errorMsg = (typeof captchaError === 'object' && captchaError.message)
                                ? captchaError.message
                                : captchaError;
                alert(errorMsg);

                // 如果验证码错误,自动刷新验证码
                fetch('/captcha/refresh/', {
                    method: 'GET',
                    headers: { 'X-Requested-With': 'XMLHttpRequest' }
                })
                .then(response => response.json())
                .then(captchaData => {
                    // 更新验证码图片
                    const captchaImage = document.getElementById('captcha-img');
                    if (captchaImage) {
                        captchaImage.src = captchaData.image_url;
                    }
                    // 更新隐藏验证码 key(一般为captcha_0)
                    const captchaKeyInput = document.querySelector('input[name="captcha_0"]');
                    if (captchaKeyInput) {
                        captchaKeyInput.value = captchaData.key;
                    }
                    // 清空验证码输入框(假设 name 为 captcha_1)
                    const captchaInput = document.querySelector('.captcha-field input[name="captcha_1"]');
                    if (captchaInput) {
                        captchaInput.value = "";
                    }
                })
                .catch(error => console.error('刷新验证码失败:', error));
            } else {
                // 若有其他错误,则统一提示
                alert("提交失败,请检查输入!");
            }
        }
    })
    .catch(error => console.error('提交错误:', error));
});

该代码实现的逻辑说明:

  • 当表单通过 Ajax 提交后,根据返回的 data.success 判断是否成功。
  • 成功时在页面上创建一个内联消息提示,并在 2 秒后刷新页面。
  • 如果返回的错误中包含验证码错误信息,则弹出错误提示,之后调用 /captcha/refresh/ 接口自动刷新验证码图片及相应的隐藏输入框(验证码 key),并清空用户输入的验证码内容。

6. 服务器端处理 Ajax 提交的表单

在 views.py 中处理 Ajax 提交的评论逻辑,同时返回 JSON 数据。以下是一个简单的视图示例:

from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from .forms import CommentForm

@csrf_exempt  # 开发阶段可用,生产环境建议正确处理 CSRF
def submit_comment(request):
    if request.method == 'POST':
        form = CommentForm(request.POST)
        if form.is_valid():
            # 这里可以保存评论到数据库
            return JsonResponse({'success': True})
        else:
            return JsonResponse({'success': False, 'errors': form.errors})
    return JsonResponse({'success': False})

这样,服务器会返回一个 JSON 格式的响应,便于前端根据具体错误进行提示或刷新验证码。


7. 总结

本文详细介绍了:

  • 安装与配置 django-simple-captcha
  • 添加自定义验证码刷新视图 custom_captcha_refresh
  • 在 URL 配置中使用 path("captcha/refresh/", custom_captcha_refresh, name="captcha-refresh") 注册该视图;
  • 在表单中添加验证码字段,并自定义错误提示;
  • 在模板中渲染验证码及实现图片点击刷新;
  • 使用 Ajax 提交表单及处理验证码错误时自动刷新验证码,同时在提交成功后在页面显示内联成功提示并自动刷新页面。

通过以上步骤,你可以构建一个用户体验更友好的验证码交互流程。如果还想进一步优化,比如使用更美观的 UI 提示组件或结合前端框架集成动画效果,这篇文章已经为你打下了坚实的基础。