Wie paginiere ich Django mit anderen get-Variablen?

77

Ich habe Probleme mit der Paginierung in Django . Nehmen Sie die folgende URL als Beispiel:

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

Auf dieser Seite sortiere ich eine Liste der Benutzer nach ihrem Vornamen. Ohne eine sort GET-Variable wird standardmäßig nach id sortiert.

Wenn ich jetzt auf den nächsten Link klicke, erwarte ich die folgende URL:

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

Stattdessen verliere ich alle get Variablen und ende mit

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

Dies ist ein Problem, da die zweite Seite nach ID anstelle von Vorname sortiert ist.

Wenn ich request.get_full_path verwende, erhalte ich schließlich eine hässliche URL:

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

Was ist die Lösung? Gibt es eine Möglichkeit, auf die GET-Variablen in der Vorlage zuzugreifen und den Wert für die Seite zu ersetzen?

Ich verwende die Paginierung wie in der Dokumentation von Django beschrieben und bevorzuge es, sie weiterhin zu verwenden. Der von mir verwendete Vorlagencode ähnelt dem folgenden:

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

Antworten:

61

Ich fand die vorgeschlagenen benutzerdefinierten Tags zu komplex. Dies habe ich in der Vorlage getan:

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

Und die Tag-Funktion:

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

    dict_ = request.GET.copy()

    dict_[field] = value

    return dict_.urlencode()

Wenn der url_param noch nicht in der URL enthalten ist, wird er mit Wert hinzugefügt. Wenn es bereits vorhanden ist, wird es durch den neuen Wert ersetzt. Dies ist eine einfache Lösung, die zu mir passt, aber nicht funktioniert, wenn die URL mehrere Parameter mit demselben Namen enthält.

Sie müssen auch die RequestContext-Anforderungsinstanz aus Ihrer Sicht für Ihre Vorlage bereitstellen. Mehr Infos hier:

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

mpaf
quelle
4
Sie können sogar umgestalten, um nicht requestals Parameter übergeben zu müssen. Siehe diese Antwort stackoverflow.com/a/2160298/1272513
alfetopito
42

Ich denke, dass die url_replace- Lösung eleganter umgeschrieben werden kann als

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

mit Vorlagenstring vereinfacht zu

<a href="?{% url_replace page=paginator.next_page_number %}">
skoval00
quelle
4
Danke, das funktioniert! Verwenden Sie für Python 3 urllib.parse.urlencode(). Siehe diese Frage .
Arogachev
Und für Python 2.7 wäre es import urllibund return urllib.urlencode(query).
S_M
Damit dieses Tag funktioniert, sollte der django.template.context_processors.requestKontextprozessor aktiviert sein. Obwohl es standardmäßig aktiviert ist.
skoval00
1
Für GET-Parameter mit mehreren Werten für denselben Schlüssel ist es besser zu verwenden: query = context['request'].GET.copy()undreturn query.urlencode()
jakubste
6
Nachteile - es erstellt doppelte Parameter in URL wie &p=2&p=3&p=4
folgt
12

Nach einigem Herumspielen habe ich eine Lösung gefunden ... obwohl ich nicht weiß, ob es wirklich eine gute ist. Ich würde eine elegantere Lösung bevorzugen.

Auf jeden Fall übergebe ich die Anfrage an die Vorlage und kann über request.GET auf alle GET-Variablen zugreifen. Dann durchlaufe ich das GET-Wörterbuch und drucke es, solange die Variable keine Seite ist.

