在 Django 中實現留言檢索與排序功能:完整指南
更新日期: 2024 年 11 月 26 日
本文為 Django 高階教學,第 5 篇:
- 使用 Poetry 管理 Django 專案:完整指南
- 使用 ModelForm 優化 Django 表單與資料庫操作
- Django 留言功能實作指南:從模型設計到應用完成
- Django 留言功能新增與視圖互動詳解
- 在 Django 中實現留言檢索與排序功能:完整指南 👈 所在位置
- 在 Django 中實現留言換行與顯示時間功能
- 在 Django 中實現留言刪除功能:完整指南
- 在 Django 中實現軟刪除功能:完整指南
- 在 Django 中實現軟刪除功能:Fat Model, Thin View
建議閱讀本文前,先閱讀完 Django 進階教學 系列文
本指南將展示如何在 Django 項目中,針對特定履歷檢索留言,並按建立時間進行排序。
最終,您將學會如何優化留言代碼,實現更高效的邏輯處理。
找出特定履歷的留言
在 Django 中,利用 ORM 的多種查詢方式,我們可以方便地檢索特定履歷的留言。
以下是兩種常見方法的詳細解釋:
使用 filter
方法
filter
方法直接查詢 Comment
模型的數據,並根據條件過濾出符合條件的留言。
示例代碼
comments = Comment.objects.filter(resume_id=1)
詳細解釋
Comment.objects
:Comment
是我們定義的留言模型,objects
是它的查詢管理器,用於操作數據庫中的Comment
資料表。
filter(resume_id=1)
:filter
是一個查詢方法,接受條件作為參數。resume_id=1
表示查詢條件:找出resume_id
為 1 的所有留言。
- 返回結果:
- 該語句返回一個 QuerySet,包含與條件匹配的所有
Comment
實例。
- 該語句返回一個 QuerySet,包含與條件匹配的所有
適用場景
當我們知道具體條件(如 resume_id
),並希望直接在 Comment
模型中執行篩選時,filter
方法非常高效。
使用 comment_set
方法
comment_set
是 ForeignKey
提供的反向關聯功能,用於從目標模型(如 Resume
)直接檢索相關的外鍵數據。
示例代碼
comments = resume.comment_set.all()
詳細解釋
resume
:resume
是我們從資料庫中檢索的特定Resume
實例。
comment_set
:comment_set
是Resume
模型與Comment
模型之間的反向關聯屬性。- 它自動生成,源於
Comment
模型中的ForeignKey
:
class Comment(models.Model):
resume = models.ForeignKey(Resume, on_delete=models.CASCADE)
all()
:- 該方法檢索與該
Resume
實例相關的所有Comment
,返回一個 QuerySet。
- 該方法檢索與該
- 返回結果:
- 包含該履歷的所有相關留言。
補充說明:Resume
與 Comment
模型的關係
該方法檢索與該 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()
- 作用:
- 從
Resume
實例中檢索所有關聯的Comment
,並按照created_at
倒序排列。
- 從
- 詳細解釋:
comment_set
:調用Resume
與Comment
模型的反向關聯。order_by("-created_at")
:按created_at
字段排序,-
表示降序。all()
:檢索 QuerySet 中的所有數據。
- 返回結果:
- 返回該履歷的所有留言,按時間倒序排列,最新的留言排在最前。
render
函數
return render(request, "resumes/show.html", {"resume": resume, "comments": comments})
- 作用:
- 將數據渲染到指定模板,並返回完整的 HTML 給用戶。
- 參數解釋:
request
:當前的 HTTP 請求對象。"resumes/show.html"
:指定渲染的模板文件。{"resume": resume, "comments": comments}
:將resume
和comments
作為上下文數據傳遞給模板。
- 為什麼需要傳遞上下文?
- 上下文數據是模板渲染的核心,
resume
和comments
將在模板中用於動態顯示履歷信息與相關留言。
- 上下文數據是模板渲染的核心,
修改模板顯示留言
在 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
)。
背景邏輯:
- 關聯模型:
- 在 Django 中,
Resume
和Comment
模型之間是一對多的關係,因為一份履歷可以有多條留言。 - 通過在
Comment
模型中定義ForeignKey
,可以實現這種關聯:
- 在 Django 中,
class Comment(models.Model):
content = models.TextField()
resume = models.ForeignKey(Resume, on_delete=models.CASCADE)
- 反向關聯:
- Django 自動為
Resume
添加了一個反向關聯屬性comment_set
。 - 這表示可以通過
resume.comment_set
訪問與該Resume
關聯的所有留言。
- Django 自動為
.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
生活化的經驗說明
把這段代碼的作用比喻成「留言板與貼紙」的日常情境:
- 背景設定:
- 假設每個
Resume
是一個留言板(每個留言板代表一份履歷)。 - 用戶輸入的留言(
content
)是你準備要寫的貼紙內容。 - 你需要把貼紙寫好,並貼到對應的留言板上。
- 假設每個
resume.comment_set.create(content=content)
的作用:- 這段代碼做的事情就像是:
- 找到目標留言板(例如,ID 是 1 的留言板)。
- 直接在這個留言板上貼上一張寫有用戶留言內容(如「這是我的留言」)的貼紙。
- 貼完後,系統自動記錄這張貼紙是屬於這塊留言板的,無需你再手動做任何事。
- 這段代碼做的事情就像是:
- 具體過程:
- 你不用先製作貼紙(創建
Comment
),再手動貼上(設置resume_id
),最後貼完了再記錄(save()
)。 create()
就是一步完成所有工作:- 製作貼紙、寫內容、貼上留言板,最後系統自動保存。
- 你不用先製作貼紙(創建
小結
透過本指南,我們完成了以下功能:
- 留言檢索與排序:
- 使用
ForeignKey
提供的反向關聯功能,檢索特定履歷的所有留言。 - 按
created_at
字段倒序排列留言,確保最新留言優先顯示。
- 使用
- 模板顯示留言:
- 修改
show.html
,顯示所有留言並提供新增留言的表單。
- 修改
- 代碼優化:
- 使用
comment_set.create
簡化留言創建代碼。
- 使用
這些操作展示了 Django ORM 的靈活性,幫助我們在最少代碼中實現強大的數據處理功能。
如果希望進一步提升性能,可以考慮使用分頁功能來處理大量留言的顯示。