在 Django 中實現留言檢索與排序功能:完整指南

更新日期: 2024 年 11 月 26 日

本指南將展示如何在 Django 項目中,針對特定履歷檢索留言,並按建立時間進行排序。

最終,您將學會如何優化留言代碼,實現更高效的邏輯處理。


找出特定履歷的留言

在 Django 中,利用 ORM 的多種查詢方式,我們可以方便地檢索特定履歷的留言。

以下是兩種常見方法的詳細解釋:

使用 filter 方法

filter 方法直接查詢 Comment 模型的數據,並根據條件過濾出符合條件的留言。

示例代碼

comments = Comment.objects.filter(resume_id=1)

詳細解釋

  1. Comment.objects
    • Comment 是我們定義的留言模型,objects 是它的查詢管理器,用於操作數據庫中的 Comment 資料表。
  2. filter(resume_id=1)
    • filter 是一個查詢方法,接受條件作為參數。
    • resume_id=1 表示查詢條件:找出 resume_id 為 1 的所有留言。
  3. 返回結果
    • 該語句返回一個 QuerySet,包含與條件匹配的所有 Comment 實例。

適用場景

當我們知道具體條件(如 resume_id),並希望直接在 Comment 模型中執行篩選時,filter 方法非常高效。

使用 comment_set 方法

comment_setForeignKey 提供的反向關聯功能,用於從目標模型(如 Resume)直接檢索相關的外鍵數據。

示例代碼

comments = resume.comment_set.all()

詳細解釋

  1. resume
    • resume 是我們從資料庫中檢索的特定 Resume 實例。
  2. comment_set
    • comment_setResume 模型與 Comment 模型之間的反向關聯屬性。
    • 它自動生成,源於 Comment 模型中的 ForeignKey
class Comment(models.Model): 
    resume = models.ForeignKey(Resume, on_delete=models.CASCADE)
  1. all()
    • 該方法檢索與該 Resume 實例相關的所有 Comment,返回一個 QuerySet。
  2. 返回結果
    • 包含該履歷的所有相關留言。

補充說明:ResumeComment 模型的關係

該方法檢索與該 Resume 實例相關的所有 Comment,返回一個 QuerySet。

由於 resume 實例與 Comment 之間屬於一對多的關係,這表示單一 resume 可以關聯多個 comment

因此,comment_set 是 Django 自動生成的反向關聯屬性,用於從 Resume 實例檢索所有相關的 Comment

當我們調用 comment_set.all() 時,實際上執行了數據庫查詢,檢索出與當前 resume 實例關聯的所有 Comment,並以一個 QuerySet 的形式返回。這個 QuerySet 是延遲評估的,支持進一步篩選、排序或遍歷操作。

例如:

resume = Resume.objects.get(id=1)  # 獲取特定的 Resume
comments = resume.comment_set.all()  # 獲取該 Resume 的所有相關 Comment

for comment in comments:
    print(comment.content)  # 輸出每條留言內容

這樣的查詢不僅直觀,也能清晰表達出一對多關係的數據操作邏輯。

適用場景

comment_set 方法更直觀,特別適合處理具有關聯性的數據結構,能快速檢索與某個模型實例相關的所有數據。


總結

方法優勢適用場景
filter高度靈活,可添加複雜條件篩選需要在特定模型中直接查詢時
comment_set簡潔直觀,符合 Django 的設計通過目標實例查詢其相關數據時

修改 Views 函數實現留言檢索

resumes 應用的 views.py 中,修改 show 函數以實現留言檢索和排序:

from django.shortcuts import render, get_object_or_404
from resumes.models import Resume

def show(request, id):
    # 找到指定的 Resume
    resume = get_object_or_404(Resume, id=id)

    # 抓取與該 Resume 相關聯的 Comment,按 created_at 倒序排列
    comments = resume.comment_set.order_by("-created_at").all()

    # 渲染模板並傳遞 Resume 和 Comments
    return render(
        request,
        "resumes/show.html",
        {
            "resume": resume,
            "comments": comments,
        }
    )

