使用 get_queryset 的 Django ListView 分页
Posted
技术标签:
【中文标题】使用 get_queryset 的 Django ListView 分页【英文标题】:Django ListView pagination using get_queryset 【发布时间】:2013-02-04 06:15:16 【问题描述】:我一直在尝试学习并掌握在 Django 中使用分页的 ListViews,但我似乎在理解概念方面遇到了一些麻烦。所以,让我展示一些代码 - 我的 view.py 看起来像这样:
class SearchDisplayListView(ListView):
model = BlogPosts
template_name = "searchres_list.html"
paginate_by = '15'
context_object_name = "searchres"
def get_context_data(self, **kwargs):
context = super(SearchDisplayListView, self).get_context_data(**kwargs)
q = self.request.GET.get('q')
q = q.replace(" ","+")
context['searchq'] = q
return context
def get_queryset(self):
queryset = super(SearchDisplayListView, self).get_queryset()
# Get the q GET parameter
q = self.request.GET.get('q')
q = q.replace(" ","+")
if q is None:
# Return the base queryset
return queryset
# Return a filtered queryset
## do some awesome calculations using "q" i.e send request to search server
## get back a list from the search server.. example: range(0,100)
queryset = range(0,100)
return queryset
如您所见,我实际上并没有使用模型填充我的查询集 - 而是从我自己的“搜索服务器”返回结果 - 例如在这个示例中 range(0,100)。
现在,我正在尝试在我的模板代码中使用以下内容:
<table class="table table-hover">
<tbody>
% for i in searchres %
<tr>
<td><img src="http://placehold.it/140x140" class="img-polaroid"></td>
<td>i</td>
</tr>
% endfor %
</tbody>
</table>
我的分页如下:
% if is_paginated %
<div class="pagination pagination-centered">
<ul>
% if page_obj.has_previous %
<li><a href="/search/?q=searchq/page= page_obj.previous_page_number ">Prev</a></li>
% endif %
% for i in paginator.page_range %
% if page_obj.has_next %
<li><a href="/search/?q=searchq/page= page_obj.number ">i</a></li>
% endif %
% endfor %
% if page_obj.has_next %
<li><a href="/search/?q=searchq/page= page_obj.next_page_number ">Next</a></li>
% endif %
</ul>
</div>
现在,我有以下问题:
[1] 虽然模板似乎对第一组进行了分页(即 0 到 14),但我无法在后续页面中看到其他值。所以,当我点击下一页时,我得到:
- http://mydata:8000/search/?q=myquery/page=2
当我再次点击下一页时,我得到:
- http://mydata:8000/search/?q=boots/page=2/page=2
这显然是错误的。我看不到如何生成:
- http://mydata:8000/search/?q=boots/page=3
[2]即使我手动设置:
- http://mydata:8000/search/?q=boots/page=3
我再次看到从 0 到 14 的值,而不是 page:3 中的逻辑集。
此外,这基本上告诉我,在每个页面上,get_queryset 似乎都在运行,生成前 0 到 14 个值 - 这不是我想要的。
如您所见,我在 get_queryset 中有自己的自定义数据集 - 而不是任何 DB 交互 - 所以,我不确定如何对这些自定义数据进行分页。
感谢您花时间阅读我相当长的帖子!
【问题讨论】:
【参考方案1】:查询参数必须用&而不是/
分隔/search/?q=searchq/page= page_obj.previous_page_number
应该是:
/search/?q=searchq&page= page_obj.previous_page_number
此外,最好使用 URL 解析器而不是硬编码 url,例如:
% url 'search' searchq page_obj.previous_page_number %
【讨论】:
这简直太棒了。我只是花了一天时间来解决这个问题!你知道这在文档中提到的地方吗?再次感谢 - 接受您的回答。 只是另一个简单的问题-如果我按照您的建议使用 url 解析器-如何设置与它对应的 urls.py?即如何将 searchq、page_obj.previous_page_number 传递给我的 urls.py? @JohnJ 关于查询参数,这就是 HTTP 的工作方式,它与 django 无关。对于 URL 解析器,实际上它不适用于查询参数,但至少在第一部分您可以使用它。 % url 'myaapp.views.search' %?q=searchq&page= page_obj.previous_page_number 将正常工作。在这种情况下,你不必在 urls.py 中做任何事情。更多细节在这里:docs.djangoproject.com/en/dev/ref/templates/builtins/… 谢谢你 - 我明白你的意思 - 我想我的大脑在寻找 & !【参考方案2】:只是为了记录,代码应该是:
% if is_paginated %
<div class="pagination pagination-centered">
<ul>
% if page_obj.has_previous %
<li><a href="/search/?q=searchq&page= page_obj.previous_page_number">Prev</a></li>
% endif %
% if page_obj.has_next %
% for i in paginator.page_range %
<li><a href="/search/?q=searchq&page= i ">i</a></li>
% endfor %
<li><a href="/search/?q=searchq&page= page_obj.next_page_number ">Next</a></li>
% endif %
</ul>
</div>
有两个变化: 1. page_obj.number 返回实际页面,因此它被递增变量 i 替换。 2. 为清楚起见,将 for 循环移到 page_obj.has_next 内。如果您这样做,则页码链接仅在比提问者预期的页面多的情况下才会显示。如果您仍然想展示它:只需将其移出即可。
【讨论】:
【参考方案3】:在研究了这个问题一段时间并从这个条目开始后,我最终得到了一个复杂但通用的可重用解决方案。
对于 OP 来说太过分了,但很多人似乎都有分页和搜索问题,所以这是我的 0.02 美元。
(对于 Django 来说有点新手,所以我确信还有一些需要改进的地方,但我现在能够很快地将搜索和分页结合起来。请忽略任何关于 'db' 或 'mdb' 的内容,这高度特定于我的应用程序,它经常在 Django 数据库之外执行原始 SQL。)
简介:
基本上,我认为我可以用一块石头杀死两只鸟。我处理过滤行集的原因是我从表单驱动搜索。
而且...表单能够提供重建分页 URL 所需的信息。
因此,基本上最终得到了一个系统,该系统的工作主要包括构建一个普通的搜索表单,然后将其与 ListView 的适当子类版本连接起来。请参阅类 RoleListView。
我还需要创建列表视图的模板,但如果你参考它,@ pssecurity/security_list.html 你会发现它非常基本。
详情:
关于分页的聪明之处在于类 KSearchListView(ListView)。这些东西是完全通用的,我可以在任意多个搜索页面上重复使用它。
方法get_queryset,通过调用form.doSearch 进行数据库过滤。
实际分页在方法 get_context_data 上,该方法检查是否存在表单、是否有效,然后通过使用表单的已清理参数重新填充它来操作分页 URL。
还请注意,有两个传入 URL,一个未过滤的列表,一个过滤的搜索)。两者都映射到同一个 ListView。
urls
#Roles aka PSROLEDEFN
url(r'^roles/search',
login_required(RoleListView.as_view()),
name="psroledefn_search"),
url(r'^roles/$',
# 'pssecurity.views.psroledefn_list',
login_required(RoleListView.as_view()),
name="psroledefn_list"),
#generic
class KSearchListView(ListView):
def __str__(self):
return self.__class__.__name__
__repr__ = __str__
form_class = form = None
di_hardcoded_context =
def setdb(self):
#specific to my app
self.db = self.rdb
def dispatch(self,*args,**kwargs):
#specific to my app
if not self.mdb:
self.mdb = MultiDb.get(self.kwargs["dbr"])
self.djangodb = self.mdb.djangodb
self.rdb = self.mdb.rdb
self.setdb()
#specific to my app
return super(KSearchListView, self).dispatch(*args,**kwargs)
def get_queryset(self,*args,**kwargs):
# logging.info("%s.get_queryset(%s,%s)" % (self,args,kwargs))
self.request.get = self.request.GET
if self.form_class:
#pagination info
#do we have a form and are we coming from it?
if self.request.method == "GET":
self.form = self.form_class(self.db, self.request.GET)
if self.form.is_valid():
logging.info("form.doSearch")
li_c = self.form.doSearch()
return li_c
else:
logging.debug("not is_valid branch")
else:
self.form = self.form_class(self.mdb.rdb)
#fetch all rows for the underlying table
return self.fetch_all()
def fetch_all(self):
#specific to my app
#you would probably use a <model>.objects.all()
return list(pssys.Pssys.fetch(self.db,self.recname))
def _override_context_data(self,context):
pass
def get_context_data(self,*args,**kwargs):
# logging.info("%s.get_context_data(%s,%s)" % (self,args,kwargs))
context = super(KSearchListView, self).get_context_data(**kwargs)
context['form'] = self.form
context["dbr"] = self.mdb.rdbname
#pagination info
#we are going to put the GET variables right back into the next/prev
url = ""
page_obj = context["page_obj"]
if self.form and self.form.is_valid() and self.form.cleaned_data:
li = [self.request.path,"?"]
#driving the url assembly from the form fields and
#using the cleaned data should be pretty safe
di = self.form.cleaned_data
for k in self.form.fields:
li.append("%s=%s" % (k,di[k]))
li.append("&")
# li = li[:-1]
url = "".join(li)
#if we didn't come in through a search form
if not url:
url = "?"
#now conditionally add the previous/next as appropriate.
#url has either a trailing ? or & at this point
kpaging_previous_url = kpaging_next_url = ""
if page_obj.has_previous():
kpaging_previous_url = context["kpaging_previous_url"] = url + "page=%s" % (page_obj.previous_page_number())
if page_obj.has_next():
kpaging_next_url = context["kpaging_next_url"] = url + "page=%s" % (page_obj.next_page_number())
logging.debug("url:%s" % (url))
logging.debug("kpaging_previous_url:%s" % (kpaging_previous_url))
logging.debug("kpaging_next_url:%s" % (kpaging_next_url))
#pickup anything the subclass has set for the context
context.update(self.di_hardcoded_context)
self._override_context_data(context)
return context
#what I need to write for every list/search page...
class RoleListView(KSearchListView):
template_name = "pssecurity/security_list.html"
paginate_by = 20
recname = "PSROLEDEFN"
form_class = Search_PSROLEDEFN
di_hardcoded_context = dict(
title="Search Roles",
header="Roles",
templatename_inst="PSROLEDEFN_list",
url_action='security:psroledefn_search')
#pagination info the forms
#generic
class SearchForm(forms.Form):
def __init__(self, db, request=None):
self.db = db
li_arg = [request] if request else []
super(forms.Form, self).__init__(*li_arg)
def __str__(self):
return self.__class__.__name__
__repr__ = __str__
#what I need to write for every list/search page...
class Search_PSROLEDEFN(SearchForm):
ROLENAME = forms.CharField(max_length=20, required=False)
DESCR = forms.CharField(max_length=32, required=False)
status_custom = ChooseStatusCustomizationField()
hasUsers = ChooseYesNoDontCareField("has Users?")
hasPermissions = ChooseYesNoDontCareField("has Permissions?")
hasPortalReferences = ChooseYesNoDontCareField("has Portal?")
def doSearch(self):
ROLENAME = self.cleaned_data["ROLENAME"]
DESCR = self.cleaned_data["DESCR"].strip()
status_custom = self.cleaned_data["status_custom"]
hasPortalReferences = self.cleaned_data["hasPortalReferences"]
hasPermissions = self.cleaned_data["hasPermissions"]
hasUsers = self.cleaned_data["hasUsers"]
#cut out a lot of code specific to my app
#you would want to do an appropriate
#<model>.objects.filter()...
returns <select from my db>
#a typical template, note that isn't even specific to an object
#refer to class RoleListView to see how the template is built.
#the actual details of the fetched objects are left to <templatename_inst>
pssecurity/security_list.html
% block search %
<div id="block_search">
<span>header</span>
<div class="row">
% if form %
<div id="search" class="well col-xs-9" >
<form class= "form-horizontal" action="% url url_action dbr=dbr %" method="get">
form.as_table
<input type="submit" value="search">
</form>
</div>
% endif %
% endblock %
%block content %
<div id = "block_content">
% for inst in object_list %
<div class="row">
% include templatename_inst %
</div>
% endfor %
% include "websec/kpagination.html" %
</div>
%endblock %
#generic
kpagination.html
<div class="pagination">
<span class="step-links" >
% if li_inst.has_previous %
<a href="?page= li_inst.previous_page_number ">previous</a>
% endif %
<span class="current" >
Page li_inst.number of li_inst.paginator.num_pages .
</span>
% if li_inst.has_next %
<a \href="?page= li_inst.next_page_number ">next</a>
% endif %
</span>
</div>
【讨论】:
【参考方案4】:我们可以在基于 django 类的 Listview 中进行如下操作
views.py
try:
from urllib import urlencode
except ImportError:
from urllib.parse import urlencode
class MyListView(ListView):
# ............
def get_context_data(self, *args, **kwargs):
context = super(
GroupSubsListView, self
).get_context_data(*args, **kwargs)
query_params = self.request.GET.copy()
query_params.pop('page', None)
context['query_params'] = urlencode(query_params)
return context
模板.html
<!-- pagination -->
% if is_paginated %
<p>showing page_obj.start_index to page_obj.end_index of page_obj.paginator.count </p>
<div class="text-center">
<nav>
<ul class="pagination">
% if page_obj.has_previous %
<li class="page-item">
<a class="page-link" href="?page= page_obj.previous_page_number & query_params " aria-label="Previous">
Previous
</a>
</li>
% endif %
<li class="page-item"><a class="page-link"> Page page_obj.number of page_obj.paginator.num_pages . </a></li>
% if page_obj.has_next %
<li class="page-item">
<a class="page-link" href="?page= page_obj.next_page_number & query_params " aria-label="Next">
Next
</a>
</li>
% endif %
</ul>
</nav>
</div>
% endif %
<!-- end/ -->
【讨论】:
以上是关于使用 get_queryset 的 Django ListView 分页的主要内容,如果未能解决你的问题,请参考以下文章
Django get_object ,get_queryset方法
Django ReadOnlyModelViewSet:get_querySet 被 pk 过滤
Django 自定义管理器 get_queryset() 不起作用
Django - get_queryset() 缺少 1 个必需的位置参数:“请求”