简单基础的功能添加是比较漫长的道路,前面添加了分页和过滤功能,接下来添加的仍然会是一些琐碎而又常用的功能。
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="width: 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="width: 35px"><input type="checkbox" οnclick="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
脚本放在该文件的最下面即可,后面统一放到文件中。
渲染后的效果: