Logo

新人日誌

首頁關於我部落格

新人日誌

Logo

網站會不定期發佈技術筆記、職場心得相關的內容,歡迎關注本站!

網站
首頁關於我部落格
部落格
分類系列文

© 新人日誌. All rights reserved. 2020-present.

Djano 標籤編輯頁|前、後端設定說明

最後更新:2025年1月27日Python

本文為 Django 標籤功能 系列文,第 6 篇

  1. 新手指南:在 Django 中實作標籤功能
  2. Django 新手指南:django-taggit 簡介
  3. 新手指南:使用 Alpine.js 實現前端標籤輸入功能
  4. 新手指南|Django 標籤功能|後端資料儲存邏輯
  5. 新手指南:Django 標籤顯示功能解析
  6. Djano 標籤編輯頁|前、後端設定說明 👈所在位置
  7. 使用 Django 建立標籤分類頁:新手教學

在網頁應用程式中,標籤(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 格式渲染,避免轉義(如 " 變成 &quot;)。
      • |default:'[]':如果後端傳遞的 tags 是空值,預設為空陣列 []。
  • 範例:
    • 假設資料庫中服務的標籤是 ["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 模板引擎會自動將特殊字元轉義:&quot;[&quot;Django&quot;, &quot;Python&quot;]&quot; 這樣的結果雖然可以安全顯示在網頁上,但無法作為有效的 JavaScript 數據被解析。
  • 使用 |safe 後,Django 會將內容標記為「安全」,避免轉義,輸出的內容將保持原始格式:["Django", "Python"]

為什麼需要 |safe?

在這段代碼中,後端的 tags 是一個 JSON 格式的字串,目的是傳遞標籤數據供前端使用。如果沒有 |safe,模板會將其轉義成無效的 HTML,導致前端無法正確讀取和使用。

例如:

沒有 |safe

如果後端傳遞的 tags 是 ["Django", "Python"],渲染後的結果為:

<div x-data="{ tags: [&quot;Django&quot;, &quot;Python&quot;], 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 代碼,使得這些代碼在其他用戶的瀏覽器中執行。

這可能導致敏感信息洩露、用戶會話被劫持、用戶數據被竄改等問題。

攻擊流程概述

  1. 攻擊者在網站輸入框(如評論區、表單等)插入惡意的 JavaScript 代碼。
  2. 網站未經適當的處理,直接將這些代碼存儲或返回給其他用戶。
  3. 當其他用戶訪問包含惡意代碼的頁面時,瀏覽器執行這些代碼。
  4. 攻擊者利用執行的代碼竊取用戶數據或進行其他惡意行為。

Django 為什麼進行 HTML 字元轉義?

為了防範 XSS 攻擊,Django 預設會對模板中的變數進行 HTML 字元轉義,這樣可以防止用戶輸入的惡意代碼被執行。

HTML 字元轉義是什麼?

HTML 字元轉義是將特殊字元(如 <, >, & 等)轉換為其對應的 HTML 實體,這樣可以讓字元被安全地顯示在網頁上,而不是作為代碼執行。

字元HTML 實體說明
<&lt;小於號,避免被解析為標籤
>&gt;大於號,避免被解析為標籤
"&quot;雙引號,避免屬性被竄改
'&#x27;單引號,避免屬性被竄改
&&amp;和號,避免引起混淆
HTML 實體&lt;
說明小於號,避免被解析為標籤
HTML 實體&gt;
說明大於號,避免被解析為標籤
HTML 實體&quot;
說明雙引號,避免屬性被竄改
HTML 實體&#x27;
說明單引號,避免屬性被竄改
HTML 實體&amp;
說明和號,避免引起混淆

舉例

假設用戶在評論框中輸入:

<script>alert('XSS!')</script>

如果不轉義,模板直接渲染,結果如下:

<div>用戶評論:<script>alert('XSS!')</script></div>

瀏覽器會執行 <script> 標籤中的 JavaScript 代碼,彈出警告框 XSS!。

但如果 Django 進行了字元轉義,渲染後的結果是:

<div>用戶評論:&lt;script&gt;alert('XSS!')&lt;/script&gt;</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 陣列中移除對應的標籤。

功能流程概述

  1. 後端抓取標籤數據
    使用 get_object_or_404 獲取服務的標籤列表,並透過上下文傳遞至模板。
  2. 前端顯示標籤
    使用 Alpine.js 初始化數據,並動態生成標籤列表。
  3. 標籤管理
    提供新增與刪除標籤的功能,並透過隱藏欄位確保表單提交時的數據完整性。

結語

透過本文的說明,我們學習了如何從資料庫中抓取標籤,並將其有效地傳遞至前端進行顯示與管理。

無論是後端的數據處理,還是前端的交互設計,都保持了簡潔且高效的實現方式。

希望這篇文章能幫助您更好地理解標籤功能的實作邏輯,並應用於實際專案中!

目前還沒有留言,成為第一個留言的人吧!

發表留言

留言將在審核後顯示。

Python

目錄

  • 後端處理:抓取標籤並傳遞至前端
  • 核心代碼解析
  • 前端處理:顯示與管理標籤
  • tags:儲存後端傳遞的標籤數據
  • Django 模板中的 {{ tags|safe|default:'[]' }}
  • |safe 的作用:避免轉義
  • 補充:XSS 攻擊是什麼?
  • 動態顯示標籤
  • 功能流程概述
  • 結語