在 Web 開發中,SQL Injection(SQL 注入) 是最常見且危害極大的資安漏洞之一。它可以讓攻擊者透過竄改 SQL 查詢語句來未授權地讀取、修改、甚至刪除資料庫中的資料。本文將帶你認識 SQL Injection 是什麼、實際攻擊範例,以及開發者該如何有效預防這種攻擊。 SQL Injection 是什麼? SQL Injection 是一種將惡意 SQL 語句插入應用程式輸入欄位中的攻擊方式,目的是操控原本預期的 SQL 查詢邏輯。 舉例來說,假設以下是一段不安全的登入邏輯: # ❌ 極度危險的程式碼 username = input("請輸入帳號:") password = input("請輸入密碼:") query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'" cursor.execute(query) 當攻擊者輸入以下資料時: username: ' OR 1=1 -- password: 任意內容 查詢語句將變成: SELECT * FROM users WHERE username = '' OR 1=1 --' AND password = '任意內容' 由於 -- 是 SQL 的註解符號,後面的內容會被忽略,而 OR 1=1 永遠為真,攻擊者即可繞過密碼驗證成功登入! SQL Injection 的潛在危害 繞過身份驗證:如上例,攻擊者可直接登入任意帳戶。 資料洩漏:SELECT 語句被竄改,可讀取整個資料表。 資料破壞:透過 INSERT/UPDATE/DELETE 修改或刪除資料。 系統控制:某些情況下可執行資料庫指令進而控制伺服器。 如何預防 SQL Injection? 防範 SQL Injection 是開發者的責任,以下是幾種有效的預防方式: 1. 使用參數化查詢(Prepared Statements) 最根本的解法是使用參數化查詢,將使用者輸入與 SQL 語句分離。 以 Python(Django ORM)為例: # ✅ 安全寫法(ORM 自動處理 SQL 注入風險) User.objects.filter(username=username, password=password).exists() 以 Python 搭配 psycopg2 原生查詢為例: # ✅ 安全寫法(使用參數化) cursor.execute("SELECT * FROM users WHERE username = %s AND password = %s", (username, password)) 2. 使用 ORM(Object-Relational Mapping) ORM 框架(如 Django ORM、SQLAlchemy)天生會處理 SQL Injection 的風險,因為它們自動將參數轉義為安全格式。 3. 輸入驗證與清洗 雖不能取代參數化查詢,但針對表單輸入進行格式驗證(如 email 格式、字數限制)可降低惡意資料混入。 4. 最小權限原則(Least Privilege) 資料庫帳號應只授予應用程式所需的最低權限。避免使用擁有 DROP、GRANT 等高權限的帳號。 5. 使用 Web 應用防火牆(WAF) WAF 可自動攔截常見的 SQL Injection 攻擊模式,作為第二道防線。 如何檢測是否有 SQL Injection? 使用自動化工具(如 sqlmap)對 API 或表單送出測試 payload。 開發階段可使用 lint 工具檢查潛在的不安全 SQL 組合。 確保有完整的測試覆蓋對外輸入的功能。 面試回答 SQL Injection 是一種資安攻擊手法,攻擊者在表單輸入或 URL 中注入惡意 SQL 語句,藉此操控原本的資料庫查詢邏輯,可能造成未授權的資料讀取、修改,甚至整個資料庫被破壞。 舉例來說,若在登入功能中直接把使用者輸入組合成 SQL 字串而未加防護,攻擊者就能透過像 ' OR 1=1 -- 的輸入繞過驗證。 預防方式主要有三個: 使用參數化查詢(Prepared Statements):將輸入值與 SQL 語句分離,是最根本的解法。 使用 ORM 框架:像是 Django ORM、SQLAlchemy 會自動處理參數轉義。 避免直接拼接 SQL,並搭配輸入驗證與資料庫權限控管,從多層面加強安全性。 在我自己的專案中,例如使用 Django 框架時,會透過 ORM 查詢,確保使用者輸入不會直接進入 SQL 字串,並依角色限制資料庫操作權限,確保資安基本面不被忽略。 總結 安全措施 是否必要 備註 參數化查詢 ✅ 必須實作 最根本防禦方式 ORM 框架 ✅ 強烈建議 自動處理轉意 輸入驗證 ⚠️ 輔助防禦 不能單獨依賴 資料庫權限控制 ✅ 應落實 防止資料被大規模破壞 Web 防火牆 ⚠️ 加強防禦 作為額外保護 SQL Injection 是老生常談但仍極常見的安全漏洞,唯有落實安全開發習慣,才能避免這類攻擊發生。 延伸閱讀 OWASP: SQL Injection Django Security Docs

