如何用其他 get 变量对 Django 进行分页?

Posted

技术标签:

【中文标题】如何用其他 get 变量对 Django 进行分页?【英文标题】:How to paginate Django with other get variables? 【发布时间】:2011-01-04 02:14:40 【问题描述】:

我在Django 中使用分页时遇到问题。以以下网址为例:

http://127.0.0.1:8000/users/?sort=first_name

在此页面上,我按用户名对用户列表进行排序。如果没有排序 GET 变量,它默认按 id 排序。

现在,如果我点击下一个链接,我希望得到以下 URL:

http://127.0.0.1:8000/users/?sort=first_name&page=2

相反,我丢失了所有 get 变量并最终得到 ​​p>

http://127.0.0.1:8000/users/?page=2

这是一个问题,因为第二页是按 id 而不是 first_name 排序的。

如果我使用 request.get_full_path 我最终会得到一个丑陋的 URL:

http://127.0.0.1:8000/users/?sort=first_name&page=2&page=3&page=4

解决办法是什么?有没有办法访问模板上的 GET 变量并替换页面的值?

我正在使用Django's documentation 中描述的分页,我的偏好是继续使用它。我使用的模板代码是这样的:

% if contacts.has_next %
    <a href="?page= contacts.next_page_number ">next</a>
% endif %

【问题讨论】:

【参考方案1】:

我认为提出的自定义标签太复杂了,这是我在模板中所做的:

<a href="?% url_replace request 'page' paginator.next_page_number %">

及标签功能:

@register.simple_tag
def url_replace(request, field, value):

    dict_ = request.GET.copy()

    dict_[field] = value

    return dict_.urlencode()

如果 url_param 还没有在 url 中,它将被添加值。如果它已经存在,它将被新值替换。这是一个适合我的简单解决方案,但当 url 有多个同名参数时不起作用。

您还需要从您的视图中将 RequestContext 请求实例提供给您的模板。更多信息在这里:

http://lincolnloop.com/blog/2008/may/10/getting-requestcontext-your-templates/

【讨论】:

您甚至可以重构为不需要将request 作为参数传递。看到这个答案***.com/a/2160298/1272513【参考方案2】:

我认为 url_replace 解决方案可以更优雅地重写为

from urllib.parse import urlencode
from django import template

register = template.Library()

@register.simple_tag(takes_context=True)
def url_replace(context, **kwargs):
    query = context['request'].GET.copy()
    query.update(kwargs)
    return query.urlencode()

模板字符串简化为

<a href="?% url_replace page=paginator.next_page_number %">

【讨论】:

谢谢,这行得通!对于 Python 3,请使用 urllib.parse.urlencode()。见this question。 对于 Python 2.7,它将是 import urllibreturn urllib.urlencode(query) 对于同一个键具有多个值的 GET 参数,最好使用:query = context['request'].GET.copy()return query.urlencode() 缺点 - 它在 url 中创建重复参数,如下所示:&amp;p=2&amp;p=3&amp;p=4 移除多个 page=&page 值。简单添加: if query.get('page'): query.pop('page')【参考方案3】:

在玩了一些之后,我找到了一个解决方案......虽然我不知道它是否真的是一个好的解决方案。我更喜欢更优雅的解决方案。

无论如何,我将请求传递给模板,并且能够通过 request.GET 访问所有 GET 变量。然后我遍历 GET 字典,只要变量不是页面,我就打印它。

% if contacts.has_previous %
    <a href="?page= contacts.previous_page_number % for key,value in request.GET.items %% ifnotequal key 'page' %& key = value % endifnotequal %% endfor %">previous</a>
% endif %

<span class="current">
    Page  contacts.number  of  contacts.paginator.num_pages .
</span>

# I have all of this in one line in my code (like in the previous section), but I'm putting spaces here for readability.  #
% if contacts.has_next %
    <a href="?page= contacts.next_page_number 
        % for key,value in request.GET.items %
            % ifnotequal key 'page' %
                & key = value 
            % endifnotequal %
        % endfor %
    ">next</a>