{% 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 %}
Vagabund
quelle
2
Dieser Ansatz funktioniert, weist jedoch einige Mängel auf: 1. Er verstößt gegen das DRY-Prinzip. Sie wiederholen Ihren Code. Wenn Sie also etwas daran ändern möchten, müssen Sie es an allen Stellen ändern, an die Sie es kopiert haben. 2. Es verstößt leicht gegen das Designmuster Model-View-Controller (oder Model-Template-View, wie Django Creator es nennt) - Vorlagen sollten nur zum Rendern von Daten verwendet werden. 3. Es führt dazu, dass rendundante / bedeutungslose GET-Parameter ständig weitergegeben werden - dies ist wahrscheinlich kein großes Problem, aber meiner Meinung nach ist es eleganter, solche Parameter herauszufiltern.
Tomasz Zieliński
2
Ergänzung zum vorherigen Kommentar: Wenn Sie darauf bestehen, dies in der Vorlage zu behandeln, sollten Sie meiner Meinung nach ein benutzerdefiniertes Vorlagen-Tag schreiben, das requestals Parameter verwendet wird, und dann Ihre Parameterzeichenfolge zurück in die Vorlage drucken.
Tomasz Zieliński
Dies scheint auch nicht mit Auswahlfeldern zu funktionieren, in denen Sie mehrere Optionen auswählen können.
Liam
Es verstößt nicht gegen das DRY-Prinzip, wenn Sie die Vorlagenvererbung für die Paginierung verwenden
Guillaume Lebreton
9

In Ihrem werden views.pySie irgendwie auf die Kriterien zugreifen, nach denen Sie sortieren, z first_name. Sie müssen diesen Wert an die Vorlage übergeben und dort einfügen, um sich daran zu erinnern.

Beispiel:

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

Man kann einen Kontextprozessor erstellen, um ihn überall dort zu verwenden, wo Paginierung angewendet wird.

Zum Beispiel in 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())}

Fügen Sie den Kontextprozessor zu Ihren Django-Projekteinstellungen hinzu:

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',
)

In Ihren Vorlagen können Sie dies dann beim Paginieren verwenden:

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

Unabhängig davon, welche GET-Variablen Sie in Ihrer Anfrage haben, werden sie nach dem ?page=GET-Parameter angehängt .

José L. Patiño
quelle
4

Verbesserung der dieser durch:

Verwenden Sie urlencodevon djangoanstelle von urllib, um UnicodeEncodeErrorFehler mit unicodeArgumenten zu vermeiden .

Vorlagen-Tag:

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)

Vorlage:

<!-- 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>
Omid Raha
quelle
4

Ich hatte dieses Problem bei der Verwendung von django-bootstrap3. Die (einfache) Lösung ohne Vorlagen-Tags verwendet:

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

Ich habe eine Weile gebraucht, um das herauszufinden ... Ich habe es endlich dank dieses Beitrags getan .

nspo
quelle
2

Hier ist ein nützliches benutzerdefiniertes Vorlagen-Tag zum Erstellen von Abfragezeichenfolgen.

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

Wenn die URL http://example.com/django/page/?search=sometext lautet , sollte der generierte HTML-Code wie folgt lauten:

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

Mehr Beispiele:

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

Zum Schluss der Quellcode (von mir geschrieben):

# -*- 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
Rockallite
quelle
2

@ skoval00 ‚s Antwort ist die am meisten elegant, es fügt jedoch doppelte &page=Abfrage - Parameter an die URL.

Hier ist das Update:

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}'
Elrond unterstützt Monica
quelle
2

Meine Lösung ist auf der Grundlage dieser oben mit der leichten Verbesserung eines entfernen &page=erscheint mehrmals. Siehe diesen Kommentar

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

Diese Zeile query.pop('page', None)entfernt die Seite stillschweigend aus der URL

Java_Man
quelle
1

Dies ist ein einfacher Weg, wie ich es mache

Im Hinblick auf :

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

Dann in Vorlage:

href="?page={{ objects.next_page_number }}&{{path}}"
Rüstung
quelle
1

Eine weitere Variante der url_encode-Lösung, in diesem Fall vereinfacht durch skoval00.

Ich hatte einige Probleme mit dieser Version. Erstens wurde die Unicode-Codierung nicht unterstützt, und zweitens wurden Filter mit mehreren gleichen Schlüsseln (wie bei einem MultipleSelect-Widget) nicht verwendet. Durch die Konvertierung von .dict () gehen alle Werte bis auf einen verloren. Meine Version unterstützt Unicode und mehrere gleiche Schlüssel:

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