前端開發常常會遇到「跨來源請求被阻擋」的錯誤訊息,比如: Access to fetch at 'https://api.example.com/data' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present... 這就是著名的 CORS 問題。本篇文章將說明 CORS 是什麼、為什麼會發生,以及常見的解法。 CORS 是什麼? CORS(Cross-Origin Resource Sharing,跨來源資源共享)是一種瀏覽器的安全機制,用來限制「不同網域」之間的請求。簡單來說,它防止了一個網站的 JavaScript 去隨意讀取另一個網站的資料。 什麼是「跨來源」? 只要以下任何一個不同,就會被視為「不同來源」: 區塊 範例 協定 http://example.com vs https://example.com 網域 api.example.com vs www.example.com Port example.com:3000 vs example.com:8000 為什麼會出現 CORS 錯誤? 瀏覽器會阻擋從網頁腳本對其他來源發起的請求(例如使用 fetch、axios)。除非 伺服器端主動聲明允許該請求的來源,否則請求會被攔截。 範例情境 假設前端使用 React (http://localhost:3000),想從後端 API (http://localhost:8000) 取得資料: fetch('http://localhost:8000/api/data') 這是跨來源請求,除非後端有設定 CORS header,否則會被瀏覽器擋下來。 解決 CORS 問題的方法 1. 修改後端設定 CORS 問題應由「伺服器端」解決。需要在伺服器回應中加上正確的 Access-Control-Allow-Origin header。 範例:使用 Express const express = require('express'); const cors = require('cors'); const app = express(); app.use(cors()); // 允許所有來源 // 或只允許特定來源 // app.use(cors({ origin: 'http://localhost:3000' })); app.get('/api/data', (req, res) => { res.json({ message: 'Hello from server!' }); }); app.listen(8000); 範例:使用 Django + django-cors-headers pip install django-cors-headers # settings.py INSTALLED_APPS = [ ... 'corsheaders', ] MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', ... ] # 允許所有網域 CORS_ALLOW_ALL_ORIGINS = True # 或只允許特定網域 # CORS_ALLOWED_ORIGINS = [ # "http://localhost:3000", # ] 2. 使用 Proxy 避開 CORS 開發時可使用前端代理轉發請求,例如 React: // vite.config.js or package.json proxy: { '/api': { target: 'http://localhost:8000', changeOrigin: true, }, } 這樣 fetch('/api/data') 實際會被代理成後端的請求,不會被視為跨來源。 3. 後端設定 Access-Control Headers 除了 Access-Control-Allow-Origin,有時也需要加上這些 header: Access-Control-Allow-Origin: http://localhost:3000 Access-Control-Allow-Methods: GET, POST, PUT, DELETE Access-Control-Allow-Headers: Content-Type, Authorization 這些設定也必須在「預檢請求(Preflight Request)」中正確處理。 面試題目 「請說明什麼是 CORS(Cross-Origin Resource Sharing),以及如果遇到 CORS 問題該如何解決?」 回答範例: CORS(Cross-Origin Resource Sharing) 是一種瀏覽器的安全機制,用來限制前端從不同來源(origin)去請求資源,避免惡意網站竊取資料。當前端的 JavaScript 嘗試向不同網域、協定或 port 發送請求時,如果伺服器沒有正確設定 CORS header,瀏覽器會阻擋這個請求。 常見錯誤: 例如使用 fetch 或 axios 向不同來源的 API 發送請求時,會看到類似錯誤: Access to fetch at 'http://api.example.com' from origin 'http://localhost:3000' has been blocked by CORS policy 解決方式: 從伺服器端設定 CORS header(這是正確的做法): 設定 Access-Control-Allow-Origin,明確允許某個前端網域。 可搭配套件,如: Node.js 可用 cors middleware Django 可用 django-cors-headers 本地開發時使用 proxy 轉發請求,避開跨域問題(開發用): 例如 React 可以設定 vite.config.js 或 webpack devServer proxy 總結 問題 解法 跨來源請求被阻擋 在後端加上 CORS Header 特定網域允許 設定 Access-Control-Allow-Origin 為指定來源 多來源需求 使用動態設定或 whitelist 本地開發測試 使用 Proxy 或開啟允許全部來源 延伸閱讀 MDN - HTTP access control (CORS) CORS 官方規範 (W3C) django-cors-headers GitHub 如果曾經在開發中遇過 CORS 錯誤,不妨回頭看看是不是跨來源造成的限制,並從伺服器端著手解決吧!

使用 Supabase 替代本機安裝的 PostgreSQL 並連接至 Django 在開發應用程式時,許多開發者會選擇在本機安裝 PostgreSQL 資料庫。然而,隨著雲端技術的普及,使用遠端資料庫如 Supabase 提供的 PostgreSQL 不僅可以減少本機配置的麻煩,也能讓資料庫更容易在團隊間共享。 以下是完整的指南,教導如何取代本機安裝的 PostgreSQL,並利用 Django 連接至 Supabase 資料庫。 1. 在 Supabase 官網建立資料庫 前往 Supabase 官網 並註冊帳號。 登入後,點擊 “New Project” 按鈕建立新的專案。 填寫相關資訊: Project name: 為專案命名。 Database password: 設定資料庫的密碼(請妥善保存)。 Region: 選擇靠近的伺服器區域以減少延遲。 點擊 “Create New Project”,Supabase 將自動配置資料庫,這過程大約需 2-3 分鐘。 完成後,可以在專案的 Settings 頁面下找到資料庫的連線資訊,包括 Host、Port、Database name 和 User credentials。 2. 安裝 Django 和必要的套件 如果尚未安裝 Django,請在專案環境中安裝 Django 以及支援 PostgreSQL 的套件: uv add "psycopg[binary]" 3. 配置 Django 專案連接 Supabase 在 Django 專案中,修改 settings.py 文件,將資料庫設定更改為 Supabase 提供的連線資訊: import os from pathlib import Path import environ # env init env = environ.Env() # 預設會讀取專案根目錄的 `.env` environ.Env.read_env(os.path.join(BASE_DIR, '.env')) DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': env('DB_NAME'), 'USER': env('DB_USER'), 'PASSWORD': env('DB_PASSWORD'), 'HOST': env('DB_HOST'), 'PORT': env('DB_PORT'), } } 在專案根目錄建立一個 .env 檔案,並填入以下設定(請將值替換為 Supabase 提供的實際資訊): DB_NAME=your_database_name DB_USER=postgres DB_PASSWORD=your_database_password DB_HOST=db.xxxxxxxxxxxx.supabase.co DB_PORT=5432 請注意: 確保 .env 檔案已加入到 .gitignore 中,避免敏感資訊被上傳到版本控制系統。 資料庫使用者名稱通常是 postgres,這是 Supabase 的預設設定。 資料庫密碼是建立專案時設定的密碼。 主機名稱和連接埠可以在 Supabase 專案的設定頁面中找到。 4. 測試連線 執行以下指令以測試資料庫是否正確連接: uv run python manage.py migrate 如果配置無誤,會看到 Django 成功執行資料庫遷移。 5. 小結 至此,完成將本機 PostgreSQL 替換為 Supabase 遠端資料庫,並成功使用 Django 進行連接。不僅減少了本機安裝的負擔,還能利用 Supabase 提供的強大功能,如即時同步和 API 支援,提升開發效率。