% endif %

【讨论】:

这种方法有效,但有一些缺陷: 1. 它违反了 DRY 原则——你在重复你的代码,这意味着如果你想改变它,你必须改变它你复制到的所有地方。 2. 它稍微违反了模型-视图-控制器(或模型-模板-视图,正如 Django 创建者所说)设计模式 - 模板应该仅用于呈现数据。 3. 它会导致冗余/无意义的 GET 参数一直被传递 - 这可能不是一个大问题,但在我看来,过滤掉这些参数会更优雅。 对上一条评论的补充:如果您坚持在模板中处理这个,那么我认为您应该编写自定义模板标签,将request作为参数,然后将您的参数字符串打印回模板. 另外,这似乎不适用于可以选择多个选项的选择框。 分页使用模板继承不违反DRY原则【参考方案4】:

在您的views.py 中,您将以某种方式访问​​您排序的标准,例如first_name。您需要将该值传递给模板并将其插入其中以记住它。

例子:

% if contacts.has_next %
    <a href="?sort= criteria &page= contacts.next_page_number ">next</a>
% endif %

【讨论】:

【参考方案5】:

人们可以创建一个上下文处理器,以便在应用分页的任何地方使用它。

例如在my_project/my_app/context_processors.py:

def getvars(request):
    """
    Builds a GET variables string to be uses in template links like pagination
    when persistence of the GET vars is needed.
    """
    variables = request.GET.copy()

    if 'page' in variables:
        del variables['page']

    return 'getvars': '&0'.format(variables.urlencode())

将上下文处理器添加到您的 Django 项目设置中:

TEMPLATE_CONTEXT_PROCESSORS = (
    'django.contrib.auth.context_processors.auth',
    'django.contrib.messages.context_processors.messages',
    'django.core.context_processors.i18n',
    'django.core.context_processors.request',
    'django.core.context_processors.media',
    'django.core.context_processors.static',
     ...
    'my_project.my_app.context_processors.getvars',
)

然后,在您的模板中,您可以在分页时使用它:

<div class="row">
    # Initial/backward buttons #
    <div class="col-xs-4 col-md-4 text-left">
        <a href="?page=1 getvars " class="btn btn-rounded">% trans 'first' %</a>
        % if page_obj.has_previous %
            <a href="?page= page_obj.previous_page_number  getvars " class="btn btn-rounded">% trans 'previous' %</a>
        % endif %
    </div>

    # Page selection by number #
    <div class="col-xs-4 col-md-4 text-center content-pagination">
        % for page in page_obj.paginator.page_range %
            % ifequal page page_obj.number %
                <a class="active"> page </a>
            % else %
                <a href="?page= page  getvars "> page </a>
            % endifequal %
        % endfor %
    </div>

    # Final/forward buttons #
    <div class="col-xs-4 col-md-4 text-right">
        % if page_obj.has_next %
            <a href="?page= page_obj.next_page_number  getvars " class="btn btn-rounded">% trans 'next' %</a>
        % endif %
        <a href="?page= paginator.num_pages  getvars " class="btn btn-rounded">% trans 'last' %</a>
    </div>
</div>

无论您的请求中有什么 GET 变量,它们都将附加在 ?page= GET 参数之后。

【讨论】:

【参考方案6】:

this 的改进:

使用django 中的urlencode 而不是urllib,以防止unicode 参数出现UnicodeEncodeError 错误。

模板标签:

from django.utils.http import urlencode

@register.simple_tag(takes_context=True)
def url_replace(context, **kwargs):
    query = context['request'].GET.dict()
    query.update(kwargs)
    return urlencode(query)

模板:

<!-- Pagination -->
<div class="pagination">
 <span class="step-links">
   % if coupons.has_previous %
    <a href="?% url_replace page=objects.previous_page_number %">Prev</a>
   % endif %
   <span class="current">
    Page  objects.number  of  objects.paginator.num_pages 
   </span>
   % if objects.has_next %
    <a href="?% url_replace page=objects.next_page_number %">Next</a>
   % endif %
  </span>
