项目一:CRM(客户关系管理系统)--5搜索、统计、排序

简单基础的功能添加是比较漫长的道路,前面添加了分页和过滤功能,接下来添加的仍然会是一些琐碎而又常用的功能。

1. 添加页面统计功能

这个功能实在是太简单了,只需要一行代码就能够搞定,当然是在这使用Django的情况,其他框架虽然没有使用过,但应该一行代码也能搞定,可以通过直接数据库查询将统计结果返回给模板文件进行渲染。我们在这里使用的方法很简单,不要更改其他任何文件的代码,只需要在table_objs.html文件中添加如下内容(位置自己定):

1 ...
2              </table>
3               {# 添加下面的一行代码即可! #}
4               <p>数量统计:<mark style="margin: auto 5px">{{ query_set.paginator.count }}</mark></p>
5               <nav>
6               {#分页处理#}
7 ...

显示效果如下:

2. 添加搜索功能

外键查询和非外键查询方式不同:

非外键查询使用:

In [142]: models.CustomerInfo.objects.filter(name__contains='1')
Out[142]: <QuerySet [<CustomerInfo: QQ:1 -- Name:test1>]>

外键查询使用:

In [143]: models.CustomerInfo.objects.filter(consult_course__name__contains=1)
Out[143]: <QuerySet [<CustomerInfo: QQ:1 -- Name:test1>]>

因此,在kingadmin.py中定义管理类中,如果要自定制search_fields时,一定要搞清楚field名称是否是外键,如果是外键的话,这里写的field名称应该是:field_name__外键field_name(中间使用双下换线连接),否则的话会报错:

 1 In [144]: models.CustomerInfo.objects.filter(consult_course__contains=1)
 2 ---------------------------------------------------------------------------
 3 FieldError                                Traceback (most recent call last)
 4 <ipython-input-144-f7156274ef81> in <module>()
 5 ----> 1 models.CustomerInfo.objects.filter(consult_course__contains=1)
 6 
 7 /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/db/models/manager.py in manager_method(self, *args, **kwargs)
 8      83         def create_method(name, method):
 9      84             def manager_method(self, *args, **kwargs):
10 ---> 85                 return getattr(self.get_queryset(), name)(*args, **kwargs)
11      86             manager_method.__name__ = method.__name__
12      87             manager_method.__doc__ = method.__doc__
13 
14 /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/db/models/query.py in filter(self, *args, **kwargs)
15     782         set.
16     783         """
17 --> 784         return self._filter_or_exclude(False, *args, **kwargs)
18     785 
19     786     def exclude(self, *args, **kwargs):
20 
21 /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/db/models/query.py in _filter_or_exclude(self, negate, *args, **kwargs)
22     800             clone.query.add_q(~Q(*args, **kwargs))
23     801         else:
24 --> 802             clone.query.add_q(Q(*args, **kwargs))
25     803         return clone
26     804 
27 
28 /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/db/models/sql/query.py in add_q(self, q_object)
29    1248         existing_inner = set(
30    1249             (a for a in self.alias_map if self.alias_map[a].join_type == INNER))
31 -> 1250         clause, _ = self._add_q(q_object, self.used_aliases)
32    1251         if clause:
33    1252             self.where.add(clause, AND)
34 
35 /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/db/models/sql/query.py in _add_q(self, q_object, used_aliases, branch_negated, current_negated, allow_joins, split_subq)
36    1274                     child, can_reuse=used_aliases, branch_negated=branch_negated,
37    1275                     current_negated=current_negated, connector=connector,
38 -> 1276                     allow_joins=allow_joins, split_subq=split_subq,
39    1277                 )
40    1278                 joinpromoter.add_votes(needed_inner)
41 
42 /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/db/models/sql/query.py in build_filter(self, filter_expr, branch_negated, current_negated, can_reuse, connector, allow_joins, split_subq)
43    1199             lookup_class = field.get_lookup(lookups[0])
44    1200             if lookup_class is None:
45 -> 1201                 raise FieldError('Related Field got invalid lookup: {}'.format(lookups[0]))
46    1202             if len(targets) == 1:
47    1203                 lhs = targets[0].get_col(alias, field)
48 
49 FieldError: Related Field got invalid lookup: contains

前端在渲染的时候也是报错:

2.1. 原生admin搜索功能分析

回来看看起初我们在admin.py中添加的功能字段:

 1 ...
 2 #自定义操作
 3 class CustomerAdmin(admin.ModelAdmin):
 4     list_display = ('name', 'id','qq','source','consultant','content','status','date')
 5     list_filter = ('source','consultant','date')
 6     #搜索功能,并指定搜索字段
 7     search_fields = ('qq','name','consult_course__name')
 8     raw_id_fields = ('consult_course',)
 9     filter_horizontal = ('tags',)
10     list_editable = ('status',)
11 ...

添加了这么多的功能,大家应该早就发现了吧!Django为我们提供的套路都是一样的,我们进行模仿重构也是同样的套路。在此基础上,我们还可以进一步的扩展!

2.2. 添加搜索字段

在我们自己的king_admin中添加,首先要在基类中添加,然后在独立的自定义子类中添加,具体如下:

 1 from CRM import models
 2 #创建基类
 3 class BaseAdmin(object):
 4     list_display = []
 5     list_filter = []
 6     search_fields = []
 7     list_per_page = 10
 8 ...
 9 #自定义类,显示特定字段
10 class CustomerAdmin(BaseAdmin):
11     list_display = ['qq','name','source','consultant','consult_course','date','status']
12     list_filters = ['source','consultant','consult_course','status']
13     #添加如下代码,搜索字段自选
14     search_fields = ['qq', 'name', 'consultant__name']
15     list_per_page = 2
16     #model = models.Customer
17 ...

这里的consultant__name考虑到了跨表的问题,先这样写,后面在优化的时候集中处理!

2.3. 编写搜索功能函数

这里,我们还要考虑一个问题:前面我们添加了过滤、分页、统计功能,那么搜索功能是该基于这些功能呢? 很显然,是必须的。也就是说,当我们将数据过滤后得到的结果,在通过搜索查询到所需的具体数据。

utils.py文件中,过滤功能函数下面继续编写搜索功能函数:

 1 from django.db.models import Q
 2 ...
 3 #-----------------------搜索功能-----------------------------------
 4 def table_search(request,admin_class,object_list):
 5     """
 6     :param request: 封装的请求体
 7     :param admin_class: 自定义类
 8     :param object_list: 过滤后的数据
 9     :return:
10     """
11     #在请求中通过参数查询结果
12     search_text = request.GET.get("_q","")
13     #创建Q查询对象,组合搜索
14     q_obj = Q()
15     #设定连接方式
16     q_obj.connector = "OR"
17     #遍历搜索选项
18     for search_words in admin_class.search_fields:
19         q_obj.children.append(("{0}__contains".format(search_words), search_text))
20     search_result = object_list.filter(q_obj)
21     return search_result, search_text

2.4. 编写视图函数

搜索功能已经写好,视图函数这里只需要引用添加就完美了。先找到过滤后的数据变量,将其作为参数传入即可:

 1 def display_objects(request, app_name, table_name):
 2     #获取自定义的admin_class
 3     admin_class = enabled_admins[app_name][table_name]
 4     #数据查询
 5     #query_set = admin_class.model.objects.all()
 6     #分页处理
 7     #1.分页对象参数构建:对象列表,每页显示数量
 8     #query_set_list = admin_class.model.objects.all()
 9     #延伸===>添加过滤条件
10     query_set_list, filter_conditions = table_filter(request, admin_class)
11     #延伸===>添加搜索功能
12     query_set_list, search_text = table_search(request, admin_class, query_set_list)
13     #2.分页对象创建
14     paginator = Paginator(query_set_list, admin_class.list_per_page)
15     #3.获取前端点击的页面数值
16     get_page = request.GET.get('page')
17     #4.页面异常处理
18     try:
19         #直接获取该页内容
20         query_set = paginator.page(get_page)
21     except PageNotAnInteger:
22         #不是整数值,跳转到首页
23         query_set = paginator.page(1)
24     except EmptyPage:
25         #超出范围,跳转到最后一页
26         query_set = paginator.page(paginator.num_pages)
27     return render(request, 'king_admin/table_objs.html',
28                                  {'admin_class': admin_class,
29                                   'query_set': query_set,
30                                   'filter_conditions': filter_conditions,
31                                   'search_text': search_text})

上述文件中添加的内容并不是很多,添加内容如下:

1 #延伸===>添加搜索功能
2     query_set_list, search_text = table_search(request, admin_class, query_set_list)
3      
4 #搜索结果作为参数返回
5   'search_text': search_text

2.5. 编写模板文件

这里就不需使用templatetags,直接在模板添加样式代码和后台数据参数,为了减少代码的重复,将搜索功能和过滤放在同一个表单里面:

 1 ...
 2                 <form class="" method="get">
 3                     {#条件过滤#}
 4                     {% for condition in admin_class.list_filters %}
 5                         <div class="col-lg-2">
 6                                 <span>{{ condition }}</span>
 7                                 {% render_filter_element condition admin_class filter_conditions %}
 8                         </div>
 9                     {% endfor %}
10                     <button type="SUBMIT" class="btn btn-success">检索</button>
11                 {# 添加搜索功能 #}
12                 <div class="row">
13                   <div class="col-lg-2" style="margin: auto 30px">
14                     <input type="search" name="_q" class="form-control" style="margin-left:15px" value="{{ search_text }}"
15                            placeholder="{% for search_field in admin_class.search_fields %}{{ search_field }},{% endfor %} ">
16                   </div>
17                   <button type="SUBMIT" class="btn btn-success">search</button>
18                   </div>
19                 </form>
20 ...
21 {# 添加搜索到分页 #}
22 {% create_page_element query_set filter_conditions search_text %}
23 ...

标签模板内的分页功能修改:

 1 ...
 2 #----------------------------分页优化处理-----------------------------
 3 @register.simple_tag
 4 def create_page_element(query_set, filter_conditions, search_text):
 5     '''返回整个分页元素'''
 6     page_btns = ''
 7     filters = ''
 8     #过滤条件
 9     for k, v in filter_conditions.items():
10         filters += '&{0}={1}'.format(k, v)
11     added_dot_ele = False #标志符
12     for page_num in query_set.paginator.page_range:
13         if page_num < 3 or page_num > query_set.paginator.num_pages -2 
14                 or abs(query_set.number - page_num) <= 2: #代表最前2页或最后2页 #abs判断前后1页
15             element_class = ""
16             if query_set.number == page_num:
17                 added_dot_ele = False
18                 element_class = "active"
19             page_btns += '''<li class="%s"><a href="?page=%s%s&_q=%s">%s</a></li>''' % (element_class, page_num,
20                                                                                         filters, search_text ,page_num)
21         else: #显示...
22             if added_dot_ele == False: #现在还没加...
23                 page_btns += '<li><a>...</a></li>'
24                 added_dot_ele = True
25     return mark_safe(page_btns)

添加搜索参数和拼接url

渲染后的页面效果:

2.6. BUG解决

搜索QQ号,居然出现这个的页面:不能搜索

这个问题在前面已经叙述过了,这个_q在数据中是没有的,我可以将其作为保留字!

还记得之前写了一个专门用来存储保留字的列表吗?我们只需要将_q添加到列表即可,在utils.py文件中的过滤功能函数里面:

 1 ----------------过滤功能------------------------------
 2 def table_filter(request, admin_class):
 3     """条件过滤,并构造滤后的数据结构"""
 4     filter_conditions = {}
 5     keywords = ['page', '_q'] #保留关键字
 6     for k, v in request.GET.items():
 7         if k in keywords:
 8             continue
 9         if v:
10             filter_conditions[k] = v
11     return admin_class.model.objects.filter(**filter_conditions), filter_conditions

完美解决问题!

3. 添加排序功能

3.1. 原生admin排序功能分析

如图:

看出默认情况下,Django是以二者进行降序排列的。当点击时,进行反序排列,还有点击取消功能。

3.2. 编写排序功能函数

在编写之前我们还是同样的要在king_admin中添加排序字段:

 1 #创建基类
 2 class BaseAdmin(object):
 3     list_display = []
 4     list_filter = []
 5     search_fields = []
 6     ordering = None  #添加此字段
 7     list_per_page = 10
 8 ...
 9 #自定义类,显示特定字段
10 class CustomerAdmin(BaseAdmin):
11     list_display = ['id', 'qq','name','source','consultant','consult_course','date','status']
12     list_filters = ['source','consultant','consult_course','status']
13     search_fields = ['qq', 'name', 'consultant__name', ]
14     ordering = 'date'
15     list_per_page = 2
16 ...

utils.py文件中,添加排序函数:

 1 ...
 2 #-----------------------排序功能-----------------------------------
 3 def table_sort(request, admin_class, query_set_list):
 4     """
 5     默认情况下,Djngo中取出来的数据是无序的
 6     :param request:
 7     :param query_set_list: 过滤、搜索之后的数据
 8     :return:
 9     """
10     #---------------------------初始化排序设定--------------------------------
11     # 默认排序条件---降序
12     king_admin_ordering = "-{0}".format(admin_class.ordering if admin_class.ordering else "-id")
13     # 获取排序后结果
14     order_by_init = query_set_list.order_by(king_admin_ordering)
15     #-----------------------------排序判断------------------------------------
16     #通过参数获取到结果,默认None,修改为空
17     order_by_text = request.GET.get('o', '')
18     #判断是否存在
19     if order_by_text:
20         #存在即根据获取字段排反序
21         order_result = order_by_init.order_by(order_by_text)
22         #下次获取到的数据排序,要取反结果即:添加或去除‘-’
23         #判断是否存在‘-’,存在就去除
24         if order_by_text.startswith('-'):
25             order_by_text = order_by_text.strip('-')
26         #没有就加上
27         else:
28             order_by_text = '-{0}'.format(order_by_text)
29     #不存在返回数据
30     else:
31         order_result = order_by_init
32     return  order_result, order_by_text

3.3. 编写视图函数

添加前,需要考虑一些问题:如何结合前面添加的功能? 也就是说,考虑到过滤,分页,搜索,统计等。按照前面的方式,我们都是以前面为基础,分页为后盾进行组合的,这里同样是可以的。

 1 ...
 2 def display_objects(request, app_name, table_name):
 3     #获取自定义的admin_class
 4     admin_class = enabled_admins[app_name][table_name]
 5     #数据查询
 6     #query_set = admin_class.model.objects.all()
 7     #分页处理
 8     #1.分页对象参数构建:对象列表,每页显示数量
 9     #query_set_list = admin_class.model.objects.all()
10     #延伸===>添加过滤条件
11     query_set_list, filter_conditions = table_filter(request, admin_class)
12     #延伸===>添加搜索功能
13     query_set_list, search_text = table_search(request, admin_class, query_set_list)
14     #延伸===>添加排序功能
15     query_set_list, order_by_text = table_sort(request, admin_class, query_set_list)
16     #2.分页对象创建
17     paginator = Paginator(query_set_list, admin_class.list_per_page)
18     #3.获取前端点击的页面数值
19     get_page = request.GET.get('page')
20     #4.页面异常处理
21     try:
22         #直接获取该页内容
23         query_set = paginator.page(get_page)
24     except PageNotAnInteger:
25         #不是整数值,跳转到首页
26         query_set = paginator.page(1)
27     except EmptyPage:
28         #超出范围,跳转到最后一页
29         query_set = paginator.page(paginator.num_pages)
30     return render(request, 'king_admin/table_objs.html',
31                                  {'admin_class': admin_class,
32                                   'query_set': query_set,
33                                   'filter_conditions': filter_conditions,
34                                   'search_text': search_text,
35                                   'order_by_text': order_by_text})
36 ...

上述文件添加的主要内容是:

1 #延伸===>添加排序功能
2   query_set_list, order_by_text = table_sort(request, admin_class, query_set_list)

返回数据的参数:'order_by_text': order_by_text

3.4. 编写模板文件

由于是结合前面的功能,这里我们要继续优化分页功能的templatetags里面的标签函数,其实就是在拼接的url里面添加一个额外的参数,目的是传递数据。

table_objs.html文件的分页标签修改:

1 ...
2  {% create_page_element query_set filter_conditions search_text order_by_text %}
3 ...

添加一个排序参数

标签内功能函数修改如下:

 1 ...
 2 #----------------------------分页优化处理-----------------------------
 3 @register.simple_tag
 4 def create_page_element(query_set, filter_conditions, search_text, order_by_text):
 5     '''返回整个分页元素'''
 6     page_btns = ''
 7     filters = ''
 8     #过滤条件
 9     for k, v in filter_conditions.items():
10         filters += '&{0}={1}'.format(k, v)
11     added_dot_ele = False #标志符
12     for page_num in query_set.paginator.page_range:
13         if page_num < 3 or page_num > query_set.paginator.num_pages -2 
14                 or abs(query_set.number - page_num) <= 2: #代表最前2页或最后2页 #abs判断前后1页
15             element_class = ""
16             if query_set.number == page_num:
17                 added_dot_ele = False
18                 element_class = "active"
19             page_btns += '''<li class="%s"><a href="?page=%s%s&_q=%s&o=%s">%s</a></li>''' % (element_class, page_num,
20                                                                                                 filters, search_text,
21                                                                                              order_by_text ,page_num)
22         else: #显示...
23             if added_dot_ele == False: #现在还没加...
24                 page_btns += '<li><a>...</a></li>'
25                 added_dot_ele = True
26     return mark_safe(page_btns)

添加排序参数和拼接url

基本的功能添加完毕,接下来就是添加触发按钮:通过字段进行触发排功能。

对显示字段进行一定的修改:

 1 ...
 2 <table class="table table-hover">
 3                   <thead>
 4                        <tr>
 5                             {% for title_name in admin_class.list_display %}
 6                                  {% create_table_title title_name order_by_text filter_conditions %}
 7                             {% endfor %}
 8                        </tr>
 9                   </thead>
10                   <tbody>
11 ...

标签功能函数的添加:

 1 #------------------------创建表头--------------------------------
 2 @register.simple_tag
 3 def create_table_title(title_name, order_by_text, filter_conditions):
 4     #过滤条件
 5     filters = ''
 6     for k, v in filter_conditions.items():
 7         filters += "&{0}={1}".format(k, v)
 8     #标签元素
 9     element = '''<th><a href="?{filters}&o={order_by_text}">{title_name}</a>{sort_icon}</th>'''
10     #判断排序数据
11     if order_by_text:
12         #升序箭头
13         if order_by_text.startswith("-"):
14             sort_icon = '''<span class="glyphicon glyphicon-chevron-up"></span>'''
15         #降序箭头
16         else:
17             sort_icon = '''<span class="glyphicon glyphicon-chevron-down"></span>'''
18         #对比字段
19         if order_by_text.strip("-") == title_name:
20             order_by_text = order_by_text
21         else:
22             order_by_text = title_name
23             sort_icon = ''
24     else:  # 没有排序
25         order_by_text = title_name
26         sort_icon = ''
27     ele = element.format(order_by_text=order_by_text, title_name=title_name, sort_icon=sort_icon, filters=filters)
28     return mark_safe(ele)

渲染效果如下:

在此 额外添加了一列 id列。

3.5. BUG修改

1.显示效果看着没有问题,当点击进行排序时,又出现之前的错误:

 解决:在过滤功能函数中添加为保留关键字

 1 ---------------过滤功能------------------------------
 2 def table_filter(request, admin_class):
 3     """条件过滤,并构造滤后的数据结构"""
 4     filter_conditions = {}
 5     keywords = ['page', '_q', 'o'] #保留关键字
 6     for k, v in request.GET.items():
 7         if k in keywords:
 8             continue
 9         if v:
10             filter_conditions[k] = v
11     return admin_class.model.objects.filter(**filter_conditions), filter_conditions

如下图:

不错,能进行正常的排序啦!但还是有点小缺点:怎么取消排序?后面优化的时候在添加吧。

4. 添加复选框 

4.1. 添加checkbox标签

为每行的开头添加一个checkbox,只需要在table_objs.html文件中的表格标签中添加:

 1 ...
 2             <table class="table table-hover">
 3                   <thead>
 4                        <tr>
 5                             {# 添加该行 #}
 6                             <th style=" 35px"><input type="checkbox" ></th>
 7                             {% for title_name in admin_class.list_display %}
 8                                  {% create_table_title title_name order_by_text filter_conditions %}
 9                             {% endfor %}
10                        </tr>
11                   </thead>
12                   <tbody>
13                       {% for item in query_set %}
14                         <tr>
15                            {# 添加该行 #} 
16                             <td ><input  type="checkbox" value=""></td>
17                             {#创建列表行数据#}
18                             {% create_row item admin_class %}
19                         </tr>
20                       {% endfor %}
21                   </tbody>
22               </table>
23 ...

4.2. 添加事件和value

对标题的checkbox添加全选和全取消点击事件,并将每行的数据id添加到value

 1 ...
 2                   <thead>
 3                        <tr>
 4                             <th style=" 35px"><input type="checkbox" onclick="checkAllToggle(this);"></th>
 5                             {% for title_name in admin_class.list_display %}
 6                                  {% create_table_title title_name order_by_text filter_conditions %}
 7                             {% endfor %}
 8                        </tr>
 9                   </thead>
10                   <tbody>
11                       {% for item in query_set %}
12                         <tr>
13                             <td ><input tag="object_checkbox" type="checkbox" value="{{ item.id }}"></td>
14                             {#创建列表行数据#}
15                             {% create_row item admin_class %}
16                         </tr>
17                       {% endfor %}
18                   </tbody>
19                    
20 ...          
21  <script>
22         function checkAllToggle(self) {
23             if($(self).prop('checked')){
24                 $('input[tag="object_checkbox"]').prop('checked', true);
25             }else{
26                 $('input[tag="object_checkbox"]').prop('checked', false);
27             }
28         }
29     </script>
30 {% endblock %}

JS脚本放在该文件的最下面即可,后面统一放到文件中。

渲染后的效果:

原文地址:https://www.cnblogs.com/eaglesour/p/8097927.html