本文詳細解釋了 Git 本機與遠端操作的常見指令及其應用場景,涵蓋 git fetch、git pull、git push、Pull Request (PR) 的管理,以及如何保持 Git 圖表清晰的最佳實踐。 git fetch 與分支同步 git fetch 是用來從遠端倉庫抓取最新的更新,但不會自動合併到本地分支。它的作用是讓本地倉庫知道遠端的最新狀態。 使用範例: git fetch 效果:抓取遠端的更新,並建立對應的遠端分支(如 origin/master)。 應用場景:當你想檢查遠端的最新變更,但不希望立即影響本地分支時使用。 本機與遠端同步的方式 要讓本機分支跟上遠端的進度,可以使用以下幾種方式: 1. git merge 將遠端的變更合併到本地分支,保留本地和遠端的提交歷史。 git pull = git fetch + git merge 效果:合併後會保留兩條提交記錄,適合需要完整歷史的場景。 缺點:可能導致 Git 圖表變得複雜。 2. git rebase 將遠端的變更應用到本地分支的基礎上,重新整理提交歷史。 git pull --rebase = git fetch + git rebase 效果:提交歷史會變得線性,讓 Git 圖表更清晰。 注意:在使用 rebase 時,需確保沒有其他人依賴你的分支,避免衝突。 3. git reset 重置本地分支到遠端分支的狀態,適合需要完全同步的情況。 git push 的進階用法 git push 是將本地分支的變更推送到遠端分支。以下是一些進階用法: 1. 推送到相同名稱的分支 git push origin main 效果:將本地的 main 分支推送到遠端的 main 分支。 2. 推送到不同名稱的分支 git push origin main:cat 效果:將本地的 main 分支推送到遠端的 cat 分支。 3. 刪除遠端分支 git push origin :cat 效果:刪除遠端的 cat 分支,相當於執行 null:cat。 Pull Request (PR) 的管理 Pull Request 是團隊協作中不可或缺的一部分,用於代碼審查和合併。 PR 的最佳實踐 設定審核條件: 可以設定 6 人團隊中至少 5 人接受後才能合併。 當條件滿足時,合併按鈕會亮起,任何人都可以按下合併。 PR 的重要性: 即使功能不是你開發的,參與 PR 的 Code Review 也能幫助你了解功能的運作方式。 在面試中,可以提到你參與過的 Code Review,展示對系統的理解。 避免 PR 堆積: PR 長時間未處理會導致難以合併,建議每週安排一次 PR 日,集中討論和處理 PR。 保持 Git 圖表清晰的建議 優先使用 git rebase: 在執行 git fetch 後,盡量使用 rebase 而非 merge,避免提交歷史過於複雜。 避免強制推送到共享分支: git push origin main --force 應謹慎使用,尤其是在共享分支上。 如果需要整理自己的分支,可以在個人分支上使用 --force。 常見操作範例 建立新分支並推送 git branch issues/705 git push origin issues/705 效果:建立一個名為 issues/705 的分支,並推送到遠端。 發起 PR 並進行 Code Review 在遠端倉庫中發起 PR,請求其他成員進行代碼審查。 通過審查後,合併到主分支。 面試中的 Git 技巧 在面試中,展示你對 Git 的理解和實踐經驗非常重要。例如: 參與 Code Review: 即使某功能不是你開發的,可以提到在 PR 中進行過代碼審查,並了解該功能的實現細節。 版本控制的實踐: 提到如何使用 rebase 保持提交歷史清晰,或如何處理衝突。 這篇文章介紹了 Git 本機與遠端操作的常見場景和實踐,適合用於團隊協作和個人開發的日常工作中。

