项目一:CRM(客户关系管理系统)--4过滤功能和分页功能

在上篇文章中,我们仅仅是展示了最基础的表格字段的内容,这篇文章我们来添加过滤功能和分页功能!

1. 添加分页功能

 

  1 In [189]: from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
  2 
  3 In [190]: Paginator?
  4 Init signature: Paginator(object_list, per_page, orphans=0, allow_empty_first_page=True)
  5 Docstring:      <no docstring>
  6 File:           /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/core/paginator.py
  7 Type:           type
  8 
  9 In [191]: list1 = [1,2,3,4,5,6,7,8,9,10,11,12]
 10 
 11 In [192]: pagination = Paginator(list1, 3)
 12 
 13 In [193]: dir(pagination)
 14 Out[193]: 
 15 ['__class__',
 16  '__delattr__',
 17  '__dict__',
 18  '__dir__',
 19  '__doc__',
 20  '__eq__',
 21  '__format__',
 22  '__ge__',
 23  '__getattribute__',
 24  '__gt__',
 25  '__hash__',
 26  '__init__',
 27  '__init_subclass__',
 28  '__le__',
 29  '__lt__',
 30  '__module__',
 31  '__ne__',
 32  '__new__',
 33  '__reduce__',
 34  '__reduce_ex__',
 35  '__repr__',
 36  '__setattr__',
 37  '__sizeof__',
 38  '__str__',
 39  '__subclasshook__',
 40  '__weakref__',
 41  '_check_object_list_is_ordered',
 42  '_get_page',
 43  'allow_empty_first_page',
 44  'count',
 45  'num_pages',
 46  'object_list',
 47  'orphans',
 48  'page',
 49  'page_range',
 50  'per_page',
 51  'validate_number']
 52 
 53 In [194]: [item for item in dir(pagination) if not item.startswith('_')]
 54 Out[194]: 
 55 ['allow_empty_first_page',
 56  'count',
 57  'num_pages',
 58  'object_list',
 59  'orphans',
 60  'page',
 61  'page_range',
 62  'per_page',
 63  'validate_number']
 64 
 65 In [195]: pagination.count
 66 Out[195]: 12
 67 
 68 In [196]: pagination.num_pages
 69 Out[196]: 4
 70 In [198]: pagination.page(1)
 71 Out[198]: <Page 1 of 4>
 72 
 73 In [199]: pagination.page_range
 74 Out[199]: range(1, 5)
 75 
 76 In [200]: pagination.validate_number?
 77 Signature: pagination.validate_number(number)
 78 Docstring: Validates the given 1-based page number.
 79 File:      /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/core/paginator.py
 80 Type:      method
 81 
 82 In [201]: pagination.validate_number(1)
 83 Out[201]: 1
 84 In [202]: pagination.validate_number(5)
 85 ---------------------------------------------------------------------------
 86 EmptyPage                                 Traceback (most recent call last)
 87 <ipython-input-202-b3575c7c07a1> in <module>()
 88 ----> 1 pagination.validate_number(5)
 89 
 90 /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/core/paginator.py in validate_number(self, number)
 91      48                 pass
 92      49             else:
 93 ---> 50                 raise EmptyPage(_('That page contains no results'))
 94      51         return number
 95      52 
 96 
 97 EmptyPage: That page contains no results
 98 
 99 In [203]: pagination.validate_number(4)
100 Out[203]: 4
101 In [205]: pagination.object_list
102 Out[205]: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
103 
104 In [210]: pagination.page(1).object_list
105 Out[210]: [1, 2, 3]
106 
107 In [211]: pagination.page(2).object_list
108 Out[211]: [4, 5, 6]
109 
110 In [212]: pagination.page(3).object_list
111 Out[212]: [7, 8, 9]
112 
113 In [213]: pagination.page(4).object_list
114 Out[213]: [10, 11, 12]
115 
116 In [227]: pagination.page(4).number
117 Out[227]: 4
118 
119 In [228]: pagination.page(4).index(12)
120 Out[228]: 2
121 
122 In [229]: pagination.page(4).object_list
123 Out[229]: [10, 11, 12]
124 
125 In [230]: pagination.page(4).start_index()
126 Out[230]: 10
127 
128 In [231]: pagination.page(4).end_index()
129 Out[231]: 12
130 
131 In [232]: pagination.page(4).has_previous()
132 Out[232]: True
133 
134 In [233]: pagination.page(4).has_next()
135 Out[233]: False
136 
137 In [234]: pagination.page(2).next_page_number()
138 Out[234]: 3

 

