以下的文章是 Django 類別基礎視圖(CBV)探索系列的 最後一篇,主要介紹如何利用 ListView
深入應用 分頁、搜尋、排序 等在實務中常用的功能。若您已經閱讀過前七篇文章,了解 CBV 的基礎及 Mixin 的運用,本篇將綜合這些內容,探討如何以 CBV 優雅地處理實際網頁開發中的一般需求。
前一篇直接連結,請點擊以下鏈接!
類別基礎視圖(CBV)探索系列 ⑦ - Mixin 的利用及權限管理
“擴展 Django ListView,實現強大的列表功能,提升
使用者體驗!”
1. ListView,更深化的可能性
ListView
是一個非常有用的通用視圖,能夠顯示數據列表。然而在實際網頁應用中,僅僅顯示列表是不夠的,很多數據 的有效管理需要 分頁、快速找到所需信息的 搜尋,以及基於使用者偏好的 排序 功能,這些都是必不可少的。
本章將探討如何擴展 ListView
,以便簡單而優雅地實現這些實務上的需求,充分發揮 CBV 的優勢。
2. 應用分頁(Pagination)
一次顯示大量數據會影響使用者體驗。分頁 通過將數據分為多個頁面來減輕使用者的加載負擔,幫助他們更輕鬆地找到所需的信息。
2.1 基本分頁設定
ListView
提供了內建的分頁功能。只需在視圖類中設置 paginate_by
屬性即可。
views.py 範例
from django.views.generic import ListView
from .models import Article
class ArticleListView(ListView):
model = Article
template_name = 'articles/article_list.html'
context_object_name = 'articles'
paginate_by = 10 # 每頁顯示 10 個 Article 對象
paginate_by = 10
: 這樣設置後,ListView
會自動將結果劃分為每頁 10 個,並將與分頁相關的對象(paginator
、page_obj
、is_paginated
)傳遞給模板上下文。
2.2 在模板中實現分頁 UI
在模板中,可以使用傳遞的 page_obj
顯示頁碼鏈接,並實現前一頁/下一頁按鈕等功能。
articles/article_list.html 範例
<ul>
{% for article in articles %}
<li>{{ article.title }} - {{ article.created_at }}</li>
{% empty %}
<li>尚未有發布的文章。</li>
{% endfor %}
</ul>
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
<a href="?page=1">« 首頁</a>
<a href="?page={{ page_obj.previous_page_number }}">前一頁</a>
{% endif %}
<span class="current">
頁面 {{ page_obj.number }} / {{ paginator.num_pages }}
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}&search_term={{ search_form.search_term.value|default:'' }}">下一頁</a>
<a href="?page={{ paginator.num_pages }}&search_term={{ search_form.search_term.value|default:'' }}">最後一頁 »</a>
{% endif %}
</span>
</div>
page_obj
: 包含當前頁面數據的對象,提供has_previous
、has_next
、previous_page_number
、next_page_number
、number
等屬性。paginator
: 包含整體頁面相關信息的對象,提供num_pages
(總頁數)等屬性。
2.3 保持 URL 連接
當生成分頁鏈接時,可以看到使用了 ?page=2
的 page
查詢參數。ListView
會自動識別此參數,以顯示該頁面的數據。
3. 添加搜尋(Search)功能
在網站上快速找到所需的信息非常重要。我們將擴展 ListView
來實現搜尋功能。
3.1 定義表單(Form)
首先,定義一個簡單的表單來接收搜尋關鍵字。
forms.py 範例
from django import forms
class ArticleSearchForm(forms.Form):
search_term = forms.CharField(label='搜尋關鍵字', required=False)
3.2 修改 ListView
重寫 ListView
的 get_queryset()
方法來實現搜尋功能。
views.py 範例
from django.views.generic import ListView
from django.db.models import Q
from .models import Article
from .forms import ArticleSearchForm
class ArticleListView(ListView):
model = Article
template_name = 'articles/article_list.html'
context_object_name = 'articles'
paginate_by = 10
def get_queryset(self):
queryset = super().get_queryset()
self.form = ArticleSearchForm(self.request.GET) # 從 GET 請求中獲取表單數據
if self.form.is_valid():
search_term = self.form.cleaned_data.get('search_term')
if search_term:
# 過濾標題或內容中包含搜尋詞的 Article
queryset = queryset.filter(
Q(title__icontains=search_term) | Q(content__icontains=search_term)
)
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['search_form'] = self.form # 傳遞表單到模板
return context
get_queryset()
: 重寫此方法以修改基本查詢集。- 建立表單實例,若通過有效性檢查,則從
cleaned_data
中獲取搜尋關鍵字。 - 使用
Q 物件
過濾標題(title
)或內容(content
)中包含搜尋關鍵字的Article
對象(__icontains
用於不區分大小寫的包含檢查)。 get_context_data()
: 將表單實例添加到上下文中,以便呈現在模板中。
3.3 在模板中顯示搜尋表單並保持結果
在模板中添加搜尋表單,並構建 URL 以保留搜尋結果,使其可以進行分頁。
articles/article_list.html
<form method="get">
{{ search_form }}
<button type="submit">搜尋</button>
</form>
<ul>
{% for article in articles %}
<li>{{ article.title }} - {{ article.created_at }}</li>
{% empty %}
<li>沒有搜尋結果。</li>
{% endfor %}
</ul>
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
<a href="?page=1&search_term={{ search_form.search_term.value|default:'' }}">« 首頁</a>
<a href="?page={{ page_obj.previous_page_number }}&search_term={{ search_form.search_term.value|default:'' }}">前一頁</a>
{% endif %}
<span class="current">
頁面 {{ page_obj.number }} / {{ paginator.num_pages }}
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}&search_term={{ search_form.search_term.value|default:'' }}">下一頁</a>
<a href="?page={{ paginator.num_pages }}&search_term={{ search_form.search_term.value|default:'' }}">最後一頁 »</a>
{% endif %}
</span>
</div>
- 將表單的
method
設定為get
,以將搜尋關鍵字作為 URL 查詢參數傳遞。 - 在生成頁面鏈接時,同時包含當前搜尋關鍵字(
search_form.search_term.value
)作為 URL 參數,以保持搜尋結果的分頁。使用|default:''
過濾器來處理無搜尋字串的情況。
4. 添加排序(Ordering)功能
為使用者提供按照所需標準進行排序的功能。
4.1 添加排序選擇表單(可選)
可以添加一個下拉表單來選擇排序標準,也可以簡單地透過 URL 參數處理。在此,我們將說明使用 URL 參數的方法。
4.2 修改 ListView
修改 get_queryset()
方法以應用 order_by()
。
views.py 範例(包含搜尋功能)
from django.views.generic import ListView
from django.db.models import Q
from .models import Article
from .forms import ArticleSearchForm
class ArticleListView(ListView):
model = Article
template_name = 'articles/article_list.html'
context_object_name = 'articles'
paginate_by = 10
def get_queryset(self):
queryset = super().get_queryset()
self.form = ArticleSearchForm(self.request.GET)
ordering = self.request.GET.get('ordering', '-created_at') # 預設排序:最新優先
if self.form.is_valid():
search_term = self.form.cleaned_data.get('search_term')
if search_term:
queryset = queryset.filter(
Q(title__icontains=search_term) | Q(content__icontains=search_term)
)
queryset = queryset.order_by(ordering)
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['search_form'] = self.form
context['ordering'] = self.request.GET.get('ordering', '-created_at') # 將當前排序標準傳遞到模板
return context
self.request.GET.get('ordering', '-created_at')
: 從 URL 查詢參數中獲取ordering
的值。如果沒有值,預設為-created_at
(最新優先)。queryset = queryset.order_by(ordering)
: 使用獲取的ordering
值來排序查詢集。如果模型字段名稱前加上-
,會按相反順序排序。
4.3 在模板中提供排序鏈接
在模板中提供可以按每個字段進行排序的鏈接。
articles/article_list.html 範例
<form method="get">
{{ search_form }}
<button type="submit">搜尋</button>
</form>
<table>
<thead>
<tr>
<th><a href="?ordering=title&search_term={{ search_form.search_term.value|default:'' }}">標題</a></th>
<th><a href="?ordering=-created_at&search_term={{ search_form.search_term.value|default:'' }}">創建日期</a></th>
</tr>
</thead>
<tbody>
{% for article in articles %}
<tr>
<td>{{ article.title }}</td>
<td>{{ article.created_at }}</td>
</tr>
{% empty %}
<tr><td colspan="2">沒有搜尋結果。</td></tr>
{% endfor %}
</tbody>
</table>
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
<a href="?page=1&search_term={{ search_form.search_term.value|default:'' }}&ordering={{ ordering }}">« 首頁</a>
<a href="?page={{ page_obj.previous_page_number }}&search_term={{ search_form.search_term.value|default:'' }}&ordering={{ ordering }}">前一頁</a>
{% endif %}
<span class="current">
頁面 {{ page_obj.number }} / {{ paginator.num_pages }}
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}&search_term={{ search_form.search_term.value|default:'' }}&ordering={{ ordering }}">下一頁</a>
<a href="?page={{ paginator.num_pages }}&search_term={{ search_form.search_term.value|default:'' }}&ordering={{ ordering }}">最後一頁 »</a>
{% endif %}
</span>
</div>
- 在每個表格頭部提供包含排序標準(
ordering
)的鏈接。 - 在生成頁面鏈接時,除了當前搜尋關鍵字,還要包含當前排序標準(
ordering
),以便在保持排序狀態的同時進行分頁。
5. 使用 Mixin 結合功能
利用之前學習的 Mixin,我們可以更簡潔地管理分頁、搜尋和排序功能。例如,可以將搜尋功能分離為 Mixin。
mixins.py 範例
from django.db.models import Q
from .forms import ArticleSearchForm
class ArticleSearchMixin:
def get_queryset(self):
queryset = super().get_queryset()
self.form = ArticleSearchForm(self.request.GET)
if self.form.is_valid():
search_term = self.form.cleaned_data.get('search_term')
if search_term:
queryset = queryset.filter(
Q(title__icontains=search_term) | Q(content__icontains=search_term)
)
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['search_form'] = getattr(self, 'form', ArticleSearchForm()) # 若無表單則使用默認表單
return context
views.py 範例(應用 Mixin)
from django.views.generic import ListView
from .models import Article
from .mixins import ArticleSearchMixin
class ArticleListView(ArticleSearchMixin, ListView):
model = Article
template_name = 'articles/article_list.html'
context_object_name = 'articles'
paginate_by = 10
def get_queryset(self):
queryset = super().get_queryset()
ordering = self.request.GET.get('ordering', '-created_at')
queryset = queryset.order_by(ordering)
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['ordering'] = self.request.GET.get('ordering', '-created_at')
return context
ArticleSearchMixin
被創建以分離與搜尋相關的邏輯。ArticleListView
繼承自ArticleSearchMixin
和ListView
,因此擁有搜尋與列表功能。
6. CBV 的優勢與限制(系列結尾)
通過 8 篇 CBV 探索系列,我們瞭解了為何從函數基礎視圖(FBV)轉換到類別基礎視圖(CBV)、CBV 的基本結構與應用、各種通用視圖的使用方法,以及如何利用 Mixin 進行功能擴展等核心內容。
CBV 的主要優勢:
- 代碼重用性: 通過通用視圖和 Mixin 來減少重複代碼,提高開發效率。
- 結構化代碼: 類別基礎的代碼能清晰地分離和管理邏輯。
- 擴展性: 通過繼承和 Mixin,輕鬆擴展現有功能並添加新功能。
- 維護性: 模組化的代碼使維護更簡單,並減少變更對整體代碼的影響。
- 提高開發效率: 利用 Django 提供的各種通用視圖和 Mixin,可以快速開發。
CBV 的限制與考量:
- 初學者曲線: 相比 FBV 需要對類別基礎結構有一定的理解。
- 過度繼承: 使用過多 Mixin 可能使代碼流程難以理解,適度使用 Mixin 是重要的。
- 對簡單邏輯的過度設計: 對於非常簡單的頁面或功能,FBV 可能更簡潔。在不同情況下選擇合適的視圖方式是明智的。
總的來說,Django CBV 是開發複雜且功能多樣的網頁應用程式非常強大而有用的工具。理解並適當利用 CBV 的優勢,將使我們能夠編寫更高效、維護性更好的代碼。
非常感謝您閱讀類別基礎視圖(CBV)探索系列!希望此系列能在理解 Django CBV 及在實際專案中應用上對您有所幫助。
重新查看前一篇文章
- 類別基礎視圖(CBV)探索系列 #1 – FBV 到 CBV 的轉變理由及開發者的心態
- 類別基礎視圖(CBV)探索系列 #2 – 理解 Django 的基本 View 類別
- 類別基礎視圖(CBV)探索系列 #3 – 使用 FormView 簡化表單處理
- 類別基礎視圖(CBV)探索系列 #4 – ListView & DetailView 的使用方法
- 類別基礎視圖(CBV)探索系列 #5 – 使用 CreateView、UpdateView 和 DeleteView 實現 CRUD
- 類別基礎視圖(CBV)探索系列 #6 – 使用 TemplateView & RedirectView
- 類別基礎視圖(CBV)探索系列 #7 – Mixin 的利用及權限管理
“通過擴展 ListView 實現更豐富且使用者友好的列表功能,
精通 CBV,讓你的 Django 開發技術更上一步!”
目前沒有評論。