這篇文章描述了任務管理與執行流程,涵蓋點數評估、任務拆解、立會流程、PR 管理以及團隊協作的實踐,適合用於團隊內部的工作指導與知識共享。 點數評估與難易程度 在專案管理中,為了合理分配任務並估算完成時間,點數評估是一個重要的工具。點數的大小通常與任務的難易程度和所需時間成正比。 點數對應時間: 半天:1 點 一天:2 點 兩天半:5 點 這樣的點數設計可以幫助團隊快速了解任務的規模,並根據每位成員的能力合理分配工作。小任務(1 點)適合快速完成,而大任務(5 點)則需要進一步拆解或由多位成員協作完成。 任務拆解與點數決定 點數如何決定? 點數的評估並不是由單一個人決定,而是由所有組員共同討論完成的。這樣可以避免因個人偏見導致的評估不準確,並且能夠考慮到每位組員的能力值和經驗。 任務拆解的原則 將大任務拆解為小任務: 確保每個任務的點數不超過 5 點,這樣可以降低任務的複雜度,讓進度更容易掌控。 優先處理高優先級任務: 如果某些任務會阻塞其他工作,應該優先完成,確保整體進度不受影響。 任務拆解的目的是讓每個人都能清楚知道自己該做什麼,並且能夠在合理的時間內完成。 路障型任務處理 在專案中,總會遇到一些「路障型任務」,這些任務可能因為技術難題或外部依賴而無法順利進行。為了避免這些任務拖延整體進度,我們採取以下策略: 誰先舉手就做: 當遇到路障型任務時,鼓勵組員主動承擔,但需要設定明確的停損期。 設定停損期: 如果在停損期內無法解決問題,應立即向組員求助,或將任務轉交給其他人處理。 這樣的處理方式可以有效避免任務長時間卡住,確保專案能夠持續推進。 立會流程 每日立會是敏捷開發中的一個重要環節,目的是讓團隊成員同步進度,及時解決問題。 每日立會內容 昨天做了什麼? 回顧昨天的工作,確認是否完成預定的任務。 遇到什麼困難? 如果有卡住的問題,應該在立會中提出,尋求團隊的幫助。 預計今天要做什麼? 明確當天的工作計劃,確保每個人都有清晰的目標。 確認票的狀態 如果有票可能卡住,應該立即求救,或撿其他票來做,避免進度延誤。 目標是確保所有票都能按時完成,避免影響整體進度。 任務優先級與進度追蹤 優先級設定 在專案中,並非所有任務的優先級都是相同的。我們可以根據任務的重要性和緊急程度進行分類: 高優先級:影響整體進度或其他任務的工作,應該優先完成。 中優先級:需要在一定時間內完成,但不會阻塞其他任務。 低優先級:可以延後處理的任務。 進度追蹤工具 為了更好地追蹤任務進度,可以使用看板工具(如 Trello、Jira)。將任務狀態分為以下幾類: 待處理:尚未開始的任務。 進行中:正在處理的任務。 已完成:已完成的任務。 阻塞:因外部原因無法繼續的任務。 這樣的分類可以幫助團隊快速了解專案的整體進度。 任務完成的驗收標準 驗收標準 任務是否達到預期的功能或目標。 是否通過測試(單元測試、整合測試等)。 是否符合代碼規範(如代碼審查通過)。 驗收流程 開發者完成任務後,提交 Pull Request(PR)。 組員進行 Code Review,確保代碼質量。 通過測試後,合併到主分支。 驗收標準的制定可以確保每個任務都能以高質量完成,並減少後續的返工。 Pull Request(PR)管理 PR 的重要性 Pull Request 是團隊協作中不可或缺的一部分。它不僅可以確保代碼質量,還能促進團隊成員之間的知識共享。 PR 的最佳實踐 小而頻繁的 PR: 避免一次提交過多代碼,讓審查更高效。 詳細描述修改內容: 在 PR 中清楚說明修改的目的和內容,方便其他人理解。 定期清理過期 PR: 避免堆積過多未處理的 PR,影響進度。 PR 的合併條件 可以設定需要一定人數的審核通過(如 5 人團隊中至少 3 人同意)。 確保所有測試通過後才能合併。 團隊協作的注意事項 溝通與協作 定期同步進度,確保所有人了解當前狀態。 鼓勵組員主動提出問題或建議,促進團隊合作。 知識共享 透過 Code Review 和立會,促進團隊對系統的共同理解。 使用文檔記錄重要的技術決策和流程,方便後續查閱。 常見問題與解決方案 票卡住了怎麼辦? 立即向組員求助,尋求解決方案。 如果短時間內無法解決,轉交給其他人處理,或重新評估任務。 如何避免票交不出來? 設定合理的停損期。 定期檢查任務進度,及早發現問題。 如何處理路障型任務? 設定明確的停損期,避免浪費過多時間。 將問題記錄下來,作為後續優化的參考。

