• 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里。

  • 相关阅读:
    BERT安装与使用
    32(2).层次聚类---BIRCH
    32(1).层次聚类---AGNES
    31(2).密度聚类---Mean-Shift算法
    31(1).密度聚类---DBSCAN算法
    linux环境变量
    Linux 中“一切都是文件”概念和相应的文件类型
    Linux 服务器如何禁止 ping 以及开启 ping
    如何用3个月零基础入门机器学习?
    28款GitHub最流行的开源机器学习项目
  • 原文地址:https://www.cnblogs.com/yinsedeyinse/p/13516866.html
Copyright © 2020-2023  润新知