本文為 Django 標籤功能 系列文,第 6 篇
在網頁應用程式中,標籤(Tag)是一種常見的功能,能夠協助用戶快速分類與檢索內容。
本文將以 Django 框架為例,詳細說明如何從資料庫抓取儲存的標籤,在後端進行處理,並提供給前端顯示。
本文不涵蓋標籤的儲存邏輯與前端的動態處理細節,如有需求請參考相關文章。
後端處理:抓取標籤並傳遞至前端
@login_required
def edit_service(request, id, service_id):
if not has_permission(request, id):
return redirect("services:error_page")
service = get_object_or_404(Service, id=service_id, freelancer_user=request.user)
categories = Category.objects.all()
tags = list(service.tags.names())
if request.method == "POST":
form = ServiceForm(request.POST, request.FILES, instance=service)
if form.is_valid():
form.save()
selected_categories = request.POST.getlist("category")
service.category.set(selected_categories)
tags_input = request.POST.get("tags", "")
if tags_input:
service.tags.set([tag.strip() for tag in tags_input.split(',') if tag.strip()])
else:
service.tags.clear()
return redirect("services:freelancer_dashboard", id=id)
else:
form = ServiceForm(instance=service)
return render(
request,
"services/edit_service.html",
{
"form": form,
"categories": categories,
"show_loading": True, # 傳遞顯示 loading 的標記到模板
"tags": tags
},
)核心代碼解析
在 Django 後端,我們使用了 get_object_or_404 來抓取目標服務 (Service) 的數據,並透過 .tags.names() 方法提取該服務的所有標籤名稱,最終以列表形式傳遞至前端。
service = get_object_or_404(Service, id=service_id, freelancer_user=request.user)
tags = list(service.tags.names())get_object_or_404
此方法從資料庫中查詢Service物件,若找不到符合條件的物件 (如id不存在或用戶未被授權) 時,將返回 404 錯誤頁面。service.tags.names()
這是一個 Django Taggit 提供的便捷方法,能直接獲取目標物件的標籤名稱,並以列表形式返回。例如,若某服務有標籤 “Django” 和 “Python”,結果將為['Django', 'Python']。
將抓取的標籤數據加入 render 的上下文後,便能將其傳遞至前端:
return render(
request,
"services/edit_service.html",
{
"form": form,
"categories": categories,
"show_loading": True, # 用於顯示加載標記
"tags": tags # 將標籤列表傳遞到模板
},
)
前端處理:顯示與管理標籤
<div x-data="{ tags: {{ tags|safe|default:'[]' }}, newTag: '' }" class="flex flex-col space-y-2 mt-4">
<label for="tags" class="text-gray-700 font-medium">標籤:</label>
<!-- ✅ 標籤輸入欄位 -->
<input type="text" x-model="newTag" @keydown.enter.prevent="
if (newTag.trim() !== '' && !tags.includes(newTag.trim())) {
tags.push(newTag.trim());
newTag = '';
}
" placeholder="輸入標籤並按 Enter" class="bg-white text-gray-800 border border-gray-300 rounded-md p-2 w-full" />
<!-- ✅ 已輸入的標籤顯示 -->
<div class="flex flex-wrap gap-2 mt-2">
<template x-for="(tag, index) in tags" :key="index">
<div class="flex items-center bg-indigo-500 text-white text-sm px-3 py-1 rounded-full">
<span x-text="tag"></span>
<button type="button" @click="tags.splice(index, 1)"
class="ml-2 py-1 text-white hover:text-red-300 focus:outline-none">
X
</button>
</div>
</template>
</div>
<!-- ✅ 隱藏的 input 用於 Django 表單提交 -->
<input type="hidden" name="tags" :value="tags.join(',')">
</div>在前端,我們使用了 Alpine.js 來實現簡單的標籤動態處理功能,例如新增與刪除標籤。以下是關鍵代碼與功能解析。
<div x-data="{ tags: {{ tags|safe|default:'[]' }}, newTag: '' }" class="flex flex-col space-y-2 mt-4">x-data 是 Alpine.js 的核心屬性,用來初始化和管理前端組件的數據模型。
在這段代碼中,x-data 定義了兩個關鍵變數:tags 和 newTag,它們的作用如下:
<div x-data="{ tags: {{ tags|safe|default:'[]' }}, newTag: '' }" class="flex flex-col space-y-2 mt-4">
tags:儲存後端傳遞的標籤數據
- 功能:
tags是用於儲存後端傳遞的標籤數據的陣列。- 這些標籤來自於 Django 後端通過上下文傳遞的
tags變數,通常是資料庫中已經存在的標籤名稱集合。
- 數據來源:
- 後端的 Python 代碼中,
tags = list(service.tags.names())抓取該服務的標籤,並以 JSON 格式傳遞給模板。 - 在模板中,通過 Django 模板語法
{{ tags|safe|default:'[]' }}渲染到前端:|safe:確保數據以安全的 JSON 格式渲染,避免轉義(如"變成")。|default:'[]':如果後端傳遞的tags是空值,預設為空陣列[]。
- 後端的 Python 代碼中,
- 範例:
- 假設資料庫中服務的標籤是
["Django", "Python"],那麼tags在前端初始化後的值為:tags: ["Django", "Python"] - 如果該服務尚未設定任何標籤,後端會傳遞空值,則預設為:
tags: []
- 假設資料庫中服務的標籤是
Django 模板中的 {{ tags|safe|default:'[]' }}
{{ ... }}:
Django 模板語法,用於將後端傳遞的變數插入到前端模板中,這裡是將tags的內容插入到 HTML 中。|safe:
Django 的模板過濾器,用於標記變數的內容為「安全」,告訴模板引擎不要對內容進行轉義。|default:'[]':
如果變數tags是空值(如None),則使用預設值'[]',以避免前端出現錯誤。
|safe 的作用:避免轉義
在 Django 模板中,所有變數的輸出內容默認會進行 HTML 字元轉義,以防範 XSS 攻擊。
例如,假設變數內容包含 HTML 或 JSON 特殊字元:
- 後端傳遞的值:
tags = '["Django", "Python"]' - 如果不加
|safe,Django 模板引擎會自動將特殊字元轉義:"["Django", "Python"]"這樣的結果雖然可以安全顯示在網頁上,但無法作為有效的 JavaScript 數據被解析。 - 使用
|safe後,Django 會將內容標記為「安全」,避免轉義,輸出的內容將保持原始格式:["Django", "Python"]
為什麼需要 |safe?
在這段代碼中,後端的 tags 是一個 JSON 格式的字串,目的是傳遞標籤數據供前端使用。如果沒有 |safe,模板會將其轉義成無效的 HTML,導致前端無法正確讀取和使用。
例如:
沒有 |safe
如果後端傳遞的 tags 是 ["Django", "Python"],渲染後的結果為:
<div x-data="{ tags: ["Django", "Python"], newTag: '' }"></div>
前端的 JavaScript 無法正確解析這樣的字串,會導致報錯或功能失效。
使用 |safe
有了 |safe,渲染的結果會是:
<div x-data="{ tags: ['Django', 'Python'], newTag: '' }"></div>這樣的數據格式可以被前端正確解析,並作為 JavaScript 陣列使用。
配合 |default 確保數據完整性
|default:'[]' 是保險措施,用於處理後端傳遞的 tags 可能為空的情況。例如:
- 如果
tags為None或未定義,模板會輸出空陣列[]:<div x-data="{ tags: [], newTag: '' }"></div> - 這樣可以避免前端初始化報錯,也保證程式的健壯性。
實際應用情境
假設後端的 tags 是這樣傳遞的:
tags = list(service.tags.names()) # 可能返回 ['Django', 'Python'] 或空列表 []模板中會這樣處理:
<div x-data="{ tags: {{ tags|safe|default:'[]' }}, newTag: '' }"></div>結果:
- 若
tags = ['Django', 'Python'],渲染為:<div x-data="{ tags: ['Django', 'Python'], newTag: '' }"></div> - 若
tags = [](或None),渲染為:<div x-data="{ tags: [], newTag: '' }"></div>
補充:XSS 攻擊是什麼?
XSS(Cross-Site Scripting,跨站腳本攻擊)是一種常見的網絡安全攻擊類型。
攻擊者通過向網站插入惡意的 JavaScript 代碼,使得這些代碼在其他用戶的瀏覽器中執行。
這可能導致敏感信息洩露、用戶會話被劫持、用戶數據被竄改等問題。
攻擊流程概述
- 攻擊者在網站輸入框(如評論區、表單等)插入惡意的 JavaScript 代碼。
- 網站未經適當的處理,直接將這些代碼存儲或返回給其他用戶。
- 當其他用戶訪問包含惡意代碼的頁面時,瀏覽器執行這些代碼。
- 攻擊者利用執行的代碼竊取用戶數據或進行其他惡意行為。
Django 為什麼進行 HTML 字元轉義?
為了防範 XSS 攻擊,Django 預設會對模板中的變數進行 HTML 字元轉義,這樣可以防止用戶輸入的惡意代碼被執行。
HTML 字元轉義是什麼?
HTML 字元轉義是將特殊字元(如 <, >, & 等)轉換為其對應的 HTML 實體,這樣可以讓字元被安全地顯示在網頁上,而不是作為代碼執行。
舉例
假設用戶在評論框中輸入:
<script>alert('XSS!')</script>如果不轉義,模板直接渲染,結果如下:
<div>用戶評論:<script>alert('XSS!')</script></div>瀏覽器會執行 <script> 標籤中的 JavaScript 代碼,彈出警告框 XSS!。
但如果 Django 進行了字元轉義,渲染後的結果是:
<div>用戶評論:<script>alert('XSS!')</script></div>
瀏覽器會將這些字元當作普通文本顯示:
用戶評論:<script>alert('XSS!')</script>這樣,就有效防止了惡意代碼的執行。
動態顯示標籤
標籤會以列表的形式顯示,並且每個標籤都有刪除按鈕,方便用戶進行管理。
<div class="flex flex-wrap gap-2 mt-2">
<template x-for="(tag, index) in tags" :key="index">
<div class="flex items-center bg-indigo-500 text-white text-sm px-3 py-1 rounded-full">
<span x-text="tag"></span>
<button
type="button"
@click="tags.splice(index, 1)"
class="ml-2 py-1 text-white hover:text-red-300 focus:outline-none">
X
</button>
</div>
</template>
</div>
x-for
使用 Alpine.js 的循環指令來遍歷tags,每個標籤會生成一個元素。- 刪除標籤功能
點擊X按鈕時,執行tags.splice(index, 1),從tags陣列中移除對應的標籤。
功能流程概述
- 後端抓取標籤數據
使用get_object_or_404獲取服務的標籤列表,並透過上下文傳遞至模板。 - 前端顯示標籤
使用 Alpine.js 初始化數據,並動態生成標籤列表。 - 標籤管理
提供新增與刪除標籤的功能,並透過隱藏欄位確保表單提交時的數據完整性。
結語
透過本文的說明,我們學習了如何從資料庫中抓取標籤,並將其有效地傳遞至前端進行顯示與管理。
無論是後端的數據處理,還是前端的交互設計,都保持了簡潔且高效的實現方式。
希望這篇文章能幫助您更好地理解標籤功能的實作邏輯,並應用於實際專案中!