使用 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 个必需的位置参数:“请求”

如何从 Django 中的 get_queryset 方法返回多个查询集对象或添加查询集结果

get_queryset中的Django 2.0 url参数