项目一:CRM(客户关系管理系统)--5

Posted EagleSour

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了项目一: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 ...

添加一个排序参数

标签内功能函数

以上是关于项目一:CRM(客户关系管理系统)--5的主要内容,如果未能解决你的问题,请参考以下文章

阅读《31天学会CRM项目开发》 第二天

CRM客户关系管理系统商业项目

项目一:CRM(客户关系管理系统)--9

Django项目:CRM(客户关系管理系统)--45--36PerfectCRM实现CRM用户登陆注销02

CRM总结

项目一:CRM(客户关系管理系统)--2