这一章节我们来实现以下搜索框的实现。
先回忆一下admin控件里的search是怎么用的:在自定义的配置类里有个搜索的字段名指定
上面的代码就定义了我们要搜索的对象是price字段。主要就是那个search_fields字段。如果没有指定这个字段,Django在后台是不知道对哪个字段进行搜索的。
上面的代码我们就指定了搜索的字段是price。
并且admin组件还有个特点就是在我们没有设置searche_fields字段的时候是没有搜索框显示出来的
我们也可以做一个这样的效果。
我们在前面的list视图函数中,是直接通过objects.all()的方式来拿到所有的数据然后放给前端显示出来。
我们需要对指定的字段进行搜索,先要对模板进行修改,添加一个form标签
<form action="" class="pull-right" method="GET"> <input type="text" name='q' value=""> <input type="submit" value="搜索"> </form>
这样,在提交表单的时候就会给url添加一个新的参数
上面的图就是在搜索框输入123以后提交跳转的URL。
然后我们可以通过request.GET.get('p')来获得要匹配的值。
比方我们要搜索书名为123的书,就要用到下面的语句
Book.objects.all().filter(title='123')
那么怎么把这个123传过来呢?我们已经从request.GET拿到了123这个值。后面要做的就是今天的重点了。
我们可以在指定search_fields列表里放上多个字段,那么就是从各个字段里去分别搜索了。还记得我们以前讲的Q查询么?
Q查询
还记得以前我们讲Django里的ORM的时候讲过Q查询和F查询(点击查看),在对多个字段进行查询的时候,我们可以用Q查询的方式实现
说我们要查询书名为123或者价钱等于12的书籍,就可以用这种方式来查询
from django.db.models import Q book = models.Books.objects.filter(Q(title='123')|Q(price=12))
这样就能拿到我们所需要的数据了。但是还有一点,我们在搜索数据的时候一般都是模糊查询,比方书名为Python之路,但是我就记住了一个Python,那么就要用一个关键字段了
book = models.Books.objects.filter(title__contains='Python')
这就牵扯到一些双下划线的用法了。上面那个链接里的博客里列出了常用的几种双下划线的使用方法。
Q查询对象
在配置类里我们可以配置多个搜索字段,比如书名和价格
class BookConf(ConfXadmin): list_display = deepcopy(ConfXadmin.list_display) list_display.append('price',) search_fields = ['title','price']
那么怎么把这个列表里的两个元素放在ORM语句中呢?这里介绍一个新的Q查询方法:Q查询对象
刚才我们回顾Q查询的方法,是把两个Q查询放在ORM语句中,但是可以注意一下Q查询的用法,是Q(),也就是直接生成了一个查询对象。那么我们能不能先创建这个对象呢?
q = Q() #创建q对象 q.children.append(('title','123')) #添加搜索条件1 q.children.append(('price',15)) #搜索条件2 q.connector = 'or' #条件1和条件2的关系 book = models.Books.objects.filter(q) #搜索
上面就是使用Q()对象来创建索引的过程,一定要注意的是q.chlidren原本就是一个list型的数据,每次添加的搜索关系要以列表或者元组的形式追加到q.children里。如果需要双下划线的形式也可以直接包进去
q.children.append(['title__contains','Python'])
因为第一个元素是字符串类型,我们放的字段也是字符串类型,所以可以直接用字符串拼接的形式来组成双下划线的索引形式。
数据的索引
有了上面的方法。在拿到search_fields以后我们就可以用一个for循环来获得全部的搜索条件。
1 def list_view(self,request): 2 new_data_url = self.get_add_url() 3 4 key_words = request.GET.get('q','') 5 if not key_words: 6 all_data = self.model.objects.all() 7 8 else: 9 search_q = Q() 10 search_q.connector = 'or' 11 for search_field in self.search_fields: 12 search_q.children.append((search_field+"__icontains",key_words)) 13 14 all_data = self.model.objects.all().filter(search_q)
这样,all_data就是经过了搜索以后的数据。
为了精简我们的list视图,可以把搜索的功能封装成一个函数放在ConfXadmin类里。
1 def get_search_condition(self,request): 2 key_words = request.GET.get('q','') 3 self.key_words = key_words 4 search_connection = Q() 5 6 if key_words: 7 search_connection.connector= 'or' 8 for search_field in self.search_fields: 9 search_connection.children.append((search_field+"__icontains",key_words)) 10 11 return search_connection
就几行代码,使用的时候可以直接在list_view里调用就可以了
search_conn = self.get_search_condition(request)
all_data = self.model.objects.filter(search_conn)
注意一下地3行的那个赋值语句是个很有意思的用法,一会讲优化的时候会讲到。
到目前为止我们已经实现了搜索的功能,但是后面还有一些点可以优化一下
搜索字符串保持
现在在每次搜索数据以后搜索框是直接清空的
可以试一下淘宝里的的效果,我们在搜索东西以后,搜索框会一直保持搜索字符串的,以我们现有的方法是不是先想到的是通过DOM加上JS代码来显示。
所以这就用到了上面那段代码里的
self.key_words = key_words
然后前端里的input标签可以改成这样的
<input type="text" name='q' value="{{view_obj.conf_obj.key_words}}">
注意下调用的关系,view_obj是从list里的render中通过locals拿到的,是List_View的实例对象,构造函数里有个conf_obj对应的事Confxadmin类,通过上面的代码可以拿到里面的搜索的字符串,然后保持显示的效果
这样就不用DOM操作了,即便是翻页也可以通过我们修改的带有状态保持的翻页功能来保持。
因为调用的过程略微复杂(view_obj.conf_obj.key_words),要了解具体的调用过程。相当于通过一个中间过程调用了自己的数据。ConfXadmin里的listview视图->List_View类的实例对象->conf_obj也就是Confxadmin的实力对象。
异常处理
在前面试的情况都是已知搜索对象存在的情况,但是如果搜索对象不存在的话,会有什么情况啊?
注意下URL,我们的搜索对象是10000,是不存在的,Django就给我们抛出了断言异常,仔细看一下console的输出,问题出在分页的那里,QuerrySet对象在进行切片的时候,是不能有负值的。看一下我们分页的代码中,获取当前页第一条ID值的代码
@property #静态属性,调用的时候可以省略括号 def ID_start(self): #当前页开始数据ID return (self.page-1) * self.data_per_page
当page为0 的时候,ID_start的值就成-10了,那为什么page值为0呢?问题在前面的一部分代码中
page = 1 if page<1 else page page = page_totle if page > page_totle else page
这里的代码是前面讲分页的时候(点击查看)里最后的一段代码的构造函数里的一部分
当没有数据的时候,page_totle值就为0,所以我们只需要在计算page_totle的时候加上一句代码就行了
1 page_totle,m = divmod(data_totle,data_per_page) 2 page_totle = page_totle if page_totle else 1 3 page_totle = page_totle if not m else page_totle +1 #计算总页码数 4 self.page_totle = page_totle
第2行的代码就是新添加的。当数据条数(data_totle)为0 的时候,第一行计算的页数就是0,余数m也是0,三四行的代码没有考虑到这个情况,所以加上第二行防止余数和页码都为0的时候页码总数为0。
搜索框的隐藏
本章一开始的时候讲了admin控件中,当我们没有指定搜索对象的时候是应该吧搜索框隐藏的,这个实现方法也比较容易,在模板中添加一个if判断就行了。
{%if view_obj.conf_obj.search_fields%} <form action="" class="pull-right" method="GET"> <input type="text" name='q' value="{{view_obj.conf_obj.key_words}}"> <input type="submit" value="搜索"> </form> {%endif%}
在没有指定search_fields的时候,form标签是不会生成的。所以页面上也就不显示这个搜索框了。
最后注意一点:
按照我们前面一张讲的封装的时候最后讲到的模板的渲染,其实我们应该在视图中只留一个view_obj给模板去渲染,随着我们的功能日益丰富,为了比较方便后期留接口。以后尽量把功能都放在这个view_obj里。