</div>

【讨论】:

【参考方案7】:

我在使用 django-bootstrap3 时遇到了这个问题。没有任何模板标签的(简单)解决方案正在使用:

% bootstrap_pagination page_obj extra=request.GET.urlencode %

我花了一些时间才发现这一点......我终于感谢this post。

【讨论】:

【参考方案8】:

我的解决方案是基于this 上面的一个,稍有改进以删除&amp;page= 多次出现。看到这个comment

    @register.simple_tag(takes_context=True)
    def url_replace(context, **kwargs):
        query = context['request'].GET.copy()
        query.pop('page', None)
        query.update(kwargs)
        return query.urlencode()

这一行<strong>query.pop('page', None)</strong> 默默地从 url 中删除页面

【讨论】:

【参考方案9】:

@skoval00 的答案是优雅的,但是它在 url 中添加了重复的 &amp;page= 查询参数。

这里是修复:

from urllib.parse import urlencode
from django import template

register = template.Library()

@register.simple_tag(takes_context=True)
def url_replace(context, next_page):
    query = context['request'].GET.copy().urlencode()
    
    if '&page=' in query:
        url = query.rpartition('&page=')[0]
    else:
        url = query
    return f'url&page=next_page'

【讨论】:

我在@themissionmars 的评论中使用了this 解决方案。看起来更简单?【参考方案10】:

这是一个用于构造查询字符串的有用的自定义模板标签。

<a href="?% make_query_string page=obj_list.next_page_number %">Next page</a>

如果 URL 是http://example.com/django/page/?search=sometext,生成的 html 应该是这样的:

<a href="?search=sometext&page=2">Next page</a>

更多示例:

<!-- Original URL -->
<!-- http://example.com/django/page/?page=1&item=foo&item=bar -->

<!-- Add or replace arguments -->
% make_query_string page=2 item="foo2" size=10 %
<!-- Result: page=2&item=foo2&size=10 -->

<!-- Append arguments -->
% make_query_string item+="foo2" item+="bar2" %
<!-- Result: page=1&item=foo&item=bar&item=foo2&item=bar2 -->

<!-- Remove a specific argument -->
% make_query_string item-="foo" %
<!-- Result: page=1&item=bar -->

<!-- Remove all arguments with a specific name -->
% make_query_string item= %
<!-- Result: page=1 -->

最后是源代码(我写的):

# -*- coding: utf-8 -*-
from django import template
from django.utils.encoding import force_text  # Django 1.5+ only

register = template.Library()


class QueryStringNode(template.Node):
    def __init__(self, tag_name, parsed_args, var_name=None, silent=False):
        self.tag_name = tag_name
        self.parsed_args = parsed_args
        self.var_name = var_name
        self.silent = silent

    def render(self, context):
        # django.core.context_processors.request should be enabled in
        # settings.TEMPLATE_CONTEXT_PROCESSORS.
        # Or else, directly pass the HttpRequest object as 'request' in context.
        query_dict = context['request'].GET.copy()
        for op, key, value in self.parsed_args:
            if op == '+':
                query_dict.appendlist(key, value.resolve(context))
            elif op == '-':
                list_ = query_dict.getlist(key)
                value_ = value.resolve(context)
                try:
                    list_.remove(value_)
                except ValueError:
                    # Value not found
                    if not isinstance(value_, basestring):
                        # Try to convert it to unicode, and try again
                        try:
                            list_.remove(force_text(value_))
                        except ValueError:
                            pass
            elif op == 'd':
                try:
                    del query_dict[key]
                except KeyError:
                    pass
            else:
                query_dict[key] = value.resolve(context)
        query_string = query_dict.urlencode()
        if self.var_name:
            context[self.var_name] = query_string
        if self.silent:
            return ''
        return query_string