什麼是 N+1 問題? N+1 問題 是一種常見的效能問題,通常發生在處理資料庫查詢時。當我們需要查詢一個主物件及其相關聯的子物件時,程式會執行 1 次查詢來獲取主物件,然後為每個主物件執行 N 次查詢來獲取其相關聯的子物件。這樣總共會執行 1 + N 次查詢,導致效能低下。 什麼時候會發生 N+1 問題? 關聯查詢: 當模型之間有外鍵(ForeignKey)、一對一(OneToOneField)或多對多(ManyToManyField)關聯時,容易發生 N+1 問題。 模板中迴圈訪問關聯物件: 在模板中使用迴圈訪問關聯物件時,可能會導致多次查詢。 範例: {% for comment in comments %} {{ comment.author.name }} {% endfor %} 範例:留言與作者 (多對一) 假設我們有一個留言系統,每條留言都有一個作者。當我們需要顯示所有留言及其作者時,可能會發生 N+1 問題。 查詢留言:執行 1 次查詢來獲取所有留言。 SELECT * FROM comments; 查詢作者:對於每條留言,執行 1 次查詢來獲取該留言的作者。 SELECT * FROM users WHERE id = <author_id>; 如果有 100 條留言,總共會執行 1 + 100 = 101 次查詢。 N+1 問題的影響 效能低下:大量的查詢會增加資料庫的負載,特別是在高流量的應用中。 延遲增加:每次查詢都需要與資料庫交互,導致頁面加載時間變長。 解決方法 1. 使用 select_related select_related 用於解決 一對一 或 多對一 關聯的 N+1 問題。它會在一次查詢中使用 SQL 的 JOIN,將相關聯的資料一起查出來。 範例 (多對一): # N+1 問題 comments = Comment.objects.all() for comment in comments: print(comment.author.name) # 每次訪問 author 都會執行一次查詢 # 解決 N+1 問題 comments = Comment.objects.select_related('author') for comment in comments: print(comment.author.name) # 不會再執行額外的查詢 使用 select_related,Django 在查詢留言時,會同時透過 SQL 的 JOIN 將留言和作者的資料一起查出來。 SQL 查詢: SELECT comments.*, users.* FROM comments JOIN users ON comments.author_id = users.id; SQL 查詢對比 方法 查詢次數 SQL 查詢範例 N+1 問題 1 + N 1. SELECT * FROM comments; 2. SELECT * FROM users WHERE id = <id>; 使用 select_related 1 SELECT comments.*, users.* FROM comments JOIN users ON comments.author_id = users.id; 2. 使用 prefetch_related prefetch_related 用於解決 一對多 或 多對多 關聯的 N+1 問題。它會執行兩次查詢,並在 Python 中將結果關聯起來。 範例 (一對多): # N+1 問題 authors = Author.objects.all() for author in authors: print(author.books.all()) # 每次訪問 books 都會執行一次查詢 # 解決 N+1 問題 authors = Author.objects.prefetch_related('books') for author in authors: print(author.books.all()) # 不會再執行額外的查詢 prefetch_related 會執行兩次查詢: 查詢所有作者。 查詢所有書籍,並在 Python 中將書籍與對應的作者關聯起來。 SQL 查詢: SELECT * FROM authors; SELECT * FROM books WHERE author_id IN (<author_ids>); SQL 查詢對比 方法 查詢次數 SQL 查詢範例 N+1 問題 1 + N 1. SELECT * FROM authors; 2. SELECT * FROM books WHERE author_id = <author_id>; prefetch_related 2 1. SELECT * FROM authors; 2. SELECT * FROM books WHERE author_id IN (<author_ids>); N+1 問題的查詢(使用 =) SELECT * FROM books WHERE author_id = 1; SELECT * FROM books WHERE author_id = 2; SELECT * FROM books WHERE author_id = 3; 每次查詢只匹配一個 author_id,需要執行多次查詢(N 次)。 prefetch_related 的查詢(使用 IN) SELECT * FROM books WHERE author_id IN (1, 2, 3); 一次查詢即可匹配多個 author_id,大幅減少查詢次數。 在解決 N+1 問題時,prefetch_related 使用 IN,可以一次查詢多個關聯物件,顯著提升效能 select_related 與 prefetch_related 的比較 select_related 和 prefetch_related 是 Django 中用來解決 N+1 問題的兩個方法,但它們適用於不同的場景,且工作方式也不同 特性 select_related prefetch_related 適用關係 多對一、一對一 一對多、多對多 查詢次數 1 次查詢 2 次查詢 工作方式 使用 SQL 的 JOIN 分開查詢,Python 中關聯結果 效能 高效,但不適合關聯資料過多的情況 稍低,但適合處理大量關聯資料 使用場景 外鍵(ForeignKey)、一對一關聯 反向外鍵(related_name)、多對多關聯 根據資料關聯的類型選擇正確的方法,可以有效提升 Django 應用的效能 在某些情況下,兩者都可以使用,但選擇哪一個取決於以下因素: 資料量大小: 如果關聯資料量較小,使用 select_related 更高效,因為只需要執行一次查詢。 如果關聯資料量較大,使用 prefetch_related 更合適,因為它避免了 JOIN 導致的查詢結果過於龐大。 關聯類型: 多對一或一對一:優先使用 select_related。 一對多或多對多:優先使用 prefetch_related。 查詢的靈活性: 如果需要對關聯表進行額外的過濾或排序,使用 prefetch_related 更靈活,因為它允許自訂查詢。 select_related 無法對關聯表進行過濾或排序,因為它直接使用 SQL 的 JOIN 當關聯表的資料量較大,且需要過濾或排序時,prefetch_related 是更好的選擇 3. 使用 Django Debug Toolbar Django Debug Toolbar 是一個強大的工具,可以幫助開發者檢測 N+1 問題,並查看每個請求執行的 SQL 查詢。 安裝與設定: 安裝: pip install django-debug-toolbar 在 settings.py 中添加: INSTALLED_APPS += ['debug_toolbar'] MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware'] INTERNAL_IPS = [ # ... "127.0.0.1", # ... ] 在 urls.py 中添加: from django.conf import settings from django.conf.urls import include from django.urls import path if settings.DEBUG: import debug_toolbar urlpatterns = [ path('__debug__/', include(debug_toolbar.urls)), ] + urlpatterns 啟動伺服器後,檢查每個請求的 SQL 查詢次數,找出 N+1 問題。 總結 N+1 問題 是由於多次查詢資料庫導致的效能問題,特別是在處理關聯資料時容易發生。 解決方法: 使用 select_related 解決一對一或多對一關聯的問題。 使用 prefetch_related 解決一對多或多對多關聯的問題。 使用 Django Debug Toolbar 檢測 SQL 查詢次數,找出潛在的 N+1 問題。 最佳實踐: 在開發過程中,定期檢查 SQL 查詢,確保效能最佳化。 對於大型應用,考慮使用快取(如 Redis)進一步減少資料庫查詢次數。 透過這些方法,可以有效避免 N+1 問題,提升 Django 應用的效能與穩定性。

