如何用其他 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 urllib
和 return urllib.urlencode(query)
。
对于同一个键具有多个值的 GET 参数,最好使用:query = context['request'].GET.copy()
和 return query.urlencode()
缺点 - 它在 url 中创建重复参数,如下所示:&p=2&p=3&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 上面的一个,稍有改进以删除&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 中添加了重复的 &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 的另一个细微修改,以完全消除重复并避免丑陋的 ?&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+"¶m2="+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&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 进行分页?的主要内容,如果未能解决你的问题,请参考以下文章