Xadmin控件的实现:〇六查询视图四——list视图search功能

这一章节我们来实现以下搜索框的实现。

Xadmin里的搜索功能

先回忆一下admin控件里的search是怎么用的:在自定义的配置类里有个搜索的字段名指定

class
BookConf(admin.ModelAdmin):
  list_display = ['title','publisher','price']
  search_fields = ['price']

上面的代码就定义了我们要搜索的对象是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里。

原文地址:https://www.cnblogs.com/yinsedeyinse/p/13516866.html