什麼是 Django Messages? Django 提供了一個內建的訊息框架(django.contrib.messages),用於在請求之間傳遞提示訊息,這些訊息通常用於通知使用者某些操作的結果,例如成功、錯誤或警告等。這類訊息被稱為 「快閃訊息」(Flash Messages),因為它們只會在下一次請求中顯示,然後自動消失。 使用場景 Django Messages 適合用於以下場景: 表單提交成功或失敗的提示。 操作完成後的通知(例如:資料已儲存、刪除成功)。 錯誤或警告訊息的顯示。 快閃訊息的類型 Django 提供了以下內建的訊息類型: messages.debug: 用於調試訊息。 messages.info: 一般資訊提示。 messages.success: 成功訊息。 messages.warning: 警告訊息。 messages.error: 錯誤訊息。 如何使用 Django Messages? 1. 啟用 Messages 確保 django.contrib.messages 已包含在 INSTALLED_APPS 中,並且模板中已載入 messages 中間件。 settings.py: INSTALLED_APPS = [ ... 'django.contrib.messages', ... ] MIDDLEWARE = [ ... 'django.contrib.messages.middleware.MessageMiddleware', ... ] 2. 在視圖中添加訊息 使用 messages 模組在視圖中添加訊息。 範例: from django.contrib import messages from django.shortcuts import redirect def my_view(request): # 添加成功訊息 messages.success(request, "操作成功!") # 添加警告訊息 messages.warning(request, "這是警告訊息!") return redirect("home") 3. 在模板中顯示訊息 在模板中使用 messages 模板標籤來顯示訊息。 範例: {% if messages %} {% for message in messages %} {{ message }} {% endfor %} {% endif %} message.tags: 自動添加的 CSS 樣式類型(如 success、warning)。 message: 訊息的內容。 4. 自訂樣式 可以根據訊息類型自訂樣式,提升用戶體驗。 範例(CSS): .messages { list-style: none; padding: 0; } .messages .success { color: green; } .messages .warning { color: orange; } .messages .error { color: red; } 完整範例 以下是使用 DaisyUI 和 Alpine.js 實現的完整範例,包含可點擊 x 按鈕關閉訊息的功能,並根據訊息類型(如 success、warning、error)自動套用樣式: {% if messages %} {% for message in messages %} {{ message }} x {% endfor %} {% endif %} 功能說明 Alpine.js 的 x-data 和 x-if: 使用 x-data="{ show: true }" 初始化訊息的顯示狀態。 使用 x-if="show" 控制訊息是否顯示,點擊 x 按鈕後將 show 設為 false,隱藏訊息。 DaisyUI 的 alert 樣式: 使用 alert 類別來套用 DaisyUI 的樣式。 根據 message.tags 動態添加樣式類別(如 alert-success、alert-warning、alert-error 等)。 s 關閉按鈕: 使用 DaisyUI 的按鈕樣式 btn btn-xs btn-circle btn-outline。 點擊按鈕時,透過 Alpine.js 的 @click 事件隱藏訊息。 Alpine.js 和 DaisyUI 的引入 Alpine.js 的引入: 確保在模板中已經引入 Alpine.js CDN: <script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script> DaisyUI 的安裝: DaisyUI 是基於 Tailwind CSS 的元件庫,確保已正確安裝並配置 Tailwind 和 DaisyUI。 這樣的設計不僅美觀,還能提供良好的用戶體驗,適合用於各種 Django 項目中需要提示訊息的場景。 注意事項 訊息的持久性: Django Messages 預設只會在下一次請求中顯示,之後會自動清除。 如果需要更長時間的訊息顯示,可以考慮自訂訊息存儲(Message Storage)。 訊息存儲後端: 預設使用 CookieStorage 或 SessionStorage。 可以在 settings.py 中自訂:MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage' 多條訊息顯示: 如果在同一個請求中添加多條訊息,messages 會以列表的形式顯示。 總結 Django Messages 是一個簡單而強大的工具,適合用於提示訊息的顯示。通過內建的訊息類型和模板標籤,可以快速實現用戶友好的通知功能,並提升應用的交互體驗。

