
1. 理解Django CSRF保护机制
在Django中,为了防止跨站请求伪造(CSRF)攻击,框架对所有非幂等HTTP方法(如POST、PUT、DELETE)强制执行CSRF验证。这意味着,当你从前端通过AJAX发送POST请求到Django后端时,请求中必须包含一个有效的CSRF令牌。如果缺少这个令牌,或者令牌不匹配,Django会拒绝该请求,导致数据无法更新。
尽管在某些情况下,开发者可能会在视图函数上使用@csrf_exempt装饰器来暂时禁用CSRF保护,但这并非最佳实践,因为它会降低应用的安全性。理想情况下,即使是AJAX请求,也应该正确地包含并验证CSRF令牌。本教程将指导你如何在前端正确地获取并发送CSRF令牌,从而在不牺牲安全性的前提下实现数据更新。
2. 前端集成CSRF令牌
为了在AJAX请求中包含CSRF令牌,我们需要执行以下步骤:
- 获取CSRF令牌:CSRF令牌通常作为隐藏字段或cookie在Django渲染的页面中提供。我们可以通过JavaScript从DOM中提取它,或者从名为csrftoken的cookie中获取。从cookie获取是更通用的方法,尤其适用于AJAX请求。
- 在请求头中发送令牌:获取到令牌后,将其作为X-CSRFToken头部字段添加到你的AJAX请求中。
以下是获取CSRF令牌的JavaScript函数以及如何在fetch API中使用的示例:
{% extends "base/room_home.html" %}
{% block content %}
{{ room_bills.title }}
Due: {{ room_bills.due }}
Paid Members:
{% for submission in submissions %}
-
{{ submission.user.username }} {{ submission.text }}
{% endfor %}
Did not pay members:
{% for user in did_not_submit %}
- {{ user.username }}
{% endfor %}
{% endblock %}代码改进点说明:
- CSRF令牌获取:引入了getCookie函数来安全地从浏览器cookie中获取csrftoken。
- 请求头:在fetch请求的headers中加入了'X-CSRFToken': csrftoken。
- 元素ID:将select元素的id改为id="status-{{ submission.id }}"以确保唯一性,并避免潜在的ID冲突。
- 事件监听:将onblur改为onchange,因为对于select元素,onchange通常是更合适的事件,它在用户选择一个新选项时触发。
- 初始选中状态:在页面加载时,根据后端数据设置select元素的初始选中状态,确保刷新后能显示当前状态。这通过{% if submission.status == 'P' %}selected{% endif %}在模板中完成,并在DOMContentLoaded中再次确保。
- 错误处理:为fetch请求添加了.then().catch()链,以便更好地处理网络请求成功与否的响应,并捕获潜在的错误。
3. 后端视图处理
在后端,一旦前端正确地发送了CSRF令牌,Django的CSRF中间件会自动验证它。因此,你的视图函数可以保持相对简洁。最重要的是,如果你之前为了调试或其他原因使用了@csrf_exempt,现在应该考虑移除它,让Django的CSRF保护机制发挥作用。
import json
from django.http import JsonResponse
from django.views.decorators.http import require_POST # 推荐使用
# from django.views.decorators.csrf import csrf_exempt # 如果前端已发送CSRF,建议移除此行
# 推荐使用 @require_POST 确保只响应POST请求
# 如果前端已发送CSRF令牌,请移除 @csrf_exempt
# @csrf_exempt
@require_POST
def remark_proof_api(request, room_id, bills_slug):
try:
data = json.loads(request.body.decode("utf-8"))
submission_id = data.get("submissionId") # 使用.get()方法更安全
status = data.get("status")
if not submission_id or not status:
return JsonResponse({"success": False, "message": "Missing submissionId or status"}, status=400)
# 确保 submission_id 是整数
try:
sub = Submission.objects.get(id=int(submission_id))
except Submission.DoesNotExist:
return JsonResponse({"success": False, "message": "Submission not found"}, status=404)
except ValueError:
return JsonResponse({"success": False, "message": "Invalid submissionId format"}, status=400)
# 验证状态值是否有效(根据你的模型定义)
valid_statuses = ['P', 'A', 'R'] # 假设你的Submission模型status字段有这些选项
if status not in valid_statuses:
return JsonResponse({"success": False, "message": "Invalid status value"}, status=400)
sub.status = status
sub.save()
return JsonResponse({"success": True, "message": "Status updated successfully"})
except json.JSONDecodeError:
return JsonResponse({"success": False, "message": "Invalid JSON format"}, status=400)
except Exception as e:
# 捕获其他未知错误
return JsonResponse({"success": False, "message": f"An unexpected error occurred: {str(e)}"}, status=500)
后端视图改进点说明:
- 移除@csrf_exempt:一旦前端正确发送CSRF令牌,@csrf_exempt就不再需要,甚至应该移除,以恢复Django的内置安全防护。
- @require_POST:这是一个推荐的装饰器,用于明确指定视图只接受POST请求,提高代码可读性和安全性。
- 健壮性:增加了对json.loads的错误处理、data.get()的安全访问、对submission_id类型转换的错误处理,以及对Submission对象是否存在和状态值是否有效的检查,返回更具体的错误信息和HTTP状态码。
4. URL配置
URL配置保持不变,它正确地将前端请求路由到后端视图:
from django.urls import path
from . import views # 假设views.py在当前应用目录下
urlpatterns = [
# ... 其他URL模式
path('room//bills//status/', views.remark_proof_api, name='remark-proof'),
# ...
] 5. 总结与注意事项
通过以上步骤,你的Django预算网站的账单状态更新功能将能够正常工作并持久化。关键在于理解并正确实现Django的CSRF保护机制。
- CSRF令牌的重要性:永远不要低估CSRF保护的重要性。它能有效防止恶意网站利用用户已登录的会话执行未经授权的操作。
- @csrf_exempt的慎用:虽然它能快速解决问题,但应仅在特定且理解其风险的情况下使用,并在生产环境中尽量避免。
- 前端反馈:在实际应用中,AJAX请求成功或失败后,应向用户提供适当的视觉反馈(例如,一个短暂的“更新成功”消息或错误提示),以提升用户体验。
- 错误处理:无论是前端JavaScript还是后端Python,都应包含健壮的错误处理机制,以便在出现问题时能够捕获、记录并向用户提供有用的信息。
- 初始状态设置:确保页面加载时,select元素的默认选中项与数据库中的当前状态一致,避免用户混淆。
遵循这些最佳实践,你的Django应用将更加安全、稳定和用户友好。










