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


77

我在Django中使用分页时遇到问题。以下面的URL为例:

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

相反,我失去了所有的获取变量并最终得到

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文档的说明使用分页,我的首选是继续使用它。我使用的模板代码与此类似:

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

Answers:


61

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

<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/


4
您甚至可以重构为不需要request作为参数传递。看到这个答案stackoverflow.com/a/2160298/1272513
alfetopito

42

我认为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 %}">

4
谢谢,这有效!对于Python 3,请使用urllib.parse.urlencode()。看到这个问题
arogachev

对于Python 2.7,它将是import urllibreturn urllib.urlencode(query)
S_M

为了使该标签起作用,django.template.context_processors.request应该启用上下文处理器。虽然默认情况下启用。
skoval00

1
对于具有相同键的多个值的GET参数,最好使用:query = context['request'].GET.copy()return query.urlencode()
jakubste

6
缺点-它将在网址中创建重复的参数,如下所示:&p=2&p=3&p=4
Shaig Khaligli

12

经过一番游戏后,我找到了解决方案……尽管我不知道这是否真的是一个好方法。我希望有一个更优雅的解决方案。

无论如何,我将请求传递到模板,并能够通过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 %}

2
这种方法有效,但有一些缺陷:1.它违反了DRY原理-您在重复代码,这意味着如果要更改其中的某些内容,则必须在将其复制到的所有位置进行更改。2.它略微违反了Model-View-Controller(或Django创建者称为Model-Template-View)的设计模式-模板应仅用于呈现数据。3.它导致无休止地传递多余/毫无意义的GET参数-这可能不是一个大问题,但我认为过滤掉此类参数更为优雅。
TomaszZieliński,2010年

2
对先前评论的补充:如果您坚持要在模板中处理此问题,那么我认为您应该编写将request用作参数的自定义模板标签,然后将参数字符串打印回模板。
TomaszZieliński,2010年

另外,这似乎不适用于您可以选择多个选项的选择框。
利亚姆

如果您使用模板继承进行分页,则不会违反DRY原则
Guillaume Lebreton

9

在你的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参数之后。


4

通过以下方式对此进行改进:

使用urlencodefromdjango代替urllib,以防止参数UnicodeEncodeError出错unicode

模板标签:

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>

4

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

{% bootstrap_pagination page_obj extra=request.GET.urlencode %}

花了我一段时间才找到答案...我终于对这篇文章表示感谢。


2

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

<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

2

@ skoval00的答案是优雅的,但是它&page=向URL添加了重复的查询参数。

解决方法是:

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] # equivalent to .split('page='), except more efficient 
    else:
        url = query
    return f'{url}&page={next_page}'

2

我的解决方案是基于一个以上的轻微改善,除去&page=出现多次。查看此评论

    @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()

此行将query.pop('page', None)页面从url中静默删除


1

这是我的简单方法

鉴于:

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}}"

1

url_encode解决方案的另一种做法,在这种情况下,由skoval00简化。

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

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 %} 对象: object是要为常规模板进行分页的任何对象的通用名称,并且您可以为该模板分配在此特定模板(本例中为ads_list)中调用的任何名称。
Apollo

1

@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'%}">

0

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

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

变成:

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

所以你需要一些 Sorter对象/类/函数/代码段(无论在什么地方适合而不会过度使用),其作用类似于django.core.paginator.Paginator,但可以处理sortGET参数。

它可以像这样简单:

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 %}

我可以更一般一些,但希望您能理解。


0

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

("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 %}

0

您将需要如上所述返回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 }}

0

使用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 %}

资源


0

skoval00Reinstate Monica的另一项轻微修改,以完全消除重复并避免难看的?&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

-1

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


如果页面不是最后的获取项目,失败。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.