HTMX 是一個輕量級的前端框架,允許開發者直接在 HTML 中使用屬性來實現動態交互,無需撰寫大量的 JavaScript。它支持使用標準的 HTTP 方法(GET、POST、PUT、DELETE)進行局部更新,並且可以與 Django 等後端框架無縫整合。 HTMX 的核心功能 1. 局部更新 HTMX 可以通過 hx-get 或 hx-post 等屬性發送請求,並將返回的 HTML 片段插入到指定的 DOM 節點中。 範例:按讚功能 {% if user in interview.favorited_by.all %} 已收藏 {% else %} 收藏 {% endif %} hx-post: 發送 POST 請求到指定的 URL。 hx-swap="outerHTML": 替換按鈕本身的 HTML,而不僅僅是其內部內容。 2. 動態載入內容 HTMX 支持在用戶操作時動態載入內容,例如模態視窗(Modal)。 範例:動態載入模態視窗 <button hx-get="/modal" hx-target="#modal-container" class="btn">打開模態視窗</button> <div id="modal-container"></div> hx-get: 發送 GET 請求到 /modal。 hx-target: 指定將返回的 HTML 插入到 #modal-container 中。 3. 表單提交 HTMX 可以用於處理表單提交,並動態更新頁面的一部分。 範例:新增留言 <form action="{% url 'interviews:comment' interview.id %}" method="post" hx-target=".list" hx-swap="beforeend"> {% csrf_token %} <textarea name="content"></textarea> <button>新增留言</button> </form> hx-target: 指定將返回的內容插入到 .list 元素中。 hx-swap="beforeend": 將返回的內容追加到 .list 的末尾。 4. 事件處理 HTMX 支持使用屬性來處理事件,例如在請求完成後執行操作。 範例:顯示成功訊息 <button hx-post="/action" hx-trigger="click" hx-on="htmx:afterRequest: alert('操作成功')">執行操作</button> hx-trigger: 指定觸發請求的事件。 hx-on: 綁定 HTMX 事件(如 htmx:afterRequest)並執行 JavaScript。 HTMX 與 Django 的整合 1. 後端返回部分模板 在 Django 視圖中,返回一部分模板作為 HTMX 的響應。 範例:按讚視圖 views.py @require_POST @login_required def favorite(req, id): interview = get_object_or_404(Interview, pk=id) favorites = req.user.favorite_interviews if favorites.filter(pk=interview.pk).exists(): favorites.remove(interview) else: favorites.add(interview) return render(req, "interviews/favorite.html", {"user": req.user, "interview": interview}) favorite.html {% if user in interview.favorited_by.all %} 已收藏 {% else %} 收藏 {% endif %} 視圖返回的模板只包含按鈕的 HTML,HTMX 會自動更新頁面。 2. CSRF Token HTMX 請求需要包含 CSRF Token,否則 Django 會拒絕請求。 解決方法 在 HTML 的 <body> 標籤中添加以下屬性: <body hx-headers='{"x-csrftoken": "{{ csrf_token }}"}'> 3. 局部渲染 HTMX 可以用於實現局部渲染,避免整頁刷新。 範例:留言列表 <form action="{% url 'interviews:comment' interview.id %}" method="post" hx-target=".list" hx-swap="beforeend"> {% csrf_token %} <textarea name="content"></textarea> <button>新增留言</button> </form> <ul class="list"> {% for comment in comments %} {{ comment.user }} 說:{{ comment.content }} {% endfor %} </ul> 新增留言後,HTMX 只更新 .list 的內容,而不是整個頁面。 HTMX 的優勢 簡化前端開發: 無需撰寫大量 JavaScript,直接在 HTML 中定義交互邏輯。 與後端無縫整合: 使用 Django 模板引擎生成 HTML,減少前後端分離的複雜性。 提升性能: 支持局部更新,減少不必要的頁面刷新。 HTMX 的限制 複雜交互: 對於需要大量前端邏輯的應用,HTMX 可能不如前端框架(如 React 或 Vue)靈活。 學習曲線: 雖然 HTMX 簡單,但需要熟悉其屬性和事件。 總結 HTMX 是一個強大且輕量的工具,適合用於需要快速開發的 Django 項目。通過 HTMX,可以實現高效的局部更新和動態交互,提升用戶體驗,同時保持代碼的簡潔性。