函數解讀

comment_set.order_by

comments = resume.comment_set.order_by("-created_at").all()
  1. 作用
    • Resume 實例中檢索所有關聯的 Comment,並按照 created_at 倒序排列。
  2. 詳細解釋
    • comment_set:調用 ResumeComment 模型的反向關聯。
    • order_by("-created_at"):按 created_at 字段排序,- 表示降序。
    • all():檢索 QuerySet 中的所有數據。
  3. 返回結果
    • 返回該履歷的所有留言,按時間倒序排列,最新的留言排在最前。

render 函數

return render(request, "resumes/show.html", {"resume": resume, "comments": comments})
  1. 作用
    • 將數據渲染到指定模板,並返回完整的 HTML 給用戶。
  2. 參數解釋
    • request:當前的 HTTP 請求對象。
    • "resumes/show.html":指定渲染的模板文件。
    • {"resume": resume, "comments": comments}:將 resumecomments 作為上下文數據傳遞給模板。
  3. 為什麼需要傳遞上下文?
    • 上下文數據是模板渲染的核心,resumecomments 將在模板中用於動態顯示履歷信息與相關留言。

修改模板顯示留言

show.html 模板中新增留言顯示區塊,完整模板代碼如下:

{% extends "shared/layout.html" %}

{% block content %}

{% if messages %}
    <div class="alert-box">
        {% for message in messages %}
            <div class="alert {{ message.tags }}">{{ message }}</div>
        {% endfor %}
    </div>
{% endif %}

<h1>show! {{ resume.title }}</h1>
<h3>{{ resume.skill }}</h3>

<article>
    <p>{{ resume.location }}</p>
    <p>{{ resume.content }}</p>
</article>
<footer>
    <a href="{% url 'resumes:edit' resume.id %}">編輯</a>
    <a href="{% url 'resumes:delete' resume.id %}">刪除</a>
</footer>

<section>
    <form action="{% url 'resumes:comments' resume.id %}" method="post">
        {% csrf_token %}
        <textarea name="content" rows="4" cols="50"></textarea>
        <button type="submit">新增留言</button>
    </form>
</section>

<ul>
    {% for comment in comments %}
    <li>{{ comment.content }}</li>
    {% endfor %}
</ul>

{% endblock %}

模板亮點

  • 渲染留言
    • 利用 for 循環,將 comments 內的每條留言逐一渲染到頁面上。
<ul>
    {% for comment in comments %}
        <li>{{ comment.content }}</li>
    {% endfor %}
</ul>
  • 顯示最新留言: 通過在 views.py 中使用 order_by("-created_at"),確保留言按時間倒序排列。

優化留言代碼

comments 應用的視圖中,可以進一步優化新增留言的代碼:

原始代碼

@require_POST
def index(request, id):
    # 找出 resume

    resume = get_object_or_404(Resume, id=id)
    # 新增 comment

    comment = Comment()
    comment.content = request.POST.get("content")
    comment.resume_id = resume.id
    comment.save()

    messages.success(request, "新增留言成功!")

    # 轉址(轉到同個頁面)
    return redirect("resumes:show", id=resume.id)

問題

  • 代碼冗長,需多行操作才能完成留言創建。
  • 可讀性一般,無法直觀反映留言與履歷的關聯性。

優化後代碼

@require_POST
def index(request, id):
    # 找出 resume

    resume = get_object_or_404(Resume, id=id)
    # 新增 comment

    content = request.POST.get("content")
    comment = resume.comment_set.create(content = content)

    messages.success(request, "新增留言成功!")

    # 轉址(轉到同個頁面)
    return redirect("resumes:show", id=resume.id)

代碼詳細解讀

抓取特定的 Resume

resume = get_object_or_404(Resume, id=id)

作用:

這行代碼的功能是從資料庫中找到一個指定 ID 的 Resume(履歷),並確保該資料存在。

