在 Django 中一起使用 Q 对象和 Paginator

Use Q object and Paginator together in Django

我创建了视图,它通过文本框中给出的搜索查询来过滤数据。以及我使用分页器来显示分页的数据。

我的问题是,当我使用 Q 对象过滤数据并尝试通过单击下一步按钮进行分页时,所有数据都被刷新。

当我通过 Q 对象搜索文本时,URL 变为 http://127.0.0.1:8000/mael/parties/?q=keyword

然后单击下一步按钮,URL 变为 http://127.0.0.1:8000/mael/parties/?page=2

当我手动更改 URL http://127.0.0.1:8000/mael/parties/?q=keyword&page=2 时,它就起作用了。但是我不知道如何在代码中做到这一点。

是否可以同时使用Q对象搜索和分页?

我的观点

from mael.models import PartyTotalBillsView
from django.views.generic import ListView
from django.db.models import Q
from django.http import HttpResponseRedirect

class PartyListView(ListView):
    paginate_by = 2
    model = PartyTotalBillsView

def parties(request):
    # Show all records or searched query record    
    search_text = request.GET.get('q','')   

    try:
        if search_text:
            queryset = (Q(party_name__icontains=search_text))
            party_list = PartyTotalBillsView.objects.filter(queryset).order_by('party_name')
        else:
            # Show all data if empty keyword is entered
            party_list = PartyTotalBillsView.objects.order_by('party_name')
    except PartyTotalBillsView.DoesNotExist:
        party_list = None
    
    paginator = Paginator(party_list, 2) # Show 2 rows per page: for Test
    page_number = request.GET.get('page')
    
    party_list = paginator.get_page(page_number)
    
    return render(request, 'mael/parties.html', {'party_list': party_list})

模板文件

<form id="search-form" method="get" action="/mael/parties/">
    <input id="search-text" type="text" name="q" placeholder="Enter search keyword">
    <input class="btn-search-party" type="submit" value="Search" />
</form>        
<br/>


<table class="show-data">
    <thead>
    <tr>
        <th>ID</th>
        <th>Party Name</th>
        <th>Total Bill Amount</th>
        <th>Phone</th>
        <th>Address</th>
        <th></th>
    </tr>
    </thead>

    {% if party_list %}
    <tbody>
    {% for party in party_list %}
        <tr>
        <td class="party-id">{{ party.party_id }}</td>
        <td class="party-name">{{ party.party_name }}</td>
        <td>{{ party.total_bills }}</td>
        <td class="party-phone">{{ party.party_phone }}</td>
        <td class="party-address">{{ party.party_address }}</td>
        <td>
            <button class="btn-modify" data-partyid="{{party.party_id}}" type="buttton">
            Modify
            </button>
        </td>
        </tr>
    {% endfor %}
    </tbody>
    {% endif %}
</table>


<div class="pagination">
    <span class="step-links">
    {% if party_list.has_previous %}
        <a href="?page=1">&laquo; first</a>
        <a href="?page={{ party_list.previous_page_number }}">previous</a>
    {% endif %}

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

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

不要使用两个视图。 ListView也可以进行过滤:

class PartyListView(ListView):
    paginate_by = 2
    model = PartyTotalBillsView
    template_name = 'mael/parties.html'
    context_object_name = 'party_list'

    def querystring(self):
        qs = self.request.GET.copy()
        qs<strong>.pop(self.page_kwarg, None)</strong>
        return qs<strong>.urlencode()</strong>

    def get_queryset(self):
        qs = super().get_queryset()
        if 'q' in self.request.GET:
            qs = qs.filter(party_name__icontains=self.request.GET['q'])
        return qs.order_by('party_name')

在上一页和下一页的链接中,然后附加视图的 querystring

    <span class="step-links">
    {% if party_list.has_previous %}
        <a href="?page=1<strong>&amp;{{ view.querystring }}</strong>">&laquo; first</a>
        <a href="?page={{ page_obj.previous_page_number }}<strong>&amp;{{ view.querystring }}</strong>">previous</a>
    {% endif %}

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

    {% if party_list.has_next %}
        <a href="?page={{ page_obj.next_page_number }}<strong>&amp;{{ view.querystring }}</strong>">next</a>
        <a href="?page={{ page_obj.paginator.num_pages }}<strong>&amp;{{ view.querystring }}</strong>">last &raquo;</a>
    {% endif %}
    </span>

分页和 CBV

如果您使用带有 paginate_by 属性的 Django 通用 ListView,则不需要构建分页器实例。您可以使用 CBV(Class 基于视图)或函数视图,但不能同时使用两者。

为 HTML 显示创建一个 _django_pager.html 页面以包含在您的列表页面中。

{% comment %}
https://getbootstrap.com/docs/4.1/components/pagination/
{% endcomment %}

{% if is_paginated %}

  <ul class="pagination">

    {% if page_obj.has_previous %}

      <li class="page-item"><a class="page-link" href="?{% url_replace page=page_obj.previous_page_number %}">&laquo;</a></li>
    {% else %}
      <li class="page-item disabled"><a href="#" class="page-link">&laquo;</a></li>
    {% endif %}

    {% for i in page_obj.paginator.page_range %}
      {% if page_obj.number == i %}
        <li class="page-item active"><a href="#" class="page-link">{{ i }}<span class="sr-only">(current)</span></a></li>
      {% else %}
        <li class="page-item"><a class="page-link" href="?{% url_replace page=i %}">{{ i }}</a></li>
      {% endif %}
    {% endfor %}


    {% if page_obj.has_next %}
      <li class="page-item"><a class="page-link" href="?{% url_replace page=page_obj.next_page_number %}">&raquo;</a></li>
    {% else %}
      <li class="page-item disabled"><a href="#" class="page-link">&raquo;</a></li>
    {% endif %}
  </ul>

{% endif %}

过滤

Q 对象对于构建复杂查询非常强大,但您必须指定应用 Q 对象的数据库字段

我建议使用表单 Class,而不是在 HTML 中对表单进行硬编码。所以在 forms.py 创建一个 PartySearchForm

class PartySearchForm(forms.Form):
    """
    Search in party
    """
    search_text = forms.CharField(max_length=100,
                          required=False,
                          widget=forms.TextInput(attrs={
                              "class": "form-control",
                              "placeholder": "Search"
                              })
                          )

选项 1:过滤视图中的查询集

class PartyListView(ListView):
    model = PartyTotalBillsView
    form = PartySearchForm
    paginate_by = 100

    def build_where(self):
        where = Q(pk__gt=0)
        if self.request.GET.get("search_text"):
            search_list = self.request.GET.get("search_text", None).split()
            for search_item in search_list:
                where &= (
                    Q(party_name__icontains=search_item)
                )
        return where

    def get_queryset(self):
        qs = self.model.objects.all()
        qswhere = qs.filter(self.build_where())
        # first param must be request.GET or None (essential for the first load and initial values)
        # https://www.peterbe.com/plog/initial-values-bound-django-form-rendered
        self.form = PartySearchForm(self.request.GET or None)
        return qswhere

build_where 函数中,您可以添加任意数量的搜索字段。您可以通过将字段添加到 where 变量来搜索 party_name 以外的其他数据库字段。

where &= (
    Q(party_name__icontains=search_item)
    | Q(party_location__icontains=search_item)
)

您还可以在表单中添加 search_text 以外的其他搜索字段,并在 where 变量上添加 Q 搜索。

if self.request.GET.get("my_new_field"):
   where &= Q(supplier=self.request.GET.get("my_new_field", ""))

这里的关键点是 get_queryset 方法,其中定义了显示的查询集,即:获取、过滤和排序(也可以是一种方法)。如果在 models.py

中添加 class Meta,.order_by('party_name') 就没有用了
    class Meta:
        verbose_name = "Let's go party"
        ordering = ['party_name']

另一种方法是将查询集传递给表单并执行搜索

选项 2:在表单中过滤查询集

仅使用 SearchForm 中的搜索逻辑看起来更加清晰!

PartyListView.get_queryset变成

    def get_queryset(self):
        qs1 = self.model.objects.all()
        self.form = PartySearchForm(self.request.GET, queryset=qs1)
        qs = self.form.get_queryset(self.request.GET)
        return qs

PartySearchForm变成

class PartySearchForm(forms.Form):
    """
    Search in party
    """
    search_text = forms.CharField(max_length=100,
                          required=False,
                          widget=forms.TextInput(attrs={
                              "class": "form-control",
                              "placeholder": "Search"
                              })
                          )

    def __init__(self, *args, **kwargs):
        """
        Takes an option named argument ``queryset`` as the base queryset used in
        the ``get_queryset`` method.
        """
        self.queryset = kwargs.pop("queryset", None)
        super().__init__(*args, **kwargs)

    def get_queryset(self, request):
        where = Q(pk__gt=0)
        # is_valid() check is important to get access to cleaned_data
        if not self.is_valid():
            return self.queryset
        search_text = self.cleaned_data.get("search_text").strip()
        if search_text:
            search_list = search_text.split()
            for search_item in search_list:
                where &= (
                    Q(party_name__icontains=search_item)
                )
        qs = self.queryset.filter(where)
        return qs.distinct()

最后,如果您使用的是 Postgres DB 并想更深入地使用文本搜索,您可以实施 Django full text search. Pros & cons can be gained by reading this