@@ -54,7 +54,88 @@ urlpatterns = [
5454
5555### 生成前端统计图表
5656
57+ 如果项目中需要生成前端统计图表,可以使用百度的[ ECharts] ( < https://echarts.baidu.com/ > ) 。具体的做法是后端通过提供数据接口返回统计图表所需的数据,前端使用ECharts来渲染出柱状图、折线图、饼图、散点图等图表。例如我们要生成一个统计所有老师好评数和差评数的报表,可以按照下面的方式来做。
5758
59+ ``` Python
60+ def get_teachers_data (request ):
61+ # 查询所有老师的信息(注意:这个地方稍后也需要优化)
62+ queryset = Teacher.objects.all()
63+ # 用生成式将老师的名字放在一个列表中
64+ names = [teacher.name for teacher in queryset]
65+ # 用生成式将老师的好评数放在一个列表中
66+ good = [teacher.good_count for teacher in queryset]
67+ # 用生成式将老师的差评数放在一个列表中
68+ bad = [teacher.bad_count for teacher in queryset]
69+ # 返回JSON格式的数据
70+ return JsonResponse({' names' : names, ' good' : good, ' bad' : bad})
71+ ```
72+
73+ 映射URL。
74+
75+ ``` Python
76+ urlpatterns = [
77+ # 此处省略上面的代码
78+ path(' teachers_data/' , views.export_teachers_excel),
79+ # 此处省略下面的代码
80+ ]
81+ ```
82+
83+ 使用ECharts生成柱状图。
84+
85+ ``` HTML
86+ <!DOCTYPE html>
87+ <html lang =" en" >
88+ <head >
89+ <meta charset =" UTF-8" >
90+ <title >老师评价统计</title >
91+ </head >
92+ <body >
93+ <div id =" main" style =" width : 600px ; height : 400px " ></div >
94+ <p >
95+ <a href =" /" >返回首页</a >
96+ </p >
97+ <script src =" https://cdn.bootcss.com/echarts/4.2.1-rc1/echarts.min.js" ></script >
98+ <script >
99+ var myChart = echarts .init (document .querySelector (' #main' ))
100+ fetch (' /teachers_data/' )
101+ .then (resp => resp .json ())
102+ .then (json => {
103+ var option = {
104+ color: [' #f00' , ' #00f' ],
105+ title: {
106+ text: ' 老师评价统计图'
107+ },
108+ tooltip: {},
109+ legend: {
110+ data: [' 好评' , ' 差评' ]
111+ },
112+ xAxis: {
113+ data: json .names
114+ },
115+ yAxis: {},
116+ series: [
117+ {
118+ name: ' 好评' ,
119+ type: ' bar' ,
120+ data: json .good
121+ },
122+ {
123+ name: ' 差评' ,
124+ type: ' bar' ,
125+ data: json .bad
126+ }
127+ ]
128+ }
129+ myChart .setOption (option)
130+ })
131+ </script >
132+ </body >
133+ </html >
134+ ```
135+
136+ 运行效果如下图所示。
137+
138+ ![ ] ( ./res/echarts_bar_graph.png )
58139
59140### 配置日志
60141
@@ -223,4 +304,48 @@ Django-Debug-Toolbar是项目开发阶段辅助调试和优化的神器,只要
223304 urlpatterns.insert(0 , path(' __debug__/' , include(debug_toolbar.urls)))
224305 ```
225306
226- 4 . 使用 - 如下图所示,在配置好Django-Debug-Toolbar之后,页面右侧会看到一个调试工具栏,上面包括了如前所述的各种调试信息,包括执行时间、项目设置、请求头、SQL、静态资源、模板、缓存、信号等,查看起来非常的方便。
307+ 4 . 使用 - 如下图所示,在配置好Django-Debug-Toolbar之后,页面右侧会看到一个调试工具栏,上面包括了如前所述的各种调试信息,包括执行时间、项目设置、请求头、SQL、静态资源、模板、缓存、信号等,查看起来非常的方便。
308+
309+ ### 优化ORM代码
310+
311+ 在配置了日志或Django-Debug-Toolbar之后,我们可以查看一下之前将老师数据导出成Excel报表的视图函数执行情况,这里我们关注的是ORM框架生成的SQL查询到底是什么样子的,相信这里的结果会让你感到有一些意外。执行` Teacher.objects.all() ` 之后我们可以注意到,在控制台看到的或者通过Django-Debug-Toolbar输出的SQL是下面这样的:
312+
313+ ``` SQL
314+ SELECT ` tb_teacher` .` no` , ` tb_teacher` .` name` , ` tb_teacher` .` detail` , ` tb_teacher` .` photo` , ` tb_teacher` .` good_count` , ` tb_teacher` .` bad_count` , ` tb_teacher` .` sno` FROM ` tb_teacher` ; args= ()
315+ SELECT ` tb_subject` .` no` , ` tb_subject` .` name` , ` tb_subject` .` intro` , ` tb_subject` .` create_date` , ` tb_subject` .` is_hot` FROM ` tb_subject` WHERE ` tb_subject` .` no` = 101 ; args= (101 ,)
316+ SELECT ` tb_subject` .` no` , ` tb_subject` .` name` , ` tb_subject` .` intro` , ` tb_subject` .` create_date` , ` tb_subject` .` is_hot` FROM ` tb_subject` WHERE ` tb_subject` .` no` = 101 ; args= (101 ,)
317+ SELECT ` tb_subject` .` no` , ` tb_subject` .` name` , ` tb_subject` .` intro` , ` tb_subject` .` create_date` , ` tb_subject` .` is_hot` FROM ` tb_subject` WHERE ` tb_subject` .` no` = 101 ; args= (101 ,)
318+ SELECT ` tb_subject` .` no` , ` tb_subject` .` name` , ` tb_subject` .` intro` , ` tb_subject` .` create_date` , ` tb_subject` .` is_hot` FROM ` tb_subject` WHERE ` tb_subject` .` no` = 101 ; args= (101 ,)
319+ SELECT ` tb_subject` .` no` , ` tb_subject` .` name` , ` tb_subject` .` intro` , ` tb_subject` .` create_date` , ` tb_subject` .` is_hot` FROM ` tb_subject` WHERE ` tb_subject` .` no` = 103 ; args= (103 ,)
320+ SELECT ` tb_subject` .` no` , ` tb_subject` .` name` , ` tb_subject` .` intro` , ` tb_subject` .` create_date` , ` tb_subject` .` is_hot` FROM ` tb_subject` WHERE ` tb_subject` .` no` = 103 ; args= (103 ,)
321+ ```
322+
323+ 这里的问题通常被称为“1+N查询”(或“N+1查询”),原本获取老师的数据只需要一条SQL,但是由于老师关联了学科,当我们查询到N条老师的数据时,Django的ORM框架又向数据库发出了N条SQL去查询老师所属学科的信息。每条SQL执行都会有较大的开销而且会给数据库服务器带来压力,如果能够在一条SQL中完成老师和学科的查询肯定是更好的做法,这一点也很容易做到,相信大家已经想到怎么做了。是的,我们可以使用连接查询,但是在使用Django的ORM框架时如何做到这一点呢?对于多对一关联(如投票应用中的老师和学科),我们可以使用` QuerySet ` 的用` select_related() ` 方法来加载关联对象;而对于多对多关联(如电商网站中的订单和商品),我们可以使用` prefetch_related() ` 方法来加载关联对象。
324+
325+ 在导出老师Excel报表的视图函数中,我们可以按照下面的方式优化代码。
326+
327+ ``` Python
328+ queryset = Teacher.objects.all().select_related(' subject' )
329+ ```
330+
331+ 事实上,用ECharts生成前端报表的视图函数中,查询老师好评和差评数据的操作也能够优化,因为在这个例子中,我们只需要获取老师的姓名、好评数和差评数这三项数据,但是在默认的情况生成的SQL会查询老师表的所有字段。可以用` QuerySet ` 的` only() ` 方法来指定需要查询的属性,也可以用` QuerySet ` 的` defer() ` 方法来指定暂时不需要查询的属性,这样生成的SQL会通过投影操作来指定需要查询的列,从而改善查询性能,代码如下所示:
332+
333+ ``` Python
334+ queryset = Teacher.objects.all().only(' name' , ' good_count' , ' bad_count' )
335+ ```
336+
337+ 当然,如果要统计出每个学科的老师好评和差评的平均数,利用Django的ORM框架也能够做到,代码如下所示:
338+
339+ ``` Python
340+ queryset = Teacher.objects.values(' subject' ).annotate(
341+ good = Avg(' good_count' ), bad = Avg(' bad_count' ))
342+ ```
343+
344+ 这里获得的` QuerySet ` 中的元素是字典对象,每个字典中有三组键值对,分别是代表学科编号的` subject ` 、代表好评数的` good ` 和代表差评数的` bad ` 。如果想要获得学科的名称而不是编号,可以按照如下所示的方式调整代码:
345+
346+ ``` Python
347+ queryset = Teacher.objects.values(' subject__name' ).annotate(
348+ good = Avg(' good_count' ), bad = Avg(' bad_count' ))
349+ ```
350+
351+ 可见,Django的ORM框架允许我们用面向对象的方式完成关系数据库中的分组和聚合查询。
0 commit comments