【问题标题】:How to paginate Django with other get variables?如何用其他 get 变量对 Django 进行分页?
【发布时间】: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 %}

【问题讨论】:

    标签: django pagination


    【解决方案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/

    【讨论】:

    【解决方案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],

                                【讨论】:

                                • 如果页面不是最后一个获取项,则此操作失败。
                                猜你喜欢
                                • 1970-01-01
                                • 2016-08-28
                                • 1970-01-01
                                • 1970-01-01
                                • 2012-08-04
                                • 1970-01-01
                                • 1970-01-01
                                • 2020-05-24
                                • 1970-01-01
                                相关资源
                                最近更新 更多