iT邦幫忙

2026 iThome 鐵人賽

DAY 14
0
Claude AI

從零開始 Claude Code:30 天打造 AI 任務管理 Web App系列 第 14 篇

[Day 14] 任務寫錯怎麼辦?加入編輯功能

  • 分享至 

  • xImage
  •  

Day 13 已經讓首頁開始讀取 MySQL 裡真正的任務。
目前已經可以:

新增任務
   ↓
存進 MySQL
   ↓
首頁讀取
   ↓
顯示真正資料

但如果任務內容打錯,還沒有辦法修改。
像是:

任務名稱打錯
說明想補內容
狀態要改
分類選錯
截止日期要調整

所以 Day 14 要補上 CRUD 裡的:

Update

也就是「編輯任務」。


今天的範圍

Day 14 只做:

首頁點「編輯」
   ↓
進入編輯頁
   ↓
帶入原本資料
   ↓
修改內容
   ↓
送出表單
   ↓
更新 MySQL
   ↓
 回首頁

今天先不做:

刪除任務
篩選
快速切換狀態
分類 CRUD
services/
routes/
Claude API

先讓 Claude Code 規劃

我先把需求交給 Claude Code:

目前狀態:

- Flask 已成功連上 MySQL
- Task / Category Model 已建立
- /tasks/new 可以新增任務
- 首頁已經從 MySQL 讀取真正的 tasks
- index.html 使用 Jinja2 顯示任務列表

今天只做「編輯既有任務」。

需求:

1. 首頁每張任務卡片增加「編輯」入口
2. 新增編輯任務頁面
3. 編輯頁面要先顯示原本資料
4. 可以修改:
   - 任務名稱
   - 說明
   - 狀態
   - 分類
   - 截止日期
5. GET 負責顯示原本資料
6. POST 負責更新 Task
7. 更新成功後 redirect 回首頁
8. 找不到 task_id 時回傳 404

今天不要:
- 刪除任務
- 篩選
- 快速切換狀態
- 分類 CRUD
- services/
- routes/
- Claude API

請先規劃會新增或修改哪些檔案,
不要直接修改程式。

最後規劃是:

新增:
templates/edit_task.html

修改:
app.py
templates/index.html
static/css/style.css

沒有動資料庫結構。


app.py:新增編輯任務 Route

Day 14 新增:

@app.route("/tasks/<int:task_id>/edit", methods=["GET", "POST"])
def edit_task(task_id):
    task = Task.query.get_or_404(task_id)

    if request.method == "POST":
        category = Category.query.filter_by(
            name=request.form.get("category")
        ).first()

        due_date_text = request.form.get("due_date", "")
        due_date = (
            datetime.strptime(due_date_text, "%Y-%m-%d").date()
            if due_date_text
            else None
        )

        task.title = request.form.get("title", "")
        task.description = request.form.get("description", "")
        task.status = request.form.get("status", "todo")
        task.category_id = category.id if category else None
        task.due_date = due_date

        db.session.commit()

        return redirect(url_for("index"))

    return render_template("edit_task.html", task=task)

這次的網址會像:

/tasks/3/edit

其中:

3

就是任務的 id。


task_id 是怎麼進來的?

Route:

@app.route("/tasks/<int:task_id>/edit")

裡面的:

<int:task_id>

代表 Flask 會把網址中的數字取出來。
例如:

/tasks/3/edit

會變成:

task_id = 3

接著:

task = Task.query.get_or_404(task_id)

就會去資料庫找:

id = 3

的那筆 Task。
如果不存在,就直接回:

404 Not Found

GET:先顯示原本資料

進入:

/tasks/3/edit

第一次是 GET。
這時候不是更新資料,而是把原本的 Task 傳到:

edit_task.html

例如原本:

任務名稱:測試1
狀態:進行中
分類:生活
截止日期:2026/10/01

編輯頁打開後就會先看到這些內容。


任務名稱先帶入

<input
    type="text"
    id="title"
    name="title"
    value="{{ task.title }}"
    required
>

所以使用者不用重新輸入整份內容,只需要改要修改的部分。


description 的 NULL 問題

原本一開始是:

<textarea>{{ task.description }}</textarea>

但如果資料庫裡:

description = NULL

頁面可能會顯示:

None

所以後來修成:

<textarea id="description" name="description" rows="4">{{ task.description or "" }}</textarea>

意思是:

有 description
→ 顯示原內容

沒有 description
→ 顯示空白

這樣比較合理。


狀態要自動選中原本的值

例如原本:

status = in_progress

編輯頁打開時,「進行中」要直接被選中。

<option value="todo"
    {% if task.status == 'todo' %}selected{% endif %}>
    待處理
</option>

