在 Flask 构建的 Kanban 看板中,想要通过拖拽任务卡片来实时更新数据库状态,听起来简单,但实际落地时却有不少陷阱。本文将深入剖析核心原理,并逐步演示如何利用 HTML5 Drag and Drop API 与 Flask 后端配合,确保拖拽功能正确、稳定地运行。
首先需要明确:在 Flask Kanban 应用中,仅凭 Jinja2 模板语法无法实现拖拽后动态调用 Python 后端逻辑。原因在于,类似 {{ dropped(...) }} 的写法属于服务端渲染指令——它在页面生成时立即执行,传入的是模板变量而非用户拖拽时的实时数据。换句话说,拖拽动作尚未发生,函数就已经提前执行完了。
那么正确的解决方案是什么?核心思路是让前端通过 Fetch API 主动发起异步请求,后端提供专门的 POST 接口处理状态变更,最后通过页面重载同步视图。这套组合拳才是可靠且可维护的实现方案。
✅ 正确实现步骤
1. 前端:用 Fetch 把拖拽数据送过去
常见的错误做法是将内联模板调用硬塞进事件处理中。正确的方式是切换为纯客户端的 JavaScript 请求,示例如下:
这里需要特别留意:ondrop="drop(event, this)" 中的 this 显式传递了当前 元素,确保 el.id 能准确获取目标列的 ID。同时,所有事件处理器都必须调用 ev.stopPropagation() 以防止事件冒泡干扰——父子元素间的冲突往往由此产生。
2. 后端:给 POST 请求单独开条路
在 Kanban 路由中,需要扩展对 JSON 请求的支持。关键在于确保拖拽更新逻辑与传统的表单提交逻辑互不冲突:
from flask import request, jsonify, redirect, url_for
from werkzeug.exceptions import NotFound
@main.route("//kanban", methods=["GET", "POST"])
def kanban(project_id):
project = Project.query.get(project_id)
if not project:
raise NotFound()
try:
session = sessions_by_project[project_id]
except KeyError:
raise NotFound()
# 按 status 对任务进行分组
tasks_by_status = defaultdict(list)
for depth, task in walk_list(session.query(Task).all()):
tasks_by_status[task.status.value].append((depth, task))
# 处理拖拽状态更新:检查请求是否为 JSON
if request.method == "POST" and request.is_json:
data = request.get_json()
if "task_id" in data and "target_state" in data:
task_id = int(data["task_id"])
target_state = data["target_state"]
task = session.query(Task).get(task_id)
if not task:
raise NotFound(f"Task {task_id} not found")
task.change_status(str2status(target_state))
session.commit()
return jsonify({"success": True, "message": "Status updated"})
# 处理传统表单提交(如点击任务按钮)
if request.method == "POST" and "task" in request.form:
return redirect(url_for("main.task", project_id=project_id, id=int(request.form["task"])))
return render_template("kanban.html",
tasks_by_status=tasks_by_status,
project=project)
3. 模板:把 HTML 结构和事件绑定安排明白
这一步的核心是让 容器正确声明事件监听器,并谨慎处理子元素带来的干扰:
To-Do
{% for (depth, pending) in tasks_by_status[1] %}- {% endfor %}
{{ pending.description }}