功能概述 此功能實現了一個按讚(收藏)按鈕,使用者可以對特定的項目(如面試記錄)進行按讚或取消按讚操作。按鈕的狀態(已收藏或收藏)由後端決定,並通過 HTMX 局部更新頁面。 技術細節 1. 前端模板設計 favorite.html 這個模板負責渲染按讚按鈕,按鈕的狀態根據使用者是否已經按讚來決定。 {% if user in interview.favorited_by.all %} 已收藏 {% else %} 收藏 {% endif %} hx-post: 當按鈕被點擊時,發送一個 POST 請求到指定的 URL。 hx-swap="outerHTML": 指定 HTMX 替換整個按鈕的 HTML,而不僅僅是按鈕內的內容。 按鈕狀態: 使用 Django 模板語法 `

在本筆記中,將探討如何在 Django 中處理「動態切換按讚狀態」的邏輯: 介紹 ManyToMany 關聯模型的建立,並解釋如何使用它來設置使用者 (User) 與面試(Interview)之間的關聯。 說明為何在這樣的情境下,不能使用 get_object_or_404(),而是應該使用 filter() 方法來處理查詢。 展示如何在 favorite 這樣的 view function 中實現按讚與取消按讚功能,動態切換按讚狀態。 ManyToMany 關聯模型的建立 在 Django 中,使用 ManyToMany 關聯來表示多對多的關係。假設有 User 和 Interview 模型,可以建立以下的多對多關聯: interviews/models.py from django.contrib.auth.models import User from django.db import models class Interview(models.Model): # 其他欄位... user = models.ForeignKey(User, on_delete=models.CASCADE) favorited_by = models.ManyToManyField( User, through="FavoriteInterview", related_name="favorite_interviews", # join 欄位 ) # join table class FavoriteInterview(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) interview = models.ForeignKey(Interview, on_delete=models.CASCADE) 關聯設置 FavoriteInterview 表示每個使用者對於某個面試的「按讚」記錄。每一筆記錄由 user 和 interview 兩個外鍵組成。 使用 ManyToManyField 假設希望在 User 模型中表示使用者按讚過的所有 Interview,可以在 Interview 模型中設置 ManyToManyField 來表示這種關聯 通過 interview.favorited_by 來獲取按讚某篇面試的所有使用者 通過 user.favorite_interviews 來獲取某位使用者按讚的所有面試 在 favorite view function 中處理「按讚」和「取消按讚」的邏輯 interviews/views.py from django.shortcuts import get_object_or_404, redirect from django.contrib.auth.decorators import login_required from django.views.decorators.http import require_POST @require_POST @login_required def favorite(req, id): interview = get_object_or_404(Interview, pk=id) # 獲取指定的 interview favorites = req.user.favorite_interviews # 登入的 user 按讚的所有面試 # 以 User model 的角度處理 ManyToMany 關聯 # 判斷這位使用者是否已經按過讚 if favorites.filter(pk=interview.pk).exists(): # 如果有按過讚,則取消按讚(remove),在 FavoriteInterview table 裡刪除該筆資料 favorites.remove(interview) else: # 如果沒有按過讚,則加上(add),在 FavoriteInterview table 裡增加該筆資料 favorites.add(interview) return redirect("interviews:show", id=interview.id) 功能解釋: get_object_or_404():查找指定的 Interview 實例,若找不到則會拋出 404 錯誤(這裡是確保該面談存在)。 filter(pk=interview.pk).exists():檢查該 user 是否已經按讚過這篇 interview,如果已經按讚,則執行 remove();如果沒有按讚,則執行 add(),這樣就能夠動態切換按讚狀態。 redirect():操作完成後,重定向回該 interview 的詳細頁面。 小結 ManyToMany 關聯的建立:使用 ManyToManyField 和 through、related_name 參數來設置 User 和 Interview 之間的多對多關聯,並且在中介模型 FavoriteInterview 中存儲按讚資料。 使用 filter() 判斷使用者是否已經按過讚:使用 filter() 和 .exists() 來檢查 FavoriteInterview 是否存在對應的關聯,根據結果決定是按讚還是移除按讚。 filter() 方法可以返回符合條件的查詢結果,如果沒有找到任何符合條件的資料,它會返回一個空的 queryset,而不是拋出錯誤,因此filter() 可以靈活處理按讚的狀態 為何 get_object_or_404() 不適用判斷使用者是否已經按過讚:在動態切換按讚狀態時,使用 get_object_or_404() 不適合,因為查詢結果沒有找到資料,它會拋出 404 錯誤,這會讓我們無法靈活處理「已按讚」和「未按讚」的情況。 從不同 Model 角度處理 ManyToMany 關聯 在 Django 中,ManyToMany 關聯允許兩個模型之間建立多對多的關聯。這些關聯通常由 ManyToManyField 或透過額外的關聯表(如 through 設定)來實現。在不同的 Model 角度處理 ManyToMany 關聯時,我們有不同的寫法來處理關聯資料的新增、刪除與查詢。 1. 從 User 模型的角度處理 ManyToMany 關聯 首先,讓我們回顧如何從 User 模型的角度處理 ManyToMany 關聯。在這個例子中,User 和 Interview 之間的關聯是透過 favorite_interviews(即 ManyToManyField)來建立的。我們可以在 User 模型上使用 favorite_interviews,來處理這篇 Interview 是否已經被使用者按讚過。 @require_POST @login_required def favorite(req, id): interview = get_object_or_404(Interview, pk=id) user = req.user # 以 User model 的角度處理 ManyToMany 關聯 # 判斷這位使用者是否已經按過讚 if user.favorite_interviews.filter(pk=interview.pk).exists(): # 如果有按過讚,則取消按讚(remove) user.favorite_interviews.remove(interview) else: # 如果沒有按過讚,則加上(add) user.favorite_interviews.add(interview) user.favorite_interviews.filter(pk=interview.pk):我們通過 filter 查詢使用者是否已經對這篇 interview 按讚過,這會返回一個 QuerySet,並且使用 exists() 方法檢查是否存在此條記錄。 remove() 和 add():remove() 用來移除使用者對該 interview 的按讚記錄,add() 用來新增按讚記錄。 2. 從 Interview 模型的角度處理 ManyToMany 關聯 另一種處理方式是從 Interview 模型的角度來管理 ManyToMany 關聯。可以直接操作 favorited_by,這是 Interview 模型中的 ManyToManyField,它用來表示所有按讚過這篇訪談的使用者。 # 以 Interview model 的角度處理 ManyToMany 關聯 # 判斷這篇文章是否已經被按過讚 if interview.favorited_by.filter(pk=user.pk).exists(): # 如果有按過讚,則取消按讚(remove) interview.favorited_by.remove(user) else: # 如果沒有按過讚,則加上(add) interview.favorited_by.add(user) interview.favorited_by.filter(pk=user.pk):使用 filter 查詢是否該 user 已經按讚過這篇 interview,如果有,則移除按讚;如果沒有,則將按讚關聯添加回 favorited_by。 3. 從 FavoriteInterview 模型的角度處理 ManyToMany 關聯 如果我們使用的是 through 設定的 ManyToMany 關聯(在本例中,使用了 FavoriteInterview 作為關聯模型),可以直接操作 FavoriteInterview 模型來添加或刪除按讚記錄。 # 以 FavoriteInterview model 的角度處理 ManyToMany 關聯 favorite_interview = FavoriteInterview.objects.filter(user=user, interview=interview) if favorite_interview.exists(): # 如果已經按過讚,則刪除該記錄 favorite_interview.delete() else: # 如果沒有按過讚,則新增該記錄 FavoriteInterview.objects.create(user=user, interview=interview) 首先查詢是否已經存在這個 FavoriteInterview 記錄,如果存在則刪除(使用 .delete()),如果不存在則創建新的 FavoriteInterview 記錄(使用 .create())。 使用 filter(user=user, interview=interview) 來確保我們只關注到該使用者和該訪談的關聯。 總結: ManyToManyField 在 Django 可以用來管理多對多的關聯。但當需要處理這些關聯時,必須明確知道該如何從不同的角度來操作。 從 User 模型的角度:我們可以操作該使用者的 favorite_interviews 屬性來添加或移除關聯。 從 Interview 模型的角度:我們可以操作該面試的 favorited_by 屬性來處理關聯。 從 FavoriteInterview 模型的角度:透過 through 設定的關聯表,可以直接操作這個模型來進行新增或刪除。 這些方法各有其使用場景,選擇哪一種方式取決於你的需求和數據模型的設計。在處理 ManyToMany 關聯時,要特別注意數據一致性和簡潔性,並選擇最符合需求的解決方案。
0%