Dadurch wird eine QueryDict-Kopie erstellt und anschließend alle Schlüssel entfernt, die mit kwargs übereinstimmen (da das Update für ein QueryDict hinzugefügt wird, anstatt es zu ersetzen). Mark_safe wurde aufgrund eines Problems mit der doppelten Codierung benötigt.

Sie würden es so verwenden (vergessen Sie nicht, die Tags zu laden):

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

Dabei ist? p = 1 unsere Paginierungssyntax in der Ansicht.

Apollo-Daten
quelle
Übrigens, praktisch, wenn Sie viele Ansichten mit Paginierung haben: Erstellen Sie eine generische Paginierungsvorlage. Dann können Sie das einfach in jede Ansicht {% include "core/pagination.html" with objects=ads_list %} einfügen, in die Sie paginieren möchten: Objekte ist der generische Name dessen, was Sie für die allgemeine Vorlage paginieren, und Sie können ihr den Namen zuweisen, der in dieser bestimmten Vorlage (in diesem Fall ads_list) aufgerufen wird.
Apollo Data
1

@ Elrond unterstützt Monica

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

In Vorlage verwenden

<a class="page-link" href="?{% url_replace p=1 q='bar'%}">
Anton Chasnyk
quelle
0

Jeder solche Link, den Sie in Ihre Ansicht einfügen, muss mit relevanten Parametern ausgestattet sein. Es gibt keine implizite Magie, die konvertieren würde:

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

in:

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

Was Sie also brauchen, ist ein SorterObjekt / eine Klasse / eine Funktion / ein Snippet (was auch immer hier passen könnte, ohne es zu übertreiben), das sich ähnlich wie django.core.paginator.Paginator verhält, aber den sortGET-Parameter verarbeitet.

Es könnte so einfach sein:

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 ''
})

Dann aus Ihrer Sicht:

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

Ich könnte allgemeiner gemacht werden, aber ich hoffe, Sie bekommen das Konzept.

Tomasz Zieliński
quelle
0

Ich würde sagen, generieren Sie den nächsten und vorherigen Link von Ihrem Controller, übergeben Sie ihn dann an die Ansicht und verwenden Sie ihn von dort aus. Ich werde Ihnen ein Beispiel geben (eher wie ein Pseudocode):

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

Verwenden Sie es dann aus Ihrer Sicht folgendermaßen:

{% if contacts.has_next %}
<a href="?page={{ contacts.next_link }}">next</a>
{% endif %}
sm13294
quelle
0

Sie müssen das GET wie oben angegeben zurücksenden. Sie können den GET-Anforderungsteil der URL durch Aufrufen übergeben

render_dict['GET'] = request.GET.urlencode(True)
return render_to_response('search/search.html',
                          render_dict,
                          context_instance=RequestContext(request))

Sie können dies dann in der Vorlage verwenden, um Ihre URL zu erstellen, z

href="/search/client/{{ page.no }}/10/?{{ GET }}
ajaali
quelle
0

Mit Djangos Paginierung ist es einfach, die GET-Parameter beizubehalten.

Kopieren Sie zuerst die GET-Parameter in eine Variable (in Ansicht):

GET_params = request.GET.copy()

und senden Sie es über das Kontextwörterbuch an die Vorlage:

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

Das zweite, was Sie tun müssen, ist es zu verwenden, es in den URL-Aufrufen (href) in der Vorlage anzugeben - ein Beispiel (Erweiterung des grundlegenden Paginierungs-HTML, um zusätzliche Parameterbedingungen zu behandeln):

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

Quelle

Nabeel Ahmed
quelle
0

Eine weitere geringfügige Änderung an skoval00 und Reinstate Monica , um Doppelarbeit vollständig zu beseitigen und den hässlichen ?&page=1Teil zu vermeiden :

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
artem343
quelle
-1

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


quelle
Dies schlägt fehl, wenn die Seite nicht das letzte Abrufelement ist.