zoukankan      html  css  js  c++  java
  • 项目一: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脚本放在该文件的最下面即可,后面统一放到文件中。

    渲染后的效果:

  • 相关阅读:
    原创:USB HID读卡器数据解析(R321-13.56MHZ读卡器)
    你对USB了解吗?--USB 协议分析之 HID 设备
    Python中的单例设计模式【多测师_王sir】
    把一个json文件写入到csv文件当中【多测师_王sir】
    pip安装本地文件报错处理方法【多测师_王sir】
    ddt数据驱动常见的用法【多测师_王sir】
    读取json数据转换为字典存入到列表当中【多测师_王sir】
    读取json数据输入键拿到对应的值【多测师_王sir】
    为什么要用cookie和session【多测师_王sir】
    Python2和Python3的区别【多测师_王sir】
  • 原文地址:https://www.cnblogs.com/eaglesour/p/8097927.html
Copyright © 2011-2022 走看看