Django-filter使用分页功能

29

我正在使用 django-filter 模块在我的列表视图中提供搜索功能。

现在我也想在该视图中添加分页。
我试图将分页与筛选后的查询集合并,但是我不知道该怎么做。

到目前为止,我在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})

你已经创建了分页的HTML模板吗? - doru
是的,在我的示例中我省略了它,因为我的主要问题是如何在视图中将 Filterset 对象与 Paginator 结合起来。 - Anh Tuan Nguyen
1
嘿,@AnhTuanNguyen,你找到任何有用的答案了吗? - John Moutafis
11个回答

22
这对我有用:

在我的模板中,我不使用这个。
<li><a href="?page={{ i }}">{{ i }}</a></li>

我写了这个:

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

希望这有所帮助 :)


5
唯一的问题是,就像@DonExo在他的答案中所说,页面参数被附加到URL中。不过它还是能够工作的。 - Benbb96
问题在于您不能超过2页。每次您从请求中获取完整路径时,您将找到page=X并将其添加。 - Anass Lahrech
当使用多个参数进行过滤时,这种方法无法正常工作。 - MrObjectOriented

13
使用Django Filter并对过滤结果进行分页,可以按以下步骤操作:
1. 为您的模型创建一个过滤器类:
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', ...]
  • 每个FilterSet对象都有一个.qs属性,其中包含过滤后的查询集,如果需要,您甚至可以覆盖它

  • 我们将分页显示我们的MyModelFilter.qs属性:

    my_project/my_app/views.py中:

    from . import filters
    from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
    
    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}
        )
    
  • 就是这样了!


    PS_1:在我的经验中,Django过滤器与Django Rest Framework更配

    PS_2:如果您要使用DRF,我已经编写了一个示例,介绍如何在基于函数的视图中使用分页,您可以轻松地将其与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)
    

    1
    你或者有人能否添加一个HTML模板?我正在尝试让它工作,但是我卡在了模板上。 - Spatial Digger
    1
    @GaryNobles 这是一个很好的起点:https://docs.djangoproject.com/en/2.0/topics/templates/,这个教程也会对你有很大帮助:https://tutorial.djangogirls.org/en/django_templates/。 - John Moutafis
    我一直在阅读它们,我会再读一遍,分页已经可以了,但是我无法使筛选工作。我有 {{filter.material.label_tag}} {%render_field filter.form.material%} 我真的被卡住了,而且周一之前还有最后期限。 - Spatial Digger
    @GaryNobles 没有看到你的代码,没有人能够帮助你。如果你遇到了这么大的困难,我建议你撰写一个新的问题 :/ - John Moutafis
    很不幸,即使我的声望值达到了116,我也不能再发布问题了,而且我有阅读障碍,所以我的提问方式可能与常规不同。无论如何,我已经使用了这段代码,它已经半工作了,但我就是想不出过滤问题的解决方法,这篇文章看起来比我看到的所有文章都更有希望,但一个模板示例会帮助我理解如何实现它。我正在进行一项研究项目,如果我不能让它运行起来,我将不得不像去年一样采用原始SQL。我没有设计数据库,重新设计计划在挖掘之后进行。 - Spatial Digger

    10
    为了补充答案,我也用html表格和django过滤器以及分页器做了它。以下是我的视图和模板文件。为了确保您将正确的参数传递到分页url,需要使用模板标签。 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()
    

    6

    我花了一些时间找到了解决这个问题的DRYer和更干净的方案,而在我的看法中,最好的方案是使用模板标记。

    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
    

    并且在你的模板中

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

    来源:处理查询字符串参数


    4
    这里最重要的是如何在模板中构建URL。你可能已经知道,HTML标签需要保留,但请注意不要添加解释。

    如果需要翻译下文,请提供更多信息。

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

    如果你只是用它来在初始分页结果之间进行切换,那就完全没问题。

    但是,当你使用 django-filter 的筛选器时,查询字符串('?' 后面的部分)会得到全新的键值对,忽略了你的 ?page=2 或类似的内容。

    所以,为了使分页与筛选结果一起工作,在点击“下一页”或“上一页”按钮时——除了来自 django-filter 的键值对之外,你还需要传递 &page=5 作为一对。

    正如 @stathoula 提到的那样,你需要检查查询字符串中是否已经存在至少一个筛选字段。如果有,那么你需要使用已经存在的键值对,然后是新的 &page=3 键值对。

    看起来非常简单,但我不得不做一些小的hackish以避免在用户点击箭头时重复出现 &page=1

    在我的情况下,我将 'title' 作为一个筛选器,所以我需要检查它是否已经存在。

    以下是我为我的项目制作的代码片段,完美运行。

    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>
    

    以下是视图代码:

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

    这是一个可运行的示例:

    如果页面数量超过9页,切片功能将无法完美运行。但是在此模板中已经有足够的条件,而筛选器的目的是减少页面数量,因此不需要更改此设置。 - Benbb96

    3

    我在处理分页结果中“记住过滤/查询URL参数”的方法是:将当前的URL参数作为上下文变量进行传递:

    # views.py
    
    class PublicationFilterView(FilterView):
        model = Publication
        filterset_class = PublicationFilter
        paginate_by = 15
    
        def get_context_data(self, *args, **kwargs):
            _request_copy = self.request.GET.copy()
            parameters = _request_copy.pop('page', True) and _request_copy.urlencode()
            context = super().get_context_data(*args, **kwargs)
            context['parameters'] = parameters
            return context
    

    # templates/path/to/pagination.html
    
    <a href="?page={{ page_obj.next_page_number }}&{{ parameters }}">
      Next
    </a>
    

    2

    这个对我来说100%有效

    views.py:

    def search(request):
        
        category=Category.objects.all()
        try:
            qs=request.GET["qs"]
            products=Product.objects.filter(Q(name__icontains=qs) |Q(details__icontains=qs) | Q(category__name__icontains=qs) | Q(branch__child__icontains=qs) | Q(manufacturer__name__icontains=qs) | Q(color__name__icontains=qs)).distinct()
            print(products)
            search=f"qs={qs}"
        except:
            search=None

    在HTML中,使用

    标签。

     <ul class="shop-p__pagination">
                                            {% if products.has_provious %}
                                            <li>
    
                                           <a class="fas fa-angle-left" href="?page={{ products.previous_page_number }}&{search}"></a></li>
                                           {% endif  %}
                                           {% for i in products.paginator.page_range %}
                                           {% if products.number == i %}
                                           
                                           <li class="is-active"><a href="?page={{i}}&{{search}}">{{i}}</a></li>
                                           {% else %}
                                           <li><a href="?page={{i}}&{{search}}">{{i}}</a></li>
    
                                           {% endif %}
                                         {% endfor %}
                                   
                                           {% if products.has_next %}
                                       <li>
    
                                           <a class="fas fa-angle-right" href="?page={{ products.next_page_number }}&{{search}}"></a></li>
                                           {% endif %}
                                        </ul>


    1
    我理解您的目标是对筛选后的查询集进行分页。如果是这样,您可以将PublicationFilter对象的"qs"属性传递给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 包含过滤后的 QuerySet
    url_filter.queryset 包含未经过滤的 QuerySet


    0
    get_context_data()函数中:
    form_submitted = 'csrfmiddlewaretoken' in self.request.GET
    context['cleaned_full_path'] = '{}{}'.format(
        self.request.get_full_path().split('&page' if form_submitted else '?page')[0],
        '&' if form_submitted else '?'
    )
    

    然后,在您的模板中,加载类似以下内容的东西: < p > < code > <a href="{{ cleaned_full_path }}page={{ page_obj.paginator.num_pages }}"


    0

    除了@stathoula之外,针对@Benbb96的回应,我使用正则表达式在类视图中覆盖setup方法,成功删除了额外的page参数:

    import re
    
    ...
    
    class MyView(ListView):
    ...
    
        def setup(self, request, *args, **kwargs) -> None:
            request.GET.get("page")
            request.META["QUERY_STRING"] = re.sub("(&|\?)page=(.)*", "", request.META.get("QUERY_STRING", ""))
            return super().setup(request, *args, **kwargs)
    

    希望这能帮到任何人!
    更多信息:

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接