Job Search Engine
787 浏览 5 years, 8 months
2.8 列表过滤和搜索
版权声明: 转载请注明出处 http://www.codingsoho.com/列表过滤和搜索
当前列表是完整显示的,记录少时看不出弊端,当记录多起来时,问题就出来了。我们来本节首先解决过滤和搜索的问题。
基本过滤
过滤有两种,GET和POST,本文搜索主要用到GET方法
原始filter方法过滤
假如我们要过滤包含”张江“地区的职位, 我们可以用http://127.0.0.1:8000/jobentry/?region=张江"访问,q后面带的就是我们的搜索关键字。我们在get_context_data
函数里处理这个信息,然后将过滤结果返回给模板
class JobEntryListView(ListView):
model = JobEntry
def get_context_data(self, *args, **kwargs):
context = super(JobEntryListView, self).get_context_data(*args, **kwargs)
q = self.request.GET.get('region')
if q:
object_list = self.model.objects.filter(region__icontains=region)
context['object_list'] = object_list
return context
模板里也要加一下下面的判断,避免内容为空
{% if object_list.count %}
{% endif %}
访问上述地址,只有张江地区的岗位显示出来了。
我们也可以添加多个条件,比如除了过滤region还过滤title,可以用http://127.0.0.1:8000/jobentry/?region=张江"
代码修改为
class JobEntryListView(ListView):
model = JobEntry
def get_context_data(self, *args, **kwargs):
context = super(JobEntryListView, self).get_context_data(*args, **kwargs)
region = self.request.GET.get('region')
title = self.request.GET.get('title')
if region or title:
object_list = self.model.objects.filter(region__icontains=region, title__icontains=title)
context['object_list'] = object_list
上面代码并不严谨,只是为了说明这个逻辑,可以看到两个简单的逻辑代码已经挺复杂了。Django提供了强大的Q功能,我们可以用这个方法来改善代码。
Q搜索
接下来,我们用django的Q功能来进行逻辑计算,上面代码可以改为
from django.db.models.query import Q
region = self.request.GET.get('region')
title = self.request.GET.get('title')
if region or title:
object_list = self.model.objects.filter(
Q(region__icontains=region) &
Q(title__icontains=title))
并且它可以处理更高级的逻辑。比如,如果要求除了上面的过滤,我们希望如果只要title, description字段包含”通信“字样,那么也筛选出来,可以修改代码如下
from django.db.models.query import Q
q = self.request.GET.get('q')
region = self.request.GET.get('region', None)
title = self.request.GET.get('title', None)
qs1 = None
qs2 = None
if region or title:
query = None
if region and title:
query = Q(region__icontains=region) & Q(title__icontains=title)
elif region:
query = Q(region__icontains=region)
else:
query = Q(title__icontains=title)
qs1 = self.model.objects.filter(query)
if q:
qs2 = self.model.objects.filter(
Q(description__icontains=q) &
Q(title__icontains=q))
if qs1 and qs2:
object_list = (qs1 | qs2).distinct()
elif qs1:
object_list = qs1
elif qs2:
object_list = qs2
if qs1 or qs2:
context['object_list'] = object_list
逻辑一多代码就会复杂,Django提供了类FilterSet
可以帮助我们轻松实现过滤功能。
djagno-filter
pip install django-filter
我之前用的版本是0.15.3,跟django 1.11不兼容,所以我升级到了1.0.2,但是看了一下跟最近的guide还是有很多地方不一致,所以下面的方案只针对这两个版本使用
定义过滤类
from django_filters import FilterSet, CharFilter
class JobEntryFilter(FilterSet):
title = CharFilter(name='title', lookup_expr='icontains', distinct=True)
region = CharFilter(name='region', lookup_expr='icontains', distinct=True)
class Meta:
model = JobEntry
fields = [
'title',
'region',
]
在ListView添加这个filter_class
class JobEntryListView(ListView):
model = JobEntry
filter_class = JobEntryFilter
修改上面的get_context_data
过滤代码如下
def get_context_data(self, *args, **kwargs):
context = super(JobEntryListView, self).get_context_data(*args, **kwargs)
from django.db.models.query import Q
q = self.request.GET.get('q')
qs1 = self.filter_class(self.request.GET, self.get_queryset()).qs
qs2 = None
if q:
qs2 = self.model.objects.filter(
Q(description__icontains=q) &
Q(title__icontains=q)).distinct()
if qs1 and qs2:
object_list = (qs1 | qs2).distinct()
elif qs1:
object_list = qs1
elif qs2:
object_list = qs2
if qs1 or qs2:
context['object_list'] = object_list
这儿对公共搜索q还是放在了列表视图类来实现,这个其实也可以在过滤类里实现,之前版本可以用字段MethodFilter
,在新版本里被method方法取代
代码如下
class JobEntryFilter(FilterSet):
q = CharFilter(method="q_filter", distinct=True)
title = CharFilter(name='title', lookup_expr='icontains', distinct=True)
region = CharFilter(name='region', lookup_expr='icontains', distinct=True)
class Meta:
model = JobEntry
fields = [
'title',
'region',
]
def q_filter(self, queryset, field, value):
qs = self.Meta.model.objects.filter(Q(title__icontains=value) | Q(description__icontains=value)).distinct()
return (queryset | qs).distinct()
在列表函数里直接用一条语句来获取queryset就可以了
context['object_list'] = self.filter_class(self.request.GET, self.get_queryset()).qs
搜索表单
到目前为止,我们还都是在地址栏输入query信息,接下来我们用一个表单来传递这些值
首先安装库django-crispy-forms,这个是个强大的表单插件,不在本文详述,有兴趣可以专门看看
django-crispy-forms==1.6.0
别忘了在设置里添加
#Crispy FORM TAGs SETTINGS
CRISPY_TEMPLATE_PACK = 'bootstrap3'
使用非常简单
{% load crispy_forms_tags %}
{{ filter_form|crispy }}
模板代码如下
{% load crispy_forms_tags %}
<div class='row'>
<div class='col-xs-4 col-sm-3 col-md-2 left-bar'>
<form method="GET" action="{% url 'job_entry_list' %}">
{{ filter_form|crispy }}
<!-- <input type='hidden' name='q' value='{{ request.GET.q }}' /> -->
<input type='submit' class='btn btn-primary' style="margin-bottom: 5px;" value="{% trans 'Apply Filter' %}">
<a class="btn btn-default" style="margin-bottom: 5px;" href="{% url 'job_entry_list' %}" >{% trans 'Clear Filters' %}</a>
</form>
</div>
</div>
渲染的表单如下
作为搜索表单,我们希望排列是横着的,所以还是打算从头写一个,代码如下
.form-group input{
text-indent: 5px;
border-radius: 2px;
}
<div class="row">
<div class="col-sm-12 col-xs-0">
<div class="collapse navbar-collapse">
<div class="form-filter-inline">
<form class="form-inline" method="GET" action="{% url 'job_entry_list' %}">
{% for field in filter_form %}
<div class="form-group">
{{field.label_tag}}
{{ field }}
</div>
{% endfor %}
<div class="row form-group apply-filter">
<div class="col-sm-6">
<input type='submit' class='btn btn-primary' value="{% trans 'Apply Filter' %}">
</div>
<a class="btn btn-default" href="{% url 'job_entry_list' %}" >{% trans 'Clear Filters' %}</a>
</div>
</form>
</div>
</div>
</div>
</div>
它的效果如下
添加搜索到导航条
最后,我们打算利用导航条的搜索工具栏把这个功能集成到导航栏上
修改导航条代码如下
<form class="navbar-form navbar-left" method="get" action="{% url 'job_entry_list' %}">
<div class="form-group">
<input type="text" name='q' value='{{ request.GET.q }}' class="form-control" placeholder="Search">
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
主要改动如下
- 给form添加了method和action
- input输入框添加了name为q用于传递变量给后台, 并设置了value,这样能够回显地址栏的值
这样,兼顾自由搜索和固定字段搜索的功能完成了。