以下文字是 Django 类基于视图(CBV)探索系列的 最后一篇,介绍了如何通过深入使用 ListView
来实现如 分页、搜索、排序等在实际工作中常用的功能。如果您在前 7 篇中已查看了 CBV 的基础知识和 Mixin 的使用,那么本篇将综合这些知识,探讨如何优雅地使用 CBV 来处理实际网页开发中常见的需求。
您可以通过下面的链接查看前一篇文章!
类基于视图(CBV)探索系列 ⑦ - Mixin 的使用与权限管理
“通过扩展 Django ListView 实现强大的列表功能,提升用户体验!”
1. ListView,超出预期的可能性
ListView
是一个非常有用的通用视图,用于展示数据列表。但在实际网络应用中,除了简单地展示列表外,还需要实现 分页、有效管理 大量数据,快速查找所需信息的 搜索 ,以及根据用户偏好的 排序 功能。
在这一章中,我们将探讨如何扩展 ListView
,以优雅地实现这些实际的需求。
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 }}>下一页</a>
<a href="?page={{ paginator.num_pages }}>最后一页 »</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 的核心内容。
CBV 的主要优点:
- 代码重用性: 通过通用视图和 Mixin,减少重复代码,实现高效开发。
- 结构化代码: 类基础的代码更易于明确地分离和管理逻辑。
- 可扩展性: 通过继承和 Mixin,轻松扩展现有功能及添加新功能。
- 可维护性: 代码模块化,易于维护,减少变更对整体代码的影响。
- 提升开发生产力: 快速开发,利用 Django 提供的各种通用视图和 Mixin。
CBV 的局限性和考虑事项:
- 初学曲线: 可能需要对类基础结构有更深入的理解,相较于 FBV。
- 过度赋值: 过多的 Mixin 可能会导致代码流难以把握。适当地使用 Mixin 很重要。
- 简单逻辑可能过度设计: 在非常简单的页面或功能中,FBV 可能更简洁。根据情况选择适当的视图方式是明智的。
总的来说,Django CBV 是开发复杂多样功能的网页应用程序强大而实用的工具。如果理解并适当地运用 CBV 的优点,便能撰写更高效、易于维护的代码。
感谢您阅读类基于视图(CBV)探索系列!希望此系列能帮助您理解 Django CBV 并将其应用于实际项目中。
查看之前的文章
- 类基于视图(CBV)探索系列 #1 – 从 FBV 到 CBV 的原因及开发者心态
- 类基于视图(CBV)探索系列 #2 – 理解 Django 的基本视图类
- 类基于视图(CBV)探索系列 #3 – 利用 FormView 简化表单处理
- 类基于视图(CBV)探索系列 #4 – ListView & DetailView 的使用方法
- 类基于视图(CBV)探索系列 #5 – 使用 CreateView、UpdateView、DeleteView 实现 CRUD
- 类基于视图(CBV)探索系列 #6 – 使用 TemplateView & RedirectView
- 类基于视图(CBV)探索系列 #7 – Mixin 的使用与权限管理
“通过扩展 ListView 实现更加丰富而用户友好的列表功能,提升您的开发水平,通晓 CBV!”
目前没有评论。