<option value="in_progress"
    {% if task.status == 'in_progress' %}selected{% endif %}>
    進行中
</option>

<option value="done"
    {% if task.status == 'done' %}selected{% endif %}>
    已完成
</option>

分類的「未分類」問題

一開始編輯頁只有:

學習
專案
生活

但資料庫允許:

category_id = NULL

如果一筆任務原本沒有分類,瀏覽器會自動選第一個:

學習

這樣即使使用者完全沒改分類,送出後也可能被意外改成「學習」。
所以後來補上:

<option value=""
    {% if not task.category %}selected{% endif %}>
    未分類
</option>

完整概念:

<select id="category" name="category">
    <option value="" {% if not task.category %}selected{% endif %}>
        未分類
    </option>

    <option value="學習"
        {% if task.category and task.category.name == '學習' %}selected{% endif %}>
        學習
    </option>

    <option value="專案"
        {% if task.category and task.category.name == '專案' %}selected{% endif %}>
        專案
    </option>

    <option value="生活"
        {% if task.category and task.category.name == '生活' %}selected{% endif %}>
        生活
    </option>
</select>

如果送出:

category = ""

這段:

Category.query.filter_by(
    name=request.form.get("category")
).first()

查不到分類,就會得到:

None

最後:

task.category_id = category.id if category else None

就會正確存成:

NULL

日期也要先帶入

HTML:

<input type="date">

需要:

YYYY-MM-DD

所以使用:

<input
    type="date"
    id="due_date"
    name="due_date"
    value="{{ task.due_date.strftime('%Y-%m-%d') if task.due_date else '' }}"
>

如果原本沒有日期,就顯示空白。


POST:修改原本那筆 Task

這次跟 Day 12 最大的不同,是沒有重新:

Task(...)

Day 12 是:

建立新的 Task
→ INSERT

Day 14 則是直接修改原本查到的:

task

例如:

task.title = request.form.get("title", "")
task.description = request.form.get("description", "")
task.status = request.form.get("status", "todo")
task.category_id = category.id if category else None
task.due_date = due_date

最後:

db.session.commit()

SQLAlchemy 就會把同一筆資料更新回 MySQL。


為什麼不用 db.session.add()?

因為:

task = Task.query.get_or_404(task_id)

查出來的 task 本來就是 SQLAlchemy 正在管理的資料。
所以只要修改欄位後:

db.session.commit()

就可以。
不需要:

db.session.add(task)

首頁增加「編輯」

Day 13 的首頁已經是 Jinja2 迴圈。
Day 14 在每張任務卡片加:

<a
    class="edit-link"
    href="{{ url_for('edit_task', task_id=task.id) }}">
    編輯
</a>

https://ithelp.ithome.com.tw/upload/images/20260928/20183857sQnUiYSoLY.png
例如:

task.id = 3

連結就會前往:

/tasks/3/edit

編輯按鈕的 CSS

這次只加一個很小的樣式:

.edit-link {
    display: inline-block;
    margin-top: 6px;
    color: var(--text-secondary);
    font-size: 0.85rem;
    font-weight: 600;
    text-decoration: none;
}

.edit-link:hover {
    color: var(--primary);
    text-decoration: underline;
}

沒有重新改首頁版面,只是讓「編輯」比較容易辨識。


實際測試

我直接進入:

127.0.0.1:5000/tasks/3/edit

編輯 id = 3 的任務。
https://ithelp.ithome.com.tw/upload/images/20260928/20183857bYObm8Pi4A.png
修改任務內容後按:

儲存變更

Flask 收到 POST,更新原本那筆 Task。
接著:

return redirect(url_for("index"))

回到首頁。


MySQL 也真的有更新

回到 MySQL Workbench 查看:

SELECT * FROM tasks;

可以看到原本:

id = 3

的那一筆內容已經跟著修改。
重點是:

id 沒有變

代表這次不是新增另一筆 Task,而是真的更新原本資料。

原本:
id = 3

修改後:
id = 3

只是其他欄位被更新。
https://ithelp.ithome.com.tw/upload/images/20260928/20183857086wf88Wy0.png


Day 14 做完後

目前 CRUD 已經完成三個:

Create
→ 新增任務

Read
→ 首頁顯示任務

Update
→ 編輯任務

現在任務如果:

標題寫錯
說明要補
狀態改變
分類改變
截止日期調整

都不需要重新新增,可以直接修改原本那筆資料。
剩下最後一個基本 CRUD:

Delete

所以下一步就是把刪除功能補上。


上一篇
[Day 13] 不再寫死任務:從 MySQL 顯示資料
下一篇
[Day 15] 把基本 CRUD 補完整:加入刪除任務
系列文
從零開始 Claude Code:30 天打造 AI 任務管理 Web App 共 16 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言