如果找不到對應的 Resume,則返回 HTTP 404 錯誤頁面。

邏輯:

這段代碼使用了 Django 提供的快捷函數 get_object_or_404

  • 第一個參數: 模型類(Resume),表示需要檢索的數據類型。
  • 第二個參數: 篩選條件(id=id),用來匹配特定的 Resume

等同於以下的代碼:

try:
    resume = Resume.objects.get(id=id)
except Resume.DoesNotExist:
    raise Http404("Resume 不存在")

簡而言之: 這一步的目的就是找到對應的 Resume 實例,並將其保存到變數 resume 中,供後續操作使用。

創建並關聯留言

resume.comment_set.create(content=content)

作用:

這行代碼的功能是 創建一條新的留言(Comment),並將其自動關聯到特定的履歷(Resume

背景邏輯:

  1. 關聯模型:
    • 在 Django 中,ResumeComment 模型之間是一對多的關係,因為一份履歷可以有多條留言。
    • 通過在 Comment 模型中定義 ForeignKey,可以實現這種關聯:
class Comment(models.Model):
    content = models.TextField()
    resume = models.ForeignKey(Resume, on_delete=models.CASCADE)
  1. 反向關聯:
    • Django 自動為 Resume 添加了一個反向關聯屬性 comment_set
    • 這表示可以通過 resume.comment_set 訪問與該 Resume 關聯的所有留言。
  2. .create() 方法:
    • .create() 是一個快捷方法,它同時完成了「創建實例」和「保存到數據庫」的操作。
    • 作用等同於: comment = Comment(content=content, resume_id=resume.id) comment.save()

執行後的效果:

  • 新的留言(Comment)會自動帶有該履歷(Resume)的外鍵關聯。
  • 表單中提取的留言內容(content)會被存儲到新留言的 content 屬性中。

代碼的執行結果

假設:

  • 目前的 Resume 是 ID 為 1 的履歷。
  • 用戶輸入的留言內容是「這是我的留言」。

執行這段代碼後:

resume.comment_set.create(content=content)

數據庫中會新增以下記錄:

Comment:
  id: 1
  content: "這是我的留言"
  resume_id: 1  # 關聯到 ID 為 1 的 Resume

生活化的經驗說明

把這段代碼的作用比喻成「留言板與貼紙」的日常情境:

  1. 背景設定:
    • 假設每個 Resume 是一個留言板(每個留言板代表一份履歷)。
    • 用戶輸入的留言(content)是你準備要寫的貼紙內容。
    • 你需要把貼紙寫好,並貼到對應的留言板上。
  2. resume.comment_set.create(content=content) 的作用:
    • 這段代碼做的事情就像是:
      • 找到目標留言板(例如,ID 是 1 的留言板)。
      • 直接在這個留言板上貼上一張寫有用戶留言內容(如「這是我的留言」)的貼紙。
      • 貼完後,系統自動記錄這張貼紙是屬於這塊留言板的,無需你再手動做任何事。
  3. 具體過程:
    • 你不用先製作貼紙(創建 Comment),再手動貼上(設置 resume_id),最後貼完了再記錄(save())。
    • create() 就是一步完成所有工作:
      • 製作貼紙、寫內容、貼上留言板,最後系統自動保存。

小結

透過本指南,我們完成了以下功能:

  1. 留言檢索與排序
    • 使用 ForeignKey 提供的反向關聯功能,檢索特定履歷的所有留言。
    • created_at 字段倒序排列留言,確保最新留言優先顯示。
  2. 模板顯示留言
    • 修改 show.html,顯示所有留言並提供新增留言的表單。
  3. 代碼優化
    • 使用 comment_set.create 簡化留言創建代碼。

這些操作展示了 Django ORM 的靈活性,幫助我們在最少代碼中實現強大的數據處理功能。

如果希望進一步提升性能,可以考慮使用分頁功能來處理大量留言的顯示。

Similar Posts

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *