在 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">« 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 »</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>&{{ view.querystring }}</strong>">« first</a>
<a href="?page={{ page_obj.previous_page_number }}<strong>&{{ 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>&{{ view.querystring }}</strong>">next</a>
<a href="?page={{ page_obj.paginator.num_pages }}<strong>&{{ view.querystring }}</strong>">last »</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 %}">«</a></li>
{% else %}
<li class="page-item disabled"><a href="#" class="page-link">«</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 %}">»</a></li>
{% else %}
<li class="page-item disabled"><a href="#" class="page-link">»</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。
我创建了视图,它通过文本框中给出的搜索查询来过滤数据。以及我使用分页器来显示分页的数据。
我的问题是,当我使用 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">« 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 »</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>&{{ view.querystring }}</strong>">« first</a>
<a href="?page={{ page_obj.previous_page_number }}<strong>&{{ 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>&{{ view.querystring }}</strong>">next</a>
<a href="?page={{ page_obj.paginator.num_pages }}<strong>&{{ view.querystring }}</strong>">last »</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 %}">«</a></li>
{% else %}
<li class="page-item disabled"><a href="#" class="page-link">«</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 %}">»</a></li>
{% else %}
<li class="page-item disabled"><a href="#" class="page-link">»</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
.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。