web-dev-qa-db-fra.com

Comment paginer Django avec d'autres variables get?

J'ai des problèmes avec la pagination dans Django . Prenez l'URL ci-dessous comme exemple:

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

Sur cette page, je trie une liste d'utilisateurs par leur prénom. Sans variable de tri GET, le tri par défaut est effectué par id.

Maintenant, si je clique sur le lien suivant, j'attends l'URL suivante:

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

Au lieu de cela, je perds toutes les variables get et je me retrouve avec

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

Il s'agit d'un problème car la deuxième page est triée par id au lieu de prénom.

Si j'utilise request.get_full_path, je finirai par avoir une URL moche:

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

Quelle est la solution? Existe-t-il un moyen d'accéder aux variables GET sur le modèle et de remplacer la valeur de la page?

J'utilise la pagination comme décrit dans documentation de Django et ma préférence est de continuer à l'utiliser. Le code modèle que j'utilise est similaire à ceci:

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

Je pensais que les tags personnalisés proposés étaient trop complexes, c'est ce que j'ai fait dans le modèle:

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

Et la fonction de balise:

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

    dict_ = request.GET.copy()

    dict_[field] = value

    return dict_.urlencode()

Si le paramètre url_param n'est pas encore dans l'url, il sera ajouté avec une valeur. S'il est déjà là, il sera remplacé par la nouvelle valeur. Il s'agit d'une solution simple qui me convient, mais ne fonctionne pas lorsque l'URL a plusieurs paramètres avec le même nom.

Vous avez également besoin que l'instance de demande RequestContext soit fournie à votre modèle à partir de votre vue. Plus d'infos ici:

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

54
mpaf

Je pense que rl_replace la solution peut être réécrite plus élégamment

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

avec chaîne de modèle simplifiée en

<a href="?{% url_replace page=paginator.next_page_number %}">
33
skoval00

Après avoir joué, j'ai trouvé une solution ... même si je ne sais pas si c'est vraiment une bonne solution. Je préférerais une solution plus élégante.

Quoi qu'il en soit, je passe la demande au modèle et suis en mesure d'accéder à toutes les variables GET via request.GET. Ensuite, je fais une boucle dans le dictionnaire GET et tant que la variable n'est pas la page, je l'imprime.

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

Dans votre views.py vous accéderez en quelque sorte aux critères de tri, par exemple first_name. Vous devrez transmettre cette valeur au modèle et l'insérer là pour vous en souvenir.

Exemple:

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

J'ai eu ce problème lors de l'utilisation de Django-bootstrap3. La solution (facile) sans balises de modèle utilise:

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

Cela m'a pris du temps pour le découvrir ... Je l'ai finalement fait grâce à ce post .

4
Kopfgeldjaeger

On peut créer un processeur de contexte pour l'utiliser partout où la pagination est appliquée.

Par exemple, dans 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())}

Ajoutez le processeur de contexte à vos paramètres de projet 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',
)

Ensuite, dans vos modèles, vous pouvez l'utiliser lors de la pagination:

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

Quelles que soient les variables GET que vous avez dans votre demande, elles seront ajoutées après le ?page= Paramètre GET.

4
José L. Patiño

Voici une balise de modèle personnalisée utile pour construire des chaînes de requête.

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

Si l'URL est http://example.com/Django/page/?search=sometext , le code HTML généré devrait ressembler à:

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

Plus d'exemples:

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

Enfin, le code source (écrit par moi):

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

Amélioration de this par:

Utilisez urlencode de Django au lieu de urllib, pour éviter une erreur de UnicodeEncodeError avec les arguments unicode.

Balise de modèle:

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)

Modèle:

<!-- 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>
2
Omid Raha

C'est un moyen simple de le faire

En vue :

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

Puis dans le modèle:

href="?page={{ objects.next_page_number }}&{{path}}"
1
Armance

Chaque lien que vous mettez dans votre vue doit être équipé de paramètres pertinents. Il n'y a pas de magie implicite qui convertirait:

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

dans:

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

Donc, ce dont vous avez besoin est un Sorter objet/classe/fonction/extrait (tout ce qui pourrait convenir ici sans en faire trop), qui agirait de la même manière que Django.core.paginator.Paginator, mais gérerait sort Paramètre GET.

Cela pourrait être aussi simple que cela:

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

Ensuite, selon vous:

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

Je pourrais devenir plus générique, mais j'espère que vous comprendrez le concept.

0
Tomasz Zieliński

Vous devrez retourner le GET comme indiqué ci-dessus. Vous pouvez transmettre la partie demande GET de l'URL en appelant

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

vous pouvez ensuite l'utiliser dans le modèle pour créer votre URL, par exemple.

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

Avec la pagination de Django - la préservation des paramètres GET est simple.

Copiez d'abord les paramètres GET dans une variable (en vue):

GET_params = request.GET.copy()

et l'envoyer au modèle via le dictionnaire de contexte:

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

La deuxième chose que vous devez faire est de l'utiliser, spécifiez-le dans les appels d'URL (href) dans le modèle - un exemple (étendant le code HTML de pagination de base pour gérer la condition de paramètre supplémentaire):

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

0
Nabeel Ahmed

Je dirais générer le lien suivant et précédent à partir de votre contrôleur, puis le passer à la vue et l'utiliser à partir de là. Je vais vous donner un exemple (plus comme un pseudocode):

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

à votre avis, utilisez-le comme ceci:

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

Une autre version de la solution url_encode, dans ce cas simplifiée par skoval00.

J'ai eu quelques problèmes avec cette version. Premièrement, il ne prend pas en charge le codage Unicode et deuxièmement, il se casse pour les filtres avec plusieurs clés identiques (comme un widget MultipleSelect). En raison de la conversion .dict (), toutes les valeurs sauf une sont perdues. Ma version prend en charge unicode et plusieurs de la même clé:

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

Cela crée une copie QueryDict, puis supprime toutes les clés qui correspondent aux kwargs (depuis la mise à jour pour un QueryDict ajoute au lieu de remplacer). Mark_safe était nécessaire en raison d'un problème de double encodage.

Vous l'utiliseriez comme ceci (n'oubliez pas de charger les balises):

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

où? p = 1 est notre syntaxe de pagination dans la vue.

0
Apollo Data