1.1. 添加分页字段

king_admin.py文件中添加分页字段,表示每页显示多少内容,如下:

1 ...
2 #创建基类
3 class BaseAdmin(object):
4     list_display = []
5     list_filter = []
6     list_per_page = 2  #添加此数据
7 ...

如果我们需要修改每页要显示的内容数量时,同样是在king_admin中,只需要在自定义的子类中添加即可。如下:

1 #自定义类,显示特定字段
2 class CustomerAdmin(BaseAdmin):
3     list_display = ['qq','name','source','consultant','consult_course','date','status']
4     list_filters = ['source','consultant','consult_course','status']
5     list_per_page = 2 #添加此数据覆盖基类数据

 

1.2. 编写视图函数

Django中,已经为我们提供了封装好的分页模块,只需要引用即可!具体的使用看看官网的样例就明白了。

啥也不说,直接上代码:

 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     #2.分页对象创建
11     paginator = Paginator(query_set_list, admin_class.list_per_page)
12     #3.获取前端点击的页面数值
13     get_page = request.GET.get('page')
14     #4.页面异常处理
15     try:
16         #直接获取该页内容
17         query_set = paginator.page(get_page)
18     except PageNotAnInteger:
19         #不是整数值,跳转到首页
20         query_set = paginator.page(1)
21     except EmptyPage:
22         #超出范围,跳转到最后一页
23         query_set = paginator.page(paginator.num_pages)
24     return render(request, 'king_admin/table_objs.html',
25                                  {'admin_class': admin_class,
26                                   'query_set': query_set})

 

In [189]: from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage

In [190]: Paginator?
Init signature: Paginator(object_list, per_page, orphans=0, allow_empty_first_page=True)
Docstring:      <no docstring>
File:           /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/core/paginator.py
Type:           type

In [191]: list1 = [1,2,3,4,5,6,7,8,9,10,11,12]

In [192]: pagination = Paginator(list1, 3)

In [193]: dir(pagination)
Out[193]: 
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 '_check_object_list_is_ordered',
 '_get_page',
 'allow_empty_first_page',
 'count',
 'num_pages',
 'object_list',
 'orphans',
 'page',
 'page_range',
 'per_page',
 'validate_number']

In [194]: [item for item in dir(pagination) if not item.startswith('_')]
Out[194]: 
['allow_empty_first_page',
 'count',
 'num_pages',
 'object_list',
 'orphans',
 'page',
 'page_range',
 'per_page',
 'validate_number']

In [195]: pagination.count    # 数据总数
Out[195]: 12

In [196]: pagination.num_pages  # 共分了多少页
Out[196]: 4
In [198]: pagination.page(1)    
Out[198]: <Page 1 of 4>

In [199]: pagination.page_range  # 页码数列
Out[199]: range(1, 5)

In [200]: pagination.validate_number?
Signature: pagination.validate_number(number)
Docstring: Validates the given 1-based page number.
File:      /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/core/paginator.py
Type:      method

In [201]: pagination.validate_number(1)
Out[201]: 1
In [202]: pagination.validate_number(5)
---------------------------------------------------------------------------
EmptyPage                                 Traceback (most recent call last)
<ipython-input-202-b3575c7c07a1> in <module>()
----> 1 pagination.validate_number(5)

/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/core/paginator.py in validate_number(self, number)
     48                 pass
     49             else:
---> 50                 raise EmptyPage(_('That page contains no results'))
     51         return number
     52 

EmptyPage: That page contains no results

In [203]: pagination.validate_number(4)  # 是由有页码4
Out[203]: 4
In [205]: pagination.object_list      # 所有数据表项
Out[205]: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

In [210]: pagination.page(1).object_list  # 第一页的所有数据
Out[210]: [1, 2, 3]

In [211]: pagination.page(2).object_list  # 第二页的所有数据
Out[211]: [4, 5, 6]

