web-dev-qa-db-fra.com

django-filter utiliser les paginations

J'utilise le paquet Django-filter pour fournir une fonctionnalité de recherche dans ma vue Liste. 

Maintenant, je souhaite également ajouter une pagination à cette vue.
J'essaie de combiner la pagination à un ensemble de requêtes filtré, mais je n'ai aucune idée de la marche à suivre.

Jusqu'ici, j'ai essayé ce qui suit sur views.py:

def search(request):
    qs = local_url.objects.filter(global_url__id=1).all()
    paginator = Paginator(qs, 25)
    page = request.GET.get('page')
    try:
        pub = paginator.page(page)
    except PageNotAnInteger:
        pub = paginator.page(1)
    except EmptyPage:
       pub = paginator.page(paginator.num_pages)
    url_filter = PublicationFilter(request.GET, queryset=qs)
    return render(request, 'ingester/search_list.html', {'filter': url_filter, 'publication':pub})
10
Anh Tuan Nguyen

Cela a fonctionné pour moi: 

dans mon modèle au lieu de l'utiliser 

<li><a href="?page={{ i }}">{{ i }}</a></li>

J'ai écrit ceci: 

{% if 'whatever_parameter_you_use_to_filter' in request.get_full_path %}
   <li><a href="{{ request.get_full_path }}&page={{ i }}"{{ i }}</a></li>
{% else %}
   <li><a href="?page={{ i }}">{{ i }}</a></li>
{% endif %}

J'espère que ça aide :)

5
stathoula

Pour utiliser Django Filter et paginer le résultat filtré, procédez comme suit:

  1. Créez une classe de filtre pour votre modèle:

    Sur my_project/my_app/filters.py:

    import Django_filters
    
    class MyModelFilter(Django_filters.FilterSet):
        class Meta:
            model = MyModel
            # Declare all your model fields by which you will filter
            # your queryset here:
            fields = ['field_1', 'field_2', ...]
    
  2. Chaque objet FilterSet a une propriété .qs qui contient le filtré queryset et vous pouvez même le remplacer si vous voulez .

  3. Nous allons paginer la propriété .qs de notre MyModelFilter:

    Sur my_project/my_app/views.py:

    from . import filters
    
    
    def my_view(request):
        # BTW you do not need .all() after a .filter() 
        # local_url.objects.filter(global_url__id=1) will do
        filtered_qs = filters.MyModelFilter(
                          request.GET, 
                          queryset=MyModel.objects.all()
                      ).qs
        paginator = Paginator(filtered_qs, YOUR_PAGE_SIZE)
    
        page = request.GET.get('page')
        try:
            response = paginator.page(page)
        except PageNotAnInteger:
            response = paginator.page(1)
        except EmptyPage:
            response = paginator.page(paginator.num_pages)
    
        return render(
            request, 
            'your_template.html', 
            {'response': response}
        )
    

Et voila!


PS_1: Dans mon expérience, le filtre Django, "joue" mieux avec Django Rest Framework .

PS_2: Si vous êtes sur le point d'utiliser DRF, j'ai écrit un exemple sur la façon d'utiliser la pagination dans une vue basée sur les fonctions que vous pouvez facilement combiner avec une FilterSet:

@api_view(['GET',])
def my_function_based_list_view(request):
    paginator = PageNumberPagination()
    filtered_set = filters.MyModelFilter(
                       request.GET, 
                       queryset=MyModel.objects.all()
                   ).qs
    context = paginator.paginate_queryset(filtered_set, request)
    serializer = MyModelSerializer(context, many=True)
    return paginator.get_paginated_response(serializer.data)
4
John Moutafis

La partie la plus importante ici est la comment vous construisez vos URL dans le modèle .

vous avez probablement 

{% if pages.has_previous %}
<li><a href="?page={{ pages.previous_page_number }}">Prev</a></li>
{% endif %}

ce qui convient parfaitement si vous ne l'utilisez que pour basculer entre les premiers résultats paginés.

Mais la partie délicate est que lorsque vous utilisez les filtres Django-fitler, la chaîne de requête (la partie après le '?' ) obtient des paires de clés-valeurs totalement nouvelles, sans tenir compte de votre ?page=2 ou similaire.

Ainsi, pour que la pagination fonctionne avec des résultats filtrés, lorsque vous cliquez sur le bouton "Suivant" ou "Précédent" - parmi les valeurs-clés de Django-fitler, vous devez également transmettre le &page=5 en tant que paire.

Comme @stathoula l'a mentionné, vous devez vérifier si au moins un de vos champs de filtre est déjà présent dans la chaîne de requête. Si tel est le cas, vous devez utiliser les paires clé-valeur déjà présentes, suivies de la nouvelle paire &page=3.

Cela semble très simple, mais je devais faire de petits gestes de hackish pour ne pas répéter le &page=1 encore et encore dans la chaîne de requête, car un utilisateur clique sur les flèches.

Dans mon cas, j'ai "title" comme filtre, donc je dois vérifier s'il est déjà présent là-bas.

Voici un extrait de ce que j'ai fait fonctionner parfaitement pour mon projet.

templates/pagination.html

<div class="paginator">

    {% with request.get_full_path as querystring %}
        <ul class="pagination nav navbar-nav">

            <!-- Previous page section -->
            {% if pages.has_previous %}
                {% if 'title' in querystring %}
                    {% if 'page' in querystring %}
                        <li class="paginator {% if pages.number == page %}active{% endif %}">
                            <a href="{{ querystring|slice:":-7" }}&page={{ pages.previous_page_number }}">Prev</a>
                        </li>
                    {% else %}
                        <li class="paginator {% if pages.number == page %}active{% endif %}">
                            <a href="{{ querystring }}&page={{ pages.previous_page_number }}">Prev</a>
                        </li>
                    {% endif %}
                {% else %}
                    <li class="paginator {% if pages.number == page %}active{% endif %}">
                        <a href="?page={{ pages.previous_page_number }}">Prev</a>
                    </li>
                {% endif %}
            {% endif %}

            <!-- All pages section -->
            {% for page in pages.paginator.page_range %}
                {% if 'title' in querystring %}
                    {% if 'page' in querystring %}
                        <li class="paginator {% if pages.number == page %}active{% endif %}">
                            <a href="{{ querystring|slice:":-7" }}&page={{ page }}">{{ page }}</a>
                        </li>
                    {% else %}
                        <li class="paginator {% if pages.number == page %}active{% endif %}">
                            <a href="{{ querystring }}&page={{ page }}">{{ page }}</a>
                        </li>
                    {% endif %}
                {% else %}
                    <li class="paginator {% if pages.number == page %}active{% endif %}">
                        <a href="?page={{ page }}">{{ page }}</a>
                    </li>
                {% endif %}
            {% endfor %}

            <!-- Next page section -->
            {% if pages.has_next %}
                {% if 'title' in querystring %}
                    {% if 'page' in querystring %}
                        <li class="paginator {% if pages.number == page %}active{% endif %}">
                            <a href="{{ querystring|slice:":-7" }}&page={{ pages.next_page_number }}">Next</a>
                        </li>
                    {% else %}
                        <li class="paginator {% if pages.number == page %}active{% endif %}">
                            <a href="{{ querystring }}&page={{ pages.next_page_number }}">Next</a>
                        </li>
                    {% endif %}
                {% else %}
                    <li class="paginator {% if pages.number == page %}active{% endif %}">
                        <a href="?page={{ pages.next_page_number }}">Next</a>
                    </li>
                {% endif %}
            {% endif %}

        </ul>
    {% endwith %}

</div>

Voici la vue, au cas où:

app/views.py

def index(request):
    condo_list = Condo.objects.all().order_by('-timestamp_created')
    condo_filter = CondoFilter(request.GET, queryset=condo_list)

    paginator = Paginator(condo_filter.qs, MAX_CONDOS_PER_PAGE)
    page = request.GET.get('page')

    try:
        condos = paginator.page(page)
    except PageNotAnInteger:
        condos = paginator.page(1)
    except EmptyPage:
        condos = paginator.page(paginator.num_pages)


    return render(request, 'app/index.html', {
        'title': 'Home',
        'condos': condos,
        'page': page,
        'condo_filter': condo_filter,
    })

Voici un exemple de travail:

.

2
DonExo

Il m'a fallu un certain temps pour trouver la solution de séchage et de nettoyage plus efficace pour résoudre ce problème et le meilleur, à mon avis, est celui qui utilise des balises de modèle.

from Django import template

register = template.Library()

@register.simple_tag
def relative_url(value, field_name, urlencode=None):
    url = '?{}={}'.format(field_name, value)
    if urlencode:
        querystring = urlencode.split('&')
        filtered_querystring = filter(lambda p: p.split('=')[0] != field_name, querystring)
        encoded_querystring = '&'.join(filtered_querystring)
        url = '{}&{}'.format(url, encoded_querystring)
    return url

