diff --git "a/Day41-55/46.\346\212\245\350\241\250\345\222\214\346\227\245\345\277\227.md" "b/Day41-55/46.\346\212\245\350\241\250\345\222\214\346\227\245\345\277\227.md" index 30e69830393c3c569abc45752047b70616f1ccd4..bf25ab2f5e19d14580cf21016aae4364eac80680 100644 --- "a/Day41-55/46.\346\212\245\350\241\250\345\222\214\346\227\245\345\277\227.md" +++ "b/Day41-55/46.\346\212\245\350\241\250\345\222\214\346\227\245\345\277\227.md" @@ -54,7 +54,88 @@ urlpatterns = [ ### 生成前端统计图表 +如果项目中需要生成前端统计图表,可以使用百度的[ECharts]()。具体的做法是后端通过提供数据接口返回统计图表所需的数据,前端使用ECharts来渲染出柱状图、折线图、饼图、散点图等图表。例如我们要生成一个统计所有老师好评数和差评数的报表,可以按照下面的方式来做。 +```Python +def get_teachers_data(request): + # 查询所有老师的信息(注意:这个地方稍后也需要优化) + queryset = Teacher.objects.all() + # 用生成式将老师的名字放在一个列表中 + names = [teacher.name for teacher in queryset] + # 用生成式将老师的好评数放在一个列表中 + good = [teacher.good_count for teacher in queryset] + # 用生成式将老师的差评数放在一个列表中 + bad = [teacher.bad_count for teacher in queryset] + # 返回JSON格式的数据 + return JsonResponse({'names': names, 'good': good, 'bad': bad}) +``` + +映射URL。 + +```Python +urlpatterns = [ + # 此处省略上面的代码 + path('teachers_data/', views.export_teachers_excel), + # 此处省略下面的代码 +] +``` + +使用ECharts生成柱状图。 + +```HTML + + + + + 老师评价统计 + + +
+

+ 返回首页 +

+ + + + +``` + +运行效果如下图所示。 + +![](./res/echarts_bar_graph.png) ### 配置日志 @@ -223,4 +304,48 @@ Django-Debug-Toolbar是项目开发阶段辅助调试和优化的神器,只要 urlpatterns.insert(0, path('__debug__/', include(debug_toolbar.urls))) ``` -4. 使用 - 如下图所示,在配置好Django-Debug-Toolbar之后,页面右侧会看到一个调试工具栏,上面包括了如前所述的各种调试信息,包括执行时间、项目设置、请求头、SQL、静态资源、模板、缓存、信号等,查看起来非常的方便。 \ No newline at end of file +4. 使用 - 如下图所示,在配置好Django-Debug-Toolbar之后,页面右侧会看到一个调试工具栏,上面包括了如前所述的各种调试信息,包括执行时间、项目设置、请求头、SQL、静态资源、模板、缓存、信号等,查看起来非常的方便。 + +### 优化ORM代码 + +在配置了日志或Django-Debug-Toolbar之后,我们可以查看一下之前将老师数据导出成Excel报表的视图函数执行情况,这里我们关注的是ORM框架生成的SQL查询到底是什么样子的,相信这里的结果会让你感到有一些意外。执行`Teacher.objects.all()`之后我们可以注意到,在控制台看到的或者通过Django-Debug-Toolbar输出的SQL是下面这样的: + +```SQL +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=() +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,) +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,) +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,) +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,) +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,) +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,) +``` + +这里的问题通常被称为“1+N查询”(或“N+1查询”),原本获取老师的数据只需要一条SQL,但是由于老师关联了学科,当我们查询到N条老师的数据时,Django的ORM框架又向数据库发出了N条SQL去查询老师所属学科的信息。每条SQL执行都会有较大的开销而且会给数据库服务器带来压力,如果能够在一条SQL中完成老师和学科的查询肯定是更好的做法,这一点也很容易做到,相信大家已经想到怎么做了。是的,我们可以使用连接查询,但是在使用Django的ORM框架时如何做到这一点呢?对于多对一关联(如投票应用中的老师和学科),我们可以使用`QuerySet`的用`select_related()`方法来加载关联对象;而对于多对多关联(如电商网站中的订单和商品),我们可以使用`prefetch_related()`方法来加载关联对象。 + +在导出老师Excel报表的视图函数中,我们可以按照下面的方式优化代码。 + +```Python +queryset = Teacher.objects.all().select_related('subject') +``` + +事实上,用ECharts生成前端报表的视图函数中,查询老师好评和差评数据的操作也能够优化,因为在这个例子中,我们只需要获取老师的姓名、好评数和差评数这三项数据,但是在默认的情况生成的SQL会查询老师表的所有字段。可以用`QuerySet`的`only()`方法来指定需要查询的属性,也可以用`QuerySet`的`defer()`方法来指定暂时不需要查询的属性,这样生成的SQL会通过投影操作来指定需要查询的列,从而改善查询性能,代码如下所示: + +```Python +queryset = Teacher.objects.all().only('name', 'good_count', 'bad_count') +``` + +当然,如果要统计出每个学科的老师好评和差评的平均数,利用Django的ORM框架也能够做到,代码如下所示: + +```Python +queryset = Teacher.objects.values('subject').annotate( + good=Avg('good_count'), bad=Avg('bad_count')) +``` + +这里获得的`QuerySet`中的元素是字典对象,每个字典中有三组键值对,分别是代表学科编号的`subject`、代表好评数的`good`和代表差评数的`bad`。如果想要获得学科的名称而不是编号,可以按照如下所示的方式调整代码: + +```Python +queryset = Teacher.objects.values('subject__name').annotate( + good=Avg('good_count'), bad=Avg('bad_count')) +``` + +可见,Django的ORM框架允许我们用面向对象的方式完成关系数据库中的分组和聚合查询。 \ No newline at end of file diff --git "a/Day41-55/47.\344\270\255\351\227\264\344\273\266\347\232\204\345\272\224\347\224\250.md" "b/Day41-55/47.\344\270\255\351\227\264\344\273\266\347\232\204\345\272\224\347\224\250.md" index 837bd169c8e2231f6ed0df2662cca792660732af..1f4c320a37a29800b53f74ff6205e92a73e25b73 100644 --- "a/Day41-55/47.\344\270\255\351\227\264\344\273\266\347\232\204\345\272\224\347\224\250.md" +++ "b/Day41-55/47.\344\270\255\351\227\264\344\273\266\347\232\204\345\272\224\347\224\250.md" @@ -101,7 +101,7 @@ from django.shortcuts import redirect # 需要登录才能访问的资源路径 LOGIN_REQUIRED_URLS = { - '/praise/', '/criticize/', '/pdf/', '/excel/', + '/praise/', '/criticize/', '/excel/', '/teachers_data/', } @@ -143,4 +143,8 @@ MIDDLEWARE = [ 注意上面这个中间件列表中元素的顺序,当收到来自用户的请求时,中间件按照从上到下的顺序依次执行,这行完这些中间件以后,请求才会最终到达视图函数。当然,在这个过程中,用户的请求可以被拦截,就像上面我们自定义的中间件那样,如果用户在没有登录的情况下访问了受保护的资源,中间件会将请求直接重定向到登录页,后面的中间件和视图函数将不再执行。在响应用户请求的过程中,上面的中间件会按照从下到上的顺序依次执行,这样的话我们还可以对响应做进一步的处理。 -中间件执行的顺序是非常重要的,对于有依赖关系的中间件必须保证被依赖的中间件要置于依赖它的中间件的前面,就好比我们刚才自定义的中间件要放到`SessionMiddleware`的后面,因为我们要依赖这个中间件为请求绑定的`session`对象才能判定用户是否登录。 \ No newline at end of file +中间件执行的顺序是非常重要的,对于有依赖关系的中间件必须保证被依赖的中间件要置于依赖它的中间件的前面,就好比我们刚才自定义的中间件要放到`SessionMiddleware`的后面,因为我们要依赖这个中间件为请求绑定的`session`对象才能判定用户是否登录。 + +### 小结 + +至此,除了对用户投票数量加以限制的功能外,这个投票应用就算基本完成了,整个项目的完整代码请参考,其中用户注册时使用的手机验证码功能请大家使用自己注册的短信平台替代它。 \ No newline at end of file diff --git a/Day41-55/res/echarts_bar_graph.png b/Day41-55/res/echarts_bar_graph.png new file mode 100644 index 0000000000000000000000000000000000000000..a77571f7ad57622bf7da12465a630ed5bd2fa57b Binary files /dev/null and b/Day41-55/res/echarts_bar_graph.png differ diff --git "a/Day91-100/95.\344\275\277\347\224\250Django\345\274\200\345\217\221\351\241\271\347\233\256.md" "b/Day91-100/95.\344\275\277\347\224\250Django\345\274\200\345\217\221\351\241\271\347\233\256.md" index 924c69a42e9325e329fca3271ceb4745ee92f611..3ee684f6c4bbf37c0031c0f88c5757d5e1a70650 100644 --- "a/Day91-100/95.\344\275\277\347\224\250Django\345\274\200\345\217\221\351\241\271\347\233\256.md" +++ "b/Day91-100/95.\344\275\277\347\224\250Django\345\274\200\345\217\221\351\241\271\347\233\256.md" @@ -6,7 +6,7 @@ 问题1:描述一个Web应用的工作流程。 -![s](./res/web-application.png) +![](./res/web-application.png) 问题2:描述项目的物理架构。(上图中补充负载均衡(反向代理)服务器、数据库服务器、文件服务器、邮件服务器、缓存服务器、防火墙等,而且每个节点都有可能是多节点构成的集群,如下图所示,架构并不是一开始就是这样,而是逐步演进的) diff --git "a/\346\233\264\346\226\260\346\227\245\345\277\227.md" "b/\346\233\264\346\226\260\346\227\245\345\277\227.md" new file mode 100644 index 0000000000000000000000000000000000000000..5da833b4099596eaba5d2d2594561672fc17db47 --- /dev/null +++ "b/\346\233\264\346\226\260\346\227\245\345\277\227.md" @@ -0,0 +1,7 @@ +## 更新日志 + +### 2019年6月18日 + +1. 在朋友的建议下,给首页加了一个打赏的二维码,看看有多少愿意为知识付费。今天收到了7位小伙伴的打赏,在此表示感谢。打赏功能获得的收入在年底将全部捐赠给**绿之叶公益**([点击了解]())。之前每年都会试着为这个公益组织做一些自己能做的事情,这次当然也不能例外。 +2. Django部分(第41天到第55天)更新到第47天,最新上线的部分包括报表、日志、ORM查询优化以及中间件相关的内容,并将投票应用的完成代码同步到github。 +