In [212]: pagination.page(3).object_list  # 第三页的所有数据
Out[212]: [7, 8, 9]

In [213]: pagination.page(4).object_list  # 第四页的所有数据
Out[213]: [10, 11, 12]
# 分页对象的常用方法
In [226]: pagination.page(4).index(12)  # 数据12所在页的位置
Out[226]: 2

In [227]: pagination.page(4).number    # 页的页码
Out[227]: 4

In [229]: pagination.page(4).object_list  # 页的所有数据
Out[229]: [10, 11, 12]

In [230]: pagination.page(4).start_index()  # 页的开始项
Out[230]: 10

In [231]: pagination.page(4).end_index()  # 页的结束项
Out[231]: 12

In [232]: pagination.page(4).has_previous()  # 页是否有上一页
Out[232]: True

In [233]: pagination.page(4).has_next()    # 页是否有下一页
Out[233]: False

In [234]: pagination.page(2).next_page_number()  # 页的下一页代码
Out[234]: 3

 

1.3. 编写模板文件

此时,我们需要对二级页面添加一些内容,用于显示分页效果,如下:

 1 {% extends 'king_admin/table_index.html' %}
 2 {% load tags %}
 3 {% block container %}
 4     <div class="panel panel-info">
 5           <div class="panel-heading">
 6             <h3 class="panel-title">Panel title</h3>
 7           </div>
 8           <div class="panel-body">
 9               {#具体的表格内容展示 #}
10               <table class="table table-hover">
11                   <thead>
12                        <tr>
13                             {% for title_name in admin_class.list_display %}
14                                  <th>{{ title_name }}</th>
15                             {% endfor %}
16                        </tr>
17                   </thead>
18                   <tbody>
19                       {% for item in query_set %}
20                         <tr>
21                             {#创建列表行数据#}
22                             {% create_row item admin_class %}
23                         </tr>
24                       {% endfor %}
25                   </tbody>
26               </table>
27               <nav>
28               {#分页处理#}
29               <ul class="pagination">
30                     {#判断是否有上一页#}
31                     {% if query_set.has_previous %}
32                         <li class=""><a href="?page={{ query_set.previous_page_number }}">上一页</a></li>
33                     {% endif %}
34                     {% create_page_element query_set %}
35                     {#判断是否有下一页#}
36                     {% if query_set.has_next %}
37                         <li class=""><a href="?page={{ query_set.next_page_number }}">下一页</a></li>
38                     {% endif %}
39               </ul>
40               </nav>
41           </div>
42     </div>
43 {% endblock %}

标签文件的内容:

 1 from  django  import template
 2 from  django.utils.safestring import mark_safe
 3 register = template.Library()
 4 #------------------------显示表名称-->中文---------------------------
 5 @register.simple_tag
 6 def render_verbose_name(admin_class):
 7     return admin_class.model._meta.verbose_name
 8 #-------------------------创建表格行数据-----------------------------
 9 @register.simple_tag
10 def create_row(query_set_obj, admin_class):
11     #创建标签元素--空,None不行
12     element = ''
13     #遍历要显示的models字段
14     for row in admin_class.list_display:
15     #获取显示字段对应的字段对象
16         field_obj = admin_class.model._meta.get_field(row)
17     #获取数据
18         #判断choice
19         if field_obj.choices:
20             #通过反射获取对象里面的值,并执行该方法get_字段_display()获取choices里面的数值
21             row_data = getattr(query_set_obj, 'get_{0}_display'.format(row))()
22         else:
23             row_data = getattr(query_set_obj, row)
24         #时间格式转换
25         if type(row_data).__name__ == 'datetime':
26             row_data = row_data.strftime('%Y-%m-%d %H-%M-%S')
27         #标签元素的拼接
28         element += "<td>{0}</td>".format(row_data)
29     return mark_safe(element)
30 #-----------------------------分页处理---------------------------------
31 @register.simple_tag
32 def create_page_element(query_set):
33     '''返回整个分页元素'''
34     page_btns = ''
35     added_dot_ele = False #标志符
36     for page_num in query_set.paginator.page_range:
37         if page_num < 3 or page_num > query_set.paginator.num_pages -2 \
38                 or abs(query_set.number - page_num) <= 2: #代表最前2页或最后2页 #abs判断前后1页
39             element_class = ""
40             if query_set.number == page_num:
41                 added_dot_ele = False
42                 element_class = "active"
43             page_btns += '''<li class="%s"><a href="?page=%s">%s</a></li>''' % (element_class, page_num, page_num)
44         else: #显示...
45             if added_dot_ele == False: #现在还没加...
46                 page_btns += '<li><a>...</a></li>'
47                 added_dot_ele = True
48     return mark_safe(page_btns)

渲染之后的效果:

2. 添加过滤功能

2.1. 添加过滤字段

同样是在king_admin.py中添加过滤字段,可以自己添加过滤条件,添加内容如下:

1 ...
2 #创建基类
3 class BaseAdmin(object):
4     list_display = []
5     list_filter = [] #添加此数据
6     list_per_page = 2 
7 ...

如果我们需要修改每页的过滤条件时,同样是在king_admin中,只需要在自定义的子类中添加即可。如下:

1 #自定义类,显示特定字段
2 class CustomerAdmin(BaseAdmin):
3     list_display = ['qq','name','source','consultant','consult_course','date','status']
4     list_filters = ['source','consultant','consult_course','status'] #添加此数据覆盖基类数据
5     list_per_page = 2

2.2. 编写过滤功能函数

将过滤功能独立出来,便于我们对后面的添加的功能更好的结合与修改,在king_admin应用目录下创建utils.py文件,并编写过滤功能函数:

1 ----------------过滤功能------------------------------
2 def table_filter(request, admin_class):
3     """条件过滤,并构造滤后的数据结构"""
4     filter_conditions = {}
5     for k, v in request.GET.items():
6         if v:
7             filter_conditions[k] = v
8     return admin_class.model.objects.filter(**filter_conditions), filter_conditions

 

2.3. 编写视图函数

在基于分页功能的基础,添加过滤功能:

 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     #2.分页对象创建
12     paginator = Paginator(query_set_list, admin_class.list_per_page)
13     #3.获取前端点击的页面数值
14     get_page = request.GET.get('page')
15     #4.页面异常处理
16     try:
17         #直接获取该页内容
18         query_set = paginator.page(get_page)
19     except PageNotAnInteger:
20         #不是整数值,跳转到首页
21         query_set = paginator.page(1)
22     except EmptyPage:
23         #超出范围,跳转到最后一页
24         query_set = paginator.page(paginator.num_pages)
25     return render(request, 'king_admin/table_objs.html',
26                                  {'admin_class': admin_class,
27                                   'query_set': query_set,
28                                   'filter_conditions': filter_conditions})

 

2.4. 编写模板文件

因为过滤的条件需要提交后台,所以要构建表单,在table_ojs.html文件中添加如下内容:

 1 ...
 2           <div class="panel-body">
 3                <div class="row">
 4                 <form class="" method="get">
 5                     {#条件过滤#}
 6                     {% for condition in admin_class.list_filters %}
 7                         <div class="col-lg-2">
 8                                 <span>{{ condition }}</span>
 9                                 {% render_filter_element condition admin_class filter_conditions %}
10                         </div>
11                     {% endfor %}
12                     <button type="SUBMIT" class="btn btn-success">检索</button>
13                 </form>
14                </div>
15               {#具体的表格内容展示 #}
16               <table class="table table-hover">
17 ...

标签文件内容:

 1 from  django  import template
 2 from  django.utils.safestring import mark_safe
 3 register = template.Library()
 4 #------------------------显示表名称-->中文---------------------------
 5 @register.simple_tag
 6 def render_verbose_name(admin_class):
 7     return admin_class.model._meta.verbose_name
 8 #-------------------------创建表格行数据-----------------------------
 9 @register.simple_tag
10 def create_row(query_set_obj, admin_class):
11     #创建标签元素--空,None不行
12     element = ''
13     #遍历要显示的models字段
14     for row in admin_class.list_display:
15     #获取显示字段对应的字段对象
16         field_obj = admin_class.model._meta.get_field(row)
17     #获取数据
18         #判断choice
19         if field_obj.choices:
20             #通过反射获取对象里面的值,并执行该方法get_字段_display()获取choices里面的数值
21             row_data = getattr(query_set_obj, 'get_{0}_display'.format(row))()
22         else:
23             row_data = getattr(query_set_obj, row)
24         #时间格式转换
25         if type(row_data).__name__ == 'datetime':
26             row_data = row_data.strftime('%Y-%m-%d %H-%M-%S')
27         #标签元素的拼接
28         element += "<td>{0}</td>".format(row_data)
29     return mark_safe(element)
30 #-----------------------------分页处理---------------------------------
31 @register.simple_tag
32 def create_page_element(query_set):
33     '''返回整个分页元素'''
34     page_btns = ''
35     added_dot_ele = False #标志符
36     for page_num in query_set.paginator.page_range:
37         if page_num < 3 or page_num > query_set.paginator.num_pages -2 \
38                 or abs(query_set.number - page_num) <= 2: #代表最前2页或最后2页 #abs判断前后1页
39             element_class = ""
40             if query_set.number == page_num:
41                 added_dot_ele = False
42                 element_class = "active"
43             page_btns += '''<li class="%s"><a href="?page=%s">%s</a></li>''' % (element_class, page_num, page_num)
44         else: #显示...
45             if added_dot_ele == False: #现在还没加...
46                 page_btns += '<li><a>...</a></li>'
47                 added_dot_ele = True
48     return mark_safe(page_btns)
49 #--------------------------------过滤条件处理-----------------------------
50 @register.simple_tag
51 def render_filter_element(condition, admin_class, filter_conditions):
52     #初始化下拉框
53     select_element = """<select class='form-control' name={0}><option value=''>------
54                                                                     </option>""".format(condition)
55     #获取字段
56     field_object = admin_class.model._meta.get_field(condition)
57     #字段处理
58     # 默认不选中
59     selected = ''
60     #choice处理
61     if field_object.choices:
62         #遍历choices值
63         for choice_item in field_object.get_choices()[1:]:
64             # print(choice_item)
65         #判断选择条件是否和choice值相等
66             if filter_conditions.get(condition) == str(choice_item[0]):
67                 #被选中
68                 selected = 'selected'
69             select_element += """<option value='{0}' {1}>{2}</option>""".format(choice_item[0],
70                                                                             selected, choice_item[1])
71             selected = ''
72     #外键处理
73     if type(field_object).__name__ == 'ForeignKey':
74         for choice_item in field_object.get_choices()[1:]:
75             # 判断选择条件是否和choice值相等
76             if filter_conditions.get(condition) == str(choice_item[0]):
77                 # 被选中
78                 selected = 'selected'
79             select_element += """<option value='{0}' {1}>{2}</option>""".format(choice_item[0],
80                                                                                 selected, choice_item[1])
81             selected = ''
82     select_element += '</select>'
83     return mark_safe(select_element)

渲染后的结果:

 

2.5. BUG解决

搜索后对结果很是满意,分页显示数量也是正确,然而,当点击下一页的时候确调回了初始状态!这是为什么呢??

其实很简单,我们在做分页处理的时候并没有考虑到过滤参数,只需要在分页标签中添加过滤参数到传输数据的url中即可!

标签文件中的分页处理修改如下:

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

上述文件中只是将过滤的参数值添加到提交url的标签中。

当然,在模板文件table_objs.html中,我们也要有相应的修改,这里就简单了只需要添加一个参数(filter_conditions)即可:

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

至此,BUG解决了,可以完美的进行过滤,分页啦!

2.6. 优化

还有一点值得考虑,就是page这个关键字,是否会影响到数据的存储和查询?为了避免这样的隐患存在,我们可以将这个参数作为保留字,不允许在数据库中使用!

那么,该如何实现呢?其实很简答喽,我只需要在过滤功能函数那里添加一个判断就行啦!如下:

 1 #--------------------------过滤功能------------------------------
 2 def table_filter(request, admin_class):
 3     """条件过滤,并构造滤后的数据结构"""
 4     filter_conditions = {}
 5     keywords = ['page'] #保留关键字
 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

这里我们以列表的形式,便于后期扩展添加其他的保留字!

posted @ 2017-12-24 13:30  EagleSour  阅读(780)  评论(0编辑  收藏  举报