et dans votre modèle

<a href="{% relative_url i 'page' request.GET.urlencode %}">{{ i }}</a>

Source: Traitement des paramètres QueryString

1
Yassine Belmamoun

Si j'ai bien compris, votre objectif est de paginer votre ensemble de requêtes filtrées. Si tel est le cas, vous pouvez transmettre la propriété "qs" de l'objet PublicationFilter au constructeur Paginator:

def search(request):
    qs = local_url.objects.filter(global_url__id=1).all()
    url_filter = PublicationFilter(request.GET, queryset=qs)
    paginator = Paginator(url_filter.qs, 25)
    page = request.GET.get('page')
    try:
        pub = paginator.page(page)
    except PageNotAnInteger:
        pub = paginator.page(1)
    except EmptyPage:
        pub = paginator.page(paginator.num_pages)
    url_filter = PublicationFilter(request.GET, queryset=qs)
    return render(request, 'ingester/search_list.html', {'publication':pub})

url_filter.qs contient un QuerySet filtré
url_filter.queryset contient un ensemble de requêtes non filtré

1
Alexey

Pour ajouter aux réponses, je l’ai fait aussi avec des tables html avec Django-filters et Paginator. Vous trouverez ci-dessous ma vue et les fichiers de modèle. La balise template est nécessaire pour vous assurer de transmettre les bons paramètres à l'URL de pagination.

search_view.py

from Django.shortcuts import render
from app.models.filters_model import ApiStatusFilter
from app.models.api_status import ApiStatus
from Django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from datetime import datetime, timedelta

def status(request):
    all_entries_ordered = ApiStatus.objects.values().order_by('-created_at')[:200]

    for dictionarys in all_entries_ordered:
        dictionarys

    apistatus_list = ApiStatus.objects.values().order_by('-created_at')
    apistatus_filter = ApiStatusFilter(request.GET, queryset=apistatus_list)

    paginator = Paginator(apistatus_filter.qs, 10)
    page = request.GET.get('page')
    try:
        dataqs = paginator.page(page)
    except PageNotAnInteger:
        dataqs = paginator.page(1)
    except EmptyPage:
        dataqs = paginator.page(paginator.num_pages)

    return render(request, 'status_page_template.html', {'dictionarys': dictionarys, 'apistatus_filter': apistatus_filter, 'dataqs': dataqs, 'allobjects': apistatus_list})

status_template.html

{% load static %}
{% load my_templatetags %}

<!DOCTYPE html>
<html lang="en">
    <head>
        <link rel="stylesheet" type="text/css" href="{% static 'css/table_styling.css' %}">
        <meta charset="UTF-8">
        <title>TEST</title>
    </head>

    <body>
         <table>
            <thead>
                <tr>
                    {% for keys in dictionarys.keys %} 
                        <th>{{ keys }}</th>
                    {% endfor %}
                </tr>
            </thead>
                <form method="get">
                    {{ apistatus_filter.form.as_p }}
                    <button type="submit">Search</button>
                        {% for user in dataqs.object_list %}
                        <tr>
                            <td>{{ user.id }}</td>
                            <td>{{ user.date_time }}</td>
                            <td>{{ user.log }}</td>
                        </tr>
                        {% endfor %}
                </form>
            </tbody>
        </table>

        <div class="pagination">
            <span>
                {% if dataqs.has_previous %}
                    <a href="?{% query_transform request page=1 %}">&laquo; first</a>
                    <a href="?{% query_transform request page=dataqs.previous_page_number %}">previous</a>
                {% endif %}

                <span class="current">
                    Page {{ dataqs.number }} of {{ dataqs.paginator.num_pages }}.
                </span>

                {% if dataqs.has_next %}
                    <a href="?{% query_transform request page=dataqs.next_page_number %}">next</a>
                    <a href="?{% query_transform request page=dataqs.paginator.num_pages %}">last &raquo;</a>
                {% endif %}
            </span>
        </div> 
    </body>
</html>

my_templatetags.py

from Django import template

register = template.Library()

@register.simple_tag
def query_transform(request, **kwargs):
    updated = request.GET.copy()
    for k, v in kwargs.items():
        if v is not None:
            updated[k] = v
        else:
            updated.pop(k, 0)

    return updated.urlencode()
0
Elia Ahadi