@register.tag
def make_query_string(parser, token):
    # % make_query_string page=1 size= item+="foo" item-="bar" as foo [silent] %
    args = token.split_contents()
    tag_name = args[0]
    as_form = False
    if len(args) > 3 and args[-3] == "as":
        # % x_make_query_string ... as foo silent % case.
        if args[-1] != "silent":
            raise template.TemplateSyntaxError(
                "Only 'silent' flag is allowed after %s's name, not '%s'." %
                (tag_name, args[-1]))
        as_form = True
        silent = True
        args = args[:-1]
    elif len(args) > 2 and args[-2] == "as":
        # % x_make_query_string ... as foo % case.
        as_form = True
        silent = False

    if as_form:
        var_name = args[-1]
        raw_pairs = args[1:-2]
    else:
        raw_pairs = args[1:]

    parsed_args = []
    for pair in raw_pairs:
        try:
            arg, raw_value = pair.split('=', 1)
        except ValueError:
            raise template.TemplateSyntaxError(
                "%r tag's argument should be in format foo=bar" % tag_name)
        operator = arg[-1]
        if operator == '+':
            # item+="foo": Append to current query arguments.
            # e.g. item=1 -> item=1&item=foo
            parsed_args.append(('+', arg[:-1], parser.compile_filter(raw_value)))
        elif operator == '-':
            # item-="bar": Remove from current query arguments.
            # e.g. item=1&item=bar -> item=1
            parsed_args.append(('-', arg[:-1], parser.compile_filter(raw_value)))
        elif raw_value == '':
            # item=: Completely remove from current query arguments.
            # e.g. item=1&item=2 -> ''
            parsed_args.append(('d', arg, None))
        else:
            # item=1: Replace current query arguments, e.g. item=2 -> item=1
            parsed_args.append(('', arg, parser.compile_filter(raw_value)))

    if as_form:
        node = QueryStringNode(tag_name, parsed_args,
                               var_name=var_name, silent=silent)
    else:
        node = QueryStringNode(tag_name, parsed_args)

    return node

【讨论】:

【参考方案11】:

这是一个简单的方法

在视图中:

path = ''
path += "%s" % "&".join(["%s=%s" % (key, value) for (key, value) in request.GET.items() if not key=='page' ])

然后在模板中:

href="?page= objects.next_page_number &path"

【讨论】:

【参考方案12】:

skoval00恢复 Monica 的另一个细微修改,以完全消除重复并避免丑陋的 ?&amp;page=1 部分:

from urllib.parse import urlencode
from django import template

register = template.Library()

@register.simple_tag(takes_context=True)
def url_replace(context, next_page):
    if query.startswith('page') or not len(query):
        new_url = f'page=next_page'
    elif '&page=' in query:
        get_params = query.rpartition('&page=')[0] # equivalent to .split('page='), except more efficient 
        new_url = f'get_params&page=next_page'
    else:
        new_url = f'query&page=next_page'
    return new_url

【讨论】:

【参考方案13】:

对 url_encode 解决方案的另一种看法,在本例中由 skoval00 简化。

我对那个版本有一些问题。一,它不支持 Unicode 编码,二,它破坏了具有多个相同键的过滤器(如 MultipleSelect 小部件)。由于 .dict() 转换,除了一个之外的所有值都将丢失。我的版本支持unicode和多个同一个key:

from django import template
from django.utils.html import mark_safe

register = template.Library()

@register.simple_tag(takes_context=True)
def url_replace(context, **kwargs):
    query = context['request'].GET.copy()

    for kwarg in kwargs:
        try:
            query.pop(kwarg)
        except KeyError:
            pass

    query.update(kwargs)

    return mark_safe(query.urlencode())

这将创建一个 QueryDict 副本,然后删除与 kwargs 匹配的所有键(因为 QueryDict 的更新添加而不是替换)。由于双重编码问题,需要 Mark_safe。

你会这样使用它(不要忘记加载标签):

<a class="next" href="?% url_replace p=objects.next_page_number%">Next</a>

其中 ?p=1 是我们在视图中的分页语法。

【讨论】:

顺便说一句,如果你有很多关于分页的视图,那么实用的一边:制作一个通用的分页模板。然后,您可以在要分页的每个视图中包含它:% include "core/pagination.html" with objects=ads_list % objects 是您为通用模板分页的任何内容的通用名称,您可以为其分配在此特定模板中调用的任何内容(ads_list,在此案例)。【参考方案14】:

@Elrond 支持莫妮卡

@register.simple_tag(takes_context=True)
def url_replace(context, **kwargs):
    query = context['request'].GET.copy()
    for key in kwargs:
        query[key] = kwargs[key]
    return query.urlencode()

在模板中使用

<a class="page-link" href="?% url_replace p=1 q='bar'%">

【讨论】:

【参考方案15】:

您在视图中放置的每个此类链接都必须配备相关参数。没有隐含的魔法可以转换:

http://127.0.0.1:8000/users/?page=2

进入:

http://127.0.0.1:8000/users/?sort=first_name&page=2

所以你需要的是一些Sorter object/class/function/sn-p(任何可能适合这里的东西而不会过度使用),它的作用类似于 django.core.paginator.Paginator,但会处理 sort GET 参数。

可以这么简单:

sort_order = request.GET.get('sort', 'default-criteria')

<paginate, sort>

return render_to_response('view.html', 
    'paginated_contacts': paginated_contacts,  # Paginator stuff
    'sort_order': sort_order if sort_oder != 'default-criteria' else ''
)

那么,在你看来:

% if contacts.has_next %
    <a href="?page= contacts.next_page_number %if sort_order%&sort=sort_oder%endif%">next</a>
% endif %

我可以变得更通用,但我希望你明白这个概念。

【讨论】:

【参考方案16】:

我会说从您的控制器生成下一个和上一个链接,然后将其传递给视图并从那里使用它。我给你举个例子(更像是伪代码):

("next_link", "?param1="+param1+"&param2="+param2+"&page_nr="+(Integer.parseInt(page_nr)-1)

然后在你看来像这样使用它:

% if contacts.has_next %
<a href="?page= contacts.next_link ">next</a>
% endif %

【讨论】:

【参考方案17】:

您需要按上述方式返回 GET。您可以通过调用

来传递 url 的 GET 请求部分
render_dict['GET'] = request.GET.urlencode(True)
return render_to_response('search/search.html',
                          render_dict,
                          context_instance=RequestContext(request))

然后您可以在模板中使用它来构建您的 URL,例如

href="/search/client/ page.no /10/? GET 

【讨论】:

【参考方案18】:

使用 Django 的分页 - 保存 GET 参数很简单。

首先将 GET 参数复制到一个变量(在视图中):

GET_params = request.GET.copy()

并通过上下文字典将其发送到模板:

return render_to_response(template,
                        'request': request, 'contact': contact, 'GET_params':GET_params, context_instance=RequestContext(request))

您需要做的第二件事是使用它,在模板中的 url 调用 (href) 中指定它 - 一个示例(扩展基本分页 html 以处理额外的参数条件):

% if contacts.has_next %
    % if GET_params %
        <a href="?GET_params.urlencode&amp;page= contacts.next_page_number ">next</a>
    % else %
        <a href="?page= contacts.next_page_number ">next</a>
    % endif %
% endif %

Source

【讨论】:

【参考方案19】:

你的代码应该是这样的:

% if contacts.has_next %
<a href="?page= contacts.next_page_number % for key,value in request.GET.items %% ifnotequal key 'page' %& key = value % endifnotequal %% endfor %">next</a>
% endif %

【讨论】:

【参考方案20】:

'路径':request.get_full_path().rsplit('&page')[0],

【讨论】:

如果页面不是最后一个获取项,则此操作失败。

以上是关于如何用其他 get 变量对 Django 进行分页?的主要内容,如果未能解决你的问题,请参考以下文章

如何用sql语句 实现分页查询

如何用mybatis进行分页?

如何用JMeter对MySQL数据库进行压测

如何用ajax显示页面?

Django模板如何用一个变量查找字典值

如何用自定义变量替换 DJANGO 中 JAVASCRIPT 脚本生成的 Google Map Key