提交 0640abb2 编写于 作者: J jackfrued

更新了Linux、数据库和Django部分的文档

上级 87de54a0
...@@ -498,7 +498,7 @@ python3 ...@@ -498,7 +498,7 @@ python3
```Python ```Python
>>> from pymongo import MongoClient >>> from pymongo import MongoClient
>>> client = MongoClient('mongodb://120.77.222.217:27017') >>> client = MongoClient('mongodb://127.0.0.1:27017')
>>> db = client.school >>> db = client.school
>>> for student in db.students.find(): >>> for student in db.students.find():
... print('学号:', student['stuid']) ... print('学号:', student['stuid'])
......
...@@ -23,7 +23,7 @@ Web开发的早期阶段,开发者需要手动编写每个页面,例如一 ...@@ -23,7 +23,7 @@ Web开发的早期阶段,开发者需要手动编写每个页面,例如一
#### HTTP协议 #### HTTP协议
这里我们稍微费一些笔墨来谈谈上面提到的HTTP。HTTP(超文本传输协议)是构建于TCP(传输控制协议)之上应用级协议,它利用了TCP提供的可靠的传输服务实现了Web应用中的数据交换。按照维基百科上的介绍,设计HTTP最初的目的是为了提供一种发布和接收[HTML](https://zh.wikipedia.org/wiki/HTML)页面的方法,也就是说这个协议是浏览器和Web服务器之间传输的数据的载体。关于这个协议的详细信息以及目前的发展状况,大家可以阅读阮一峰老师的[《HTTP 协议入门》](http://www.ruanyifeng.com/blog/2016/08/http.html)[《互联网协议入门》](http://www.ruanyifeng.com/blog/2012/05/internet_protocol_suite_part_i.html)系列以及[《图解HTTPS协议》](http://www.ruanyifeng.com/blog/2014/09/illustration-ssl.html)进行了解。下图是我于2009年9月10日凌晨4点在四川省网络通信技术重点实验室用开源协议分析工具Ethereal(抓包工具WireShark的前身)截取的访问百度首页时的HTTP请求和响应的报文(协议数据),由于Ethereal截取的是经过网络适配器的数据,因此可以清晰的看到从物理链路层到应用层的协议数据。 这里我们稍微费一些笔墨来谈谈上面提到的HTTP。HTTP(超文本传输协议)是构建于TCP(传输控制协议)之上应用级协议,它利用了TCP提供的可靠的传输服务实现了Web应用中的数据交换。按照维基百科上的介绍,设计HTTP最初的目的是为了提供一种发布和接收[HTML](https://zh.wikipedia.org/wiki/HTML)页面的方法,也就是说这个协议是浏览器和Web服务器之间传输的数据的载体。关于这个协议的详细信息以及目前的发展状况,大家可以阅读阮一峰老师的[《HTTP 协议入门》](http://www.ruanyifeng.com/blog/2016/08/http.html)[《互联网协议入门》](http://www.ruanyifeng.com/blog/2012/05/internet_protocol_suite_part_i.html)系列以及[《图解HTTPS协议》](http://www.ruanyifeng.com/blog/2014/09/illustration-ssl.html)进行了解。下图是我在四川省网络通信技术重点实验室学习和工作期间使用开源协议分析工具Ethereal(抓包工具WireShark的前身)截取的访问百度首页时的HTTP请求和响应的报文(协议数据),由于Ethereal截取的是经过网络适配器的数据,因此可以清晰的看到从物理链路层到应用层的协议数据。
HTTP请求(请求行+请求头+空行+[消息体]): HTTP请求(请求行+请求头+空行+[消息体]):
...@@ -33,7 +33,7 @@ HTTP响应(响应行+响应头+空行+消息体): ...@@ -33,7 +33,7 @@ HTTP响应(响应行+响应头+空行+消息体):
![](./res/http-response.png) ![](./res/http-response.png)
> 说明:但愿这两张如同泛黄的照片般的截图能帮助你了解HTTP到底是什么样子的。 > 说明:这两张图是在2009年9月10日截取的,但愿这两张如同泛黄的照片般的截图能帮助你了解HTTP到底是什么样子的。
### Django概述 ### Django概述
...@@ -51,6 +51,8 @@ Django诞生于2003年,它是一个在真正的应用中成长起来的项目 ...@@ -51,6 +51,8 @@ Django诞生于2003年,它是一个在真正的应用中成长起来的项目
1. 检查Python环境:Django 1.11需要Python 2.7或Python 3.4以上的版本;Django 2.0需要Python 3.4以上的版本;Django 2.1需要Python 3.5以上的版本。 1. 检查Python环境:Django 1.11需要Python 2.7或Python 3.4以上的版本;Django 2.0需要Python 3.4以上的版本;Django 2.1需要Python 3.5以上的版本。
> 说明:我自己平时使用macOS做开发,macOS和Linux平台使用的命令跟Windows平台有较大的区别,这一点在之前也有过类似的说明,如果使用Windows平台做开发,替换一下对应的命令即可。
```Shell ```Shell
$ python3 --version $ python3 --version
``` ```
...@@ -75,7 +77,7 @@ Django诞生于2003年,它是一个在真正的应用中成长起来的项目 ...@@ -75,7 +77,7 @@ Django诞生于2003年,它是一个在真正的应用中成长起来的项目
$ python3 -m venv venv $ python3 -m venv venv
$ source venv/bin/activate $ source venv/bin/activate
``` ```
> 说明:上面使用了Python自带的venv模块完成了虚拟环境的创建,当然也可以使用其他的工具,例如:virtualenv或pipenv等。要激活虚拟环境,在Windows系统下是通过"venv/Scripts/activate"`执行批处理文件来实现。 > 说明:上面使用了Python自带的venv模块完成了虚拟环境的创建,当然也可以使用virtualenv或pipenv这样的工具。要激活虚拟环境,在Windows环境下可以通过"venv/Scripts/activate"执行批处理文件来实现。
4. 更新包管理工具pip。 4. 更新包管理工具pip。
...@@ -99,7 +101,7 @@ Django诞生于2003年,它是一个在真正的应用中成长起来的项目 ...@@ -99,7 +101,7 @@ Django诞生于2003年,它是一个在真正的应用中成长起来的项目
或指定版本号来安装对应的Django的版本。 或指定版本号来安装对应的Django的版本。
```Shell ```Shell
(venv)$ pip install django==1.11 (venv)$ pip install django==2.1.8
``` ```
6. 检查Django的版本。 6. 检查Django的版本。
...@@ -123,7 +125,7 @@ Django诞生于2003年,它是一个在真正的应用中成长起来的项目 ...@@ -123,7 +125,7 @@ Django诞生于2003年,它是一个在真正的应用中成长起来的项目
(venv)$ pip list (venv)$ pip list
``` ```
下图展示了Django版本和Python版本的对应关系,如果在安装时没有指定版本号,将自动选择最新的版本(在写作这段内容时,最新的版本是2.0;目前最新的版本已经更新到2.2)。 下图展示了Django版本和Python版本的对应关系,如果在安装时没有指定版本号,将自动选择最新的版本(在写作这段内容时,Django最新的版本是2.2)。
| Django版本 | Python版本 | | Django版本 | Python版本 |
| ---------- | ----------------------- | | ---------- | ----------------------- |
...@@ -143,11 +145,13 @@ Django诞生于2003年,它是一个在真正的应用中成长起来的项目 ...@@ -143,11 +145,13 @@ Django诞生于2003年,它是一个在真正的应用中成长起来的项目
执行上面的命令后看看生成的文件和文件夹,它们的作用如下所示: 执行上面的命令后看看生成的文件和文件夹,它们的作用如下所示:
- `manage.py`: 一个让你用各种方式管理 Django 项目的命令行工具。 - `manage.py`: 一个让你可以管理Django项目的工具程序。
- `oa/__init__.py`:一个空文件,告诉 Python 这个目录应该被认为是一个 Python 包。 - `oa/__init__.py`:一个空文件,告诉Python解释器这个目录应该被视为一个Python的包。
- `oa/settings.py`:Django 项目的配置文件。 - `oa/settings.py`:Django项目的配置文件。
- `oa/urls.py`:Django 项目的 URL 声明,就像你网站的“目录”。 - `oa/urls.py`:Django项目的URL声明(URL映射),就像是你的网站的“目录”。
- `oa/wsgi.py`:作为你的项目的运行在 WSGI 兼容的Web服务器上的入口。 - `oa/wsgi.py`:项目运行在WSGI兼容Web服务器上的接口文件。
> 说明:WSGI全称是Web服务器网关接口,维基百科上给出的解释是“为Python语言定义的[Web服务器](https://zh.wikipedia.org/wiki/%E7%B6%B2%E9%A0%81%E4%BC%BA%E6%9C%8D%E5%99%A8)和[Web应用程序](https://zh.wikipedia.org/wiki/%E7%BD%91%E7%BB%9C%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F)或框架之间的一种简单而通用的接口”。
8. 启动服务器运行项目。 8. 启动服务器运行项目。
...@@ -164,7 +168,7 @@ Django诞生于2003年,它是一个在真正的应用中成长起来的项目 ...@@ -164,7 +168,7 @@ Django诞生于2003年,它是一个在真正的应用中成长起来的项目
> 说明2:用于开发的服务器在需要的情况下会对每一次的访问请求重新载入一遍Python代码。所以你不需要为了让修改的代码生效而频繁的重新启动服务器。然而,一些动作,比如添加新文件,将不会触发自动重新加载,这时你得自己手动重启服务器。 > 说明2:用于开发的服务器在需要的情况下会对每一次的访问请求重新载入一遍Python代码。所以你不需要为了让修改的代码生效而频繁的重新启动服务器。然而,一些动作,比如添加新文件,将不会触发自动重新加载,这时你得自己手动重启服务器。
> 说明3:可以通过`python manage.py help`命令查看可用命令列表;在启动服务器时,也可以通过`python manage.py runserver 1.2.3.4:5678`来指定绑定的IP地址和端口。 > 说明3:可以通过`python manage.py help`命令查看可用命令列表;在启动服务器时,也可以通过`python manage.py runserver 1.2.3.4:5678`来指定将服务器运行于哪个IP地址和端口。
> 说明4:可以通过Ctrl+C来终止服务器的运行。 > 说明4:可以通过Ctrl+C来终止服务器的运行。
...@@ -199,14 +203,14 @@ Django诞生于2003年,它是一个在真正的应用中成长起来的项目 ...@@ -199,14 +203,14 @@ Django诞生于2003年,它是一个在真正的应用中成长起来的项目
执行上面的命令会在当前路径下创建hrs目录,其目录结构如下所示: 执行上面的命令会在当前路径下创建hrs目录,其目录结构如下所示:
- `__init__.py`:一个空文件,告诉 Python 这个目录应该被认为是一个 Python 包。 - `__init__.py`:一个空文件,告诉Python解释器这个目录应该被视为一个Python的包。
- `admin.py`:可以用来注册模型,用于在Django的管理界面管理模型。 - `admin.py`:可以用来注册模型,用于在Django的管理界面管理模型。
- `apps.py`:当前应用的配置。 - `apps.py`:当前应用的配置文件
- `migrations`:存放与模型有关的数据库迁移信息。 - `migrations`:存放与模型有关的数据库迁移信息。
- `__init__.py`:一个空文件,告诉 Python 这个目录应该被认为是一个 Python 包。 - `__init__.py`:一个空文件,告诉Python解释器这个目录应该被视为一个Python的包。
- `models.py`:存放应用的数据模型,即实体类及其之间的关系(MVC/MVT中的M)。 - `models.py`:存放应用的数据模型,即实体类及其之间的关系(MVC/MTV中的M)。
- `tests.py`:包含测试应用各项功能的测试类和测试函数。 - `tests.py`:包含测试应用各项功能的测试类和测试函数。
- `views.py`:处理请求并返回响应的函数(MVC中的C,MVT中的V)。 - `views.py`:处理请求并返回响应的函数(MVC中的C,MTV中的V)。
2. 修改应用目录下的视图文件views.py。 2. 修改应用目录下的视图文件views.py。
...@@ -240,7 +244,7 @@ Django诞生于2003年,它是一个在真正的应用中成长起来的项目 ...@@ -240,7 +244,7 @@ Django诞生于2003年,它是一个在真正的应用中成长起来的项目
``` ```
> 说明:上面使用的`path`函数是Django 2.x中新添加的函数,除此之外还可以使用支持正则表达式的URL映射函数`re_path`函数;Django 1.x中是用名为`url`函数来设定URL映射。 > 说明:上面使用的`path`函数是Django 2.x中新添加的函数,除此之外还可以使用支持正则表达式的URL映射函数`re_path`函数;Django 1.x中是用名为`url`函数来设定URL映射。
4. 切换到项目目录,修改该目录下的urls.py文件,对应用中设定的URL进行合并。 4. 修改项目目录下的urls.py文件,对应用中设定的URL进行合并。
```Shell ```Shell
(venv) $ vim oa/urls.py (venv) $ vim oa/urls.py
...@@ -256,6 +260,8 @@ Django诞生于2003年,它是一个在真正的应用中成长起来的项目 ...@@ -256,6 +260,8 @@ Django诞生于2003年,它是一个在真正的应用中成长起来的项目
] ]
``` ```
> 说明:上面的代码通过`include`函数将hrs应用中配置URL的文件包含到项目的URL配置中,并映射到`hrs/`路径下。
5. 重新运行项目,并打开浏览器中访问<http://localhost:8000/hrs> 5. 重新运行项目,并打开浏览器中访问<http://localhost:8000/hrs>
```Shell ```Shell
...@@ -316,9 +322,7 @@ Django诞生于2003年,它是一个在真正的应用中成长起来的项目 ...@@ -316,9 +322,7 @@ Django诞生于2003年,它是一个在真正的应用中成长起来的项目
上面通过拼接HTML代码的方式生成动态视图的做法在实际开发中是无能接受的,这一点大家一定能够想到。为了解决这个问题,我们可以提前准备一个模板页,所谓模板页就是一个带占位符的HTML页面,当我们将程序中获得的数据替换掉页面中的占位符时,一个动态页面就产生了。 上面通过拼接HTML代码的方式生成动态视图的做法在实际开发中是无能接受的,这一点大家一定能够想到。为了解决这个问题,我们可以提前准备一个模板页,所谓模板页就是一个带占位符的HTML页面,当我们将程序中获得的数据替换掉页面中的占位符时,一个动态页面就产生了。
我们可以用Django框架中template模块的Template类创建模板对象,通过模板对象的render方法实现对模板的渲染。所谓的渲染就是用数据替换掉模板页中的占位符,当然这里的渲染称为后端渲染,即在服务器端完成页面的渲染再输出到浏览器中,这种做法的主要坏处是当并发访问量较大时,服务器会承受较大的负担,所以今天有很多的Web应用都使用了前端渲染,即服务器只为浏览器提供所需的数据(通常是JSON格式),在浏览器中通过JavaScript获取这些数据并渲染到页面上,这些内容在后面为大家呈现。 我们可以用Django框架中template模块的Template类创建模板对象,通过模板对象的render方法实现对模板的渲染,在Django框架中还有一个名为`render`的便捷函数可以来完成渲染模板的操作。所谓的渲染就是用数据替换掉模板页中的占位符,当然这里的渲染称为后端渲染,即在服务器端完成页面的渲染再输出到浏览器中,这种做法的主要坏处是当并发访问量较大时,服务器会承受较大的负担,所以今天有很多的Web应用都使用了前端渲染,即服务器只提供所需的数据(通常是JSON格式),在浏览器中通过JavaScript获取这些数据并渲染到页面上,这个我们在后面的内容中会讲到。
Django框架通过shortcuts模块的快捷函数`render`简化了渲染模板的操作,具体的用法如下所示。
1. 先回到manage.py文件所在的目录创建名为templates文件夹。 1. 先回到manage.py文件所在的目录创建名为templates文件夹。
...@@ -359,7 +363,7 @@ Django框架通过shortcuts模块的快捷函数`render`简化了渲染模板的 ...@@ -359,7 +363,7 @@ Django框架通过shortcuts模块的快捷函数`render`简化了渲染模板的
</body> </body>
</html> </html>
``` ```
在上面的模板页中我们使用了`{{ greeting }}`这样的模板占位符语法,也使用了`{% for %}`这样的模板指令,这些都是Django模板语言(DTL)的一部分。如果对此不熟悉并不要紧,我们会在后续的内容中进一步的讲解,而且我们刚才也说到了,还有更好的选择就是使用前端渲染,当然这是后话。 在上面的模板页中我们使用了`{{ greeting }}`这样的模板占位符语法,也使用了`{% for %}`这样的模板指令,这些都是Django模板语言(DTL)的一部分。如果对此不熟悉并不要紧,我们会在后续的内容中进一步的讲解,而且我们刚才也说到了,渲染页面还有更好的选择就是使用前端渲染,当然这是后话。
3. 回到应用目录,修改views.py文件。 3. 回到应用目录,修改views.py文件。
...@@ -381,6 +385,8 @@ Django框架通过shortcuts模块的快捷函数`render`简化了渲染模板的 ...@@ -381,6 +385,8 @@ Django框架通过shortcuts模块的快捷函数`render`简化了渲染模板的
return render(request, 'index.html', {'depts_list': depts_list}) return render(request, 'index.html', {'depts_list': depts_list})
``` ```
> 说明:Django框架通过shortcuts模块的便捷函数`render`简化了渲染模板的操作,有了这个函数,就不用先创建`Template`对象再去调用`render`方法。。
到此为止,我们还没有办法让views.py中的`render`函数找到模板文件index.html,为此我们需要修改settings.py文件,配置模板文件所在的路径。 到此为止,我们还没有办法让views.py中的`render`函数找到模板文件index.html,为此我们需要修改settings.py文件,配置模板文件所在的路径。
4. 切换到项目目录修改settings.py文件。 4. 切换到项目目录修改settings.py文件。
...@@ -420,6 +426,5 @@ Django框架通过shortcuts模块的快捷函数`render`简化了渲染模板的 ...@@ -420,6 +426,5 @@ Django框架通过shortcuts模块的快捷函数`render`简化了渲染模板的
### 总结 ### 总结
至此,我们已经利用Django框架完成了一个非常小的Web应用,虽然它并没有任何的实际价值,但是可以通过这个项目对Django框架有一个感性的认识。当然,实际开发中我们可以用PyCharm来创建项目,如果使用专业版的PyCharm,可以直接创建Django项目。使用PyCharm的好处在于编写代码时可以获得代码提示、错误修复、自动导入等功能,从而提升开发效率,但是专业版的PyCharm需要按年支付相应的费用,社区版的PyCharm中并未包含对Django框架直接的支持,但是我们仍然可以使用它来创建Django项目,只是在使用上没有专业版的方便。关于PyCharm的使用,可以参考[《玩转PyCharm》](../玩转PyCharm.md)一文。 至此,我们已经利用Django框架完成了一个非常小的Web应用,虽然它并没有任何的实际价值,但是可以通过这个项目对Django框架有一个感性的认识。当然,实际开发中我们可以用PyCharm来创建项目,如果使用专业版的PyCharm,可以直接创建Django项目。使用PyCharm的好处在于编写代码时可以获得代码提示、错误修复、自动导入等功能,从而提升开发效率,但是专业版的PyCharm需要按年支付相应的费用,社区版的PyCharm中并未包含对Django框架直接的支持,但是我们仍然可以使用它来创建Django项目,只是在使用上没有专业版的方便。关于PyCharm的使用,可以参考[《玩转PyCharm》](../玩转PyCharm.md)一文。此外,Django最好的学习资料肯定是它的[官方文档](https://docs.djangoproject.com/zh-hans/2.0/),当然图灵社区出版的[《Django基础教程》](http://www.ituring.com.cn/book/2630)也是非常适合初学者的入门级读物。
此外,学习Django最好的资料肯定是它的[官方文档](https://docs.djangoproject.com/zh-hans/2.0/),除此之外图灵社区出版的[《Django基础教程》](http://www.ituring.com.cn/book/2630)也是非常适合初学者的读物。
\ No newline at end of file
## 深入模型 ## 深入模型
在上一个章节中,我们提到了Django是基于MVC架构的Web框架,MVC架构追求的是“模型”和“视图”的解耦合。所谓“模型”说得更直白一些就是数据,所以通常也被称作“数据模型”。在实际的项目中,数据模型通常通过数据库实现持久化操作,而关系型数据库在很长一段时间都是持久化的首选方案,下面我们以MySQL为例来说明如何使用关系型数据库来实现持久化操作。 在上一个章节中,我们提到了Django是基于MVC架构的Web框架,MVC架构追求的是“模型”和“视图”的解耦合。所谓“模型”说得更直白一些就是数据(的表示),所以通常也被称作“数据模型”。在实际的项目中,数据模型通常通过数据库实现持久化操作,而关系型数据库在过去和当下都是持久化的首选方案,下面我们以MySQL为例来说明如何使用关系型数据库来实现持久化操作。
### 配置关系型数据库MySQL ### 配置关系型数据库MySQL
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
1. 修改项目的settings.py文件,首先将我们之前创建的应用hrs添加已安装的项目中,然后配置MySQL作为持久化方案。 1. 修改项目的settings.py文件,首先将我们之前创建的应用hrs添加已安装的项目中,然后配置MySQL作为持久化方案。
```Shell ```Shell
(venv)$ cd oa/settings.py (venv)$ vim oa/settings.py
``` ```
```Python ```Python
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
'default': { 'default': {
'ENGINE': 'django.db.backends.mysql', 'ENGINE': 'django.db.backends.mysql',
'NAME': 'oa', 'NAME': 'oa',
'HOST': 'localhost', 'HOST': '127.0.0.1',
'PORT': 3306, 'PORT': 3306,
'USER': 'root', 'USER': 'root',
'PASSWORD': '123456', 'PASSWORD': '123456',
...@@ -50,13 +50,13 @@ ...@@ -50,13 +50,13 @@
NAME属性代表数据库的名称,如果使用SQLite它对应着一个文件,在这种情况下NAME的属性值应该是一个绝对路径;使用其他关系型数据库,则要配置对应的HOST(主机)、PORT(端口)、USER(用户名)、PASSWORD(口令)等属性。 NAME属性代表数据库的名称,如果使用SQLite它对应着一个文件,在这种情况下NAME的属性值应该是一个绝对路径;使用其他关系型数据库,则要配置对应的HOST(主机)、PORT(端口)、USER(用户名)、PASSWORD(口令)等属性。
2. 安装MySQL客户端工具,Python 3中使用PyMySQL,Python 2中用MySQLdb。 2. 安装Python操作MySQL的依赖库,Python 3中通常使用PyMySQL,Python 2中通常用MySQLdb。
```Shell ```Shell
(venv)$ pip install pymysql (venv)$ pip install pymysql
``` ```
如果使用Python 3需要修改**项目**`__init__.py`文件并加入如下所示的代码,这段代码的作用是将PyMySQL视为MySQLdb来使用,从而避免Django找不到连接MySQL的客户端工具而询问你:“Did you install mysqlclient? ”(你安装了mysqlclient吗?)。 如果使用Python 3需要修改**项目目录**`__init__.py`文件并加入如下所示的代码,这段代码的作用是将PyMySQL视为MySQLdb来使用,从而避免Django找不到连接MySQL的客户端工具而询问你:“Did you install mysqlclient? ”(你安装了mysqlclient吗?)。
```Python ```Python
import pymysql import pymysql
...@@ -64,15 +64,15 @@ ...@@ -64,15 +64,15 @@
pymysql.install_as_MySQLdb() pymysql.install_as_MySQLdb()
``` ```
3. 运行manage.py并指定migrate参数实现数据库迁移,为应用程序创建对应的数据表,当然在此之前需要**先启动MySQL数据库服务器并创建名为oa的数据库**,在MySQL中创建数据库的语句如下所示。 3. 如果之前没有为应用程序创建数据库,那么现在是时候创建名为oa的数据库了。在MySQL中创建数据库的SQL语句如下所示:
```SQL ```SQL
drop database if exists oa;
create database oa default charset utf8; create database oa default charset utf8;
``` ```
4. Django框架本身有自带的数据模型,我们稍后会用到这些模型,为此我们先做一次迁移操作。所谓迁移,就是根据模型自动生成关系数据库中的二维表,命令如下所示:
```Shell ```Shell
(venv)$ cd ..
(venv)$ python manage.py migrate (venv)$ python manage.py migrate
Operations to perform: Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions Apply all migrations: admin, auth, contenttypes, sessions
...@@ -93,7 +93,7 @@ ...@@ -93,7 +93,7 @@
Applying sessions.0001_initial... OK Applying sessions.0001_initial... OK
``` ```
4. 可以看到,Django帮助我们创建了10张表,这些都是使用Django框架需要的东西,稍后我们就会用到这些表。除此之外,我们还应该为我们自己的应用创建数据模型。如果要在hrs应用中实现对部门和员工的管理,我们可以创建如下所示的数据模型 5. 接下来,我们为自己的应用创建数据模型。如果要在hrs应用中实现对部门和员工的管理,我们可以先创建部门和员工数据模型,代码如下所示
```Shell ```Shell
(venv)$ vim hrs/models.py (venv)$ vim hrs/models.py
...@@ -120,11 +120,11 @@ ...@@ -120,11 +120,11 @@
no = models.IntegerField(primary_key=True, db_column='eno', verbose_name='员工编号') no = models.IntegerField(primary_key=True, db_column='eno', verbose_name='员工编号')
name = models.CharField(max_length=20, db_column='ename', verbose_name='员工姓名') name = models.CharField(max_length=20, db_column='ename', verbose_name='员工姓名')
job = models.CharField(max_length=10, verbose_name='职位') job = models.CharField(max_length=10, verbose_name='职位')
# 自参照完整性多对一外键关联 # 多对一外键关联(自参照)
mgr = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True, verbose_name='主管编号') mgr = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True, verbose_name='主管')
sal = models.DecimalField(max_digits=7, decimal_places=2, verbose_name='月薪') sal = models.DecimalField(max_digits=7, decimal_places=2, verbose_name='月薪')
comm = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True, verbose_name='补贴') comm = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True, verbose_name='补贴')
# 多对一外键关联 # 多对一外键关联(参照部门模型)
dept = models.ForeignKey(Dept, db_column='dno', on_delete=models.PROTECT, verbose_name='所在部门') dept = models.ForeignKey(Dept, db_column='dno', on_delete=models.PROTECT, verbose_name='所在部门')
class Meta: class Meta:
...@@ -132,7 +132,7 @@ ...@@ -132,7 +132,7 @@
``` ```
> 说明:上面定义模型时使用了字段类及其属性,其中IntegerField对应数据库中的integer类型,CharField对应数据库的varchar类型,DecimalField对应数据库的decimal类型,ForeignKey用来建立多对一外键关联。字段属性primary_key用于设置主键,max_length用来设置字段的最大长度,db_column用来设置数据库中与字段对应的列,verbose_name则设置了Django后台管理系统中该字段显示的名称。如果对这些东西感到很困惑也不要紧,文末提供了字段类、字段属性、元数据选项等设置的相关说明,不清楚的读者可以稍后查看对应的参考指南。 > 说明:上面定义模型时使用了字段类及其属性,其中IntegerField对应数据库中的integer类型,CharField对应数据库的varchar类型,DecimalField对应数据库的decimal类型,ForeignKey用来建立多对一外键关联。字段属性primary_key用于设置主键,max_length用来设置字段的最大长度,db_column用来设置数据库中与字段对应的列,verbose_name则设置了Django后台管理系统中该字段显示的名称。如果对这些东西感到很困惑也不要紧,文末提供了字段类、字段属性、元数据选项等设置的相关说明,不清楚的读者可以稍后查看对应的参考指南。
5. 通过模型创建数据表。 6. 再次执行迁移操作,先通过模型生成迁移文件,再执行迁移创建二维表。
```Shell ```Shell
(venv)$ python manage.py makemigrations hrs (venv)$ python manage.py makemigrations hrs
...@@ -151,7 +151,9 @@ ...@@ -151,7 +151,9 @@
![](./res/er-graph.png) ![](./res/er-graph.png)
### 在后台管理模型 ### 利用Django后台管理模型
Django框架有自带的后台管理系统来实现对模型的管理。虽然实际应用中,这个后台可能并不能满足我们的需求,但是在学习Django框架时,我们暂时可以利用Django自带的后台管理系统来管理我们的模型,同时也可以了解一个项目的后台管理系统到底需要哪些功能。
1. 创建超级管理员账号。 1. 创建超级管理员账号。
...@@ -201,23 +203,23 @@ ...@@ -201,23 +203,23 @@
4. 对模型进行CRUD操作。 4. 对模型进行CRUD操作。
可以在管理员平台对模型进行C(新增)R(查看)U(更新)D(删除)操作,如下图所示。 可以在管理员平台对模型进行C(新增)、R(查看)、U(更新)、D(删除)操作,如下图所示。
添加新的部门。 - 添加新的部门。
![](./res/admin-model-create.png) ![](./res/admin-model-create.png)
查看所有部门。 - 查看所有部门。
![](./res/admin-model-read.png) ![](./res/admin-model-read.png)
更新和删除部门。 - 更新和删除部门。
![](./res/admin-model-delete-and-update.png) ![](./res/admin-model-delete-and-update.png)
5. 注册模型管理类。 5. 注册模型管理类。
再次修改admin.py文件,通过注册模型管理类,可以在后台管理系统中更好的管理模型。 可能大家已经注意到了,刚才在后台查看部门信息的时候,显示的部门信息并不直观,为此我们再修改admin.py文件,通过注册模型管理类,可以在后台管理系统中更好的管理模型。
```Python ```Python
from django.contrib import admin from django.contrib import admin
...@@ -265,19 +267,12 @@ ...@@ -265,19 +267,12 @@
class Emp(models.Model): class Emp(models.Model):
"""员工类""" """员工类"""
# 此处省略上面的代码
mgr = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True, verbose_name='直接主管')
# 此处省略下面的代码
# 此处省略上面的代码 # 此处省略上面的代码
def __str__(self): def __str__(self):
return self.name return self.name
# 此处省略下面的代码 # 此处省略下面的代码
``` ```
修改代码后刷新查看Emp模型的页面,效果如下图所示。 修改代码后刷新查看Emp模型的页面,效果如下图所示。
...@@ -301,6 +296,7 @@ Type "help", "copyright", "credits" or "license" for more information. ...@@ -301,6 +296,7 @@ Type "help", "copyright", "credits" or "license" for more information.
```Shell ```Shell
>>> from hrs.models import Dept, Emp >>> from hrs.models import Dept, Emp
>>>
>>> dept = Dept(40, '研发2部', '深圳') >>> dept = Dept(40, '研发2部', '深圳')
>>> dept.save() >>> dept.save()
``` ```
...@@ -314,14 +310,14 @@ Type "help", "copyright", "credits" or "license" for more information. ...@@ -314,14 +310,14 @@ Type "help", "copyright", "credits" or "license" for more information.
#### 查询 #### 查询
查询所有对象。 1. 查询所有对象。
```Shell ```Shell
>>> Dept.objects.all() >>> Dept.objects.all()
<QuerySet [<Dept: 研发1部>, <Dept: 销售1部>, <Dept: 运维1部>, <Dept: 研发3部>]> <QuerySet [<Dept: 研发1部>, <Dept: 销售1部>, <Dept: 运维1部>, <Dept: 研发3部>]>
``` ```
过滤数据。 2. 过滤数据。
```Shell ```Shell
>>> Dept.objects.filter(name='研发3部') # 查询部门名称为“研发3部”的部门 >>> Dept.objects.filter(name='研发3部') # 查询部门名称为“研发3部”的部门
...@@ -337,7 +333,7 @@ Type "help", "copyright", "credits" or "license" for more information. ...@@ -337,7 +333,7 @@ Type "help", "copyright", "credits" or "license" for more information.
<QuerySet [<Dept: 研发1部>, <Dept: 销售1部>, <Dept: 运维1部>]> <QuerySet [<Dept: 研发1部>, <Dept: 销售1部>, <Dept: 运维1部>]>
``` ```
查询单个对象。 3. 查询单个对象。
```Shell ```Shell
>>> Dept.objects.get(pk=10) >>> Dept.objects.get(pk=10)
...@@ -353,7 +349,7 @@ Type "help", "copyright", "credits" or "license" for more information. ...@@ -353,7 +349,7 @@ Type "help", "copyright", "credits" or "license" for more information.
<Dept: 研发1部> <Dept: 研发1部>
``` ```
排序数据。 4. 排序数据。
```Shell ```Shell
>>> Dept.objects.order_by('no') # 查询所有部门按部门编号升序排列 >>> Dept.objects.order_by('no') # 查询所有部门按部门编号升序排列
...@@ -363,7 +359,7 @@ Type "help", "copyright", "credits" or "license" for more information. ...@@ -363,7 +359,7 @@ Type "help", "copyright", "credits" or "license" for more information.
<QuerySet [<Dept: 研发3部>, <Dept: 运维1部>, <Dept: 销售1部>, <Dept: 研发1部>]> <QuerySet [<Dept: 研发3部>, <Dept: 运维1部>, <Dept: 销售1部>, <Dept: 研发1部>]>
``` ```
切片数据 5. 数据切片(分页查询)
```Shell ```Shell
>>> Dept.objects.order_by('no')[0:2] # 按部门编号排序查询1~2部门 >>> Dept.objects.order_by('no')[0:2] # 按部门编号排序查询1~2部门
...@@ -373,7 +369,7 @@ Type "help", "copyright", "credits" or "license" for more information. ...@@ -373,7 +369,7 @@ Type "help", "copyright", "credits" or "license" for more information.
<QuerySet [<Dept: 运维1部>, <Dept: 研发3部>]> <QuerySet [<Dept: 运维1部>, <Dept: 研发3部>]>
``` ```
高级查询。 6. 高级查询。
```Shell ```Shell
>>> Emp.objects.filter(dept__no=10) # 根据部门编号查询该部门的员工 >>> Emp.objects.filter(dept__no=10) # 根据部门编号查询该部门的员工
......
## 静态资源和Ajax请求 ## 静态资源和Ajax请求
基于前面两个章节讲解的知识,我们已经可以使用Django框架来实现Web应用的开发了。接下来我们就尝试实现一个投票应用,具体的需求是用户进入应用首先查看到“学科介绍”页面,该页面显示了一个学校所开设的所有学科;通过点击某个学科,可以进入“老师介绍”页面,该页面展示了该学科所有老师的详细情况,可以在该页面上给老师点击“好评”或“差评”,但是会先跳转到“登录页”要求用户登录,登录成功才能投票;对于未注册的用户,可以在“登录页”点击“新用户注册”进入“注册页”完成用户注册,注册成功后会跳转到“登录页”,注册失败会获得相应的提示信息。 基于前面两个章节讲解的知识,我们已经可以使用Django框架来完成Web应用的开发了。接下来我们就尝试实现一个投票应用,具体的需求是用户进入应用首先查看到“学科介绍”页面,该页面显示了一个学校所开设的所有学科;通过点击某个学科,可以进入“老师介绍”页面,该页面展示了该学科所有老师的详细情况,可以在该页面上给老师点击“好评”或“差评”;如果用户没有登录,在投票时会先跳转到“登录页”要求用户登录,登录成功才能投票;对于未注册的用户,可以在“登录页”点击“新用户注册”进入“注册页”完成用户注册操作,注册成功后会跳转到“登录页”,注册失败会获得相应的提示信息。
### 准备工作 ### 准备工作
...@@ -53,7 +53,7 @@ class Teacher(models.Model): ...@@ -53,7 +53,7 @@ class Teacher(models.Model):
... ...
``` ```
> 注意:为了给vote应用生成迁移,需要先修改Django项目的配置文件settings.py,在INSTALLED_APPS中添加vote应用。 > 注意:为了给vote应用生成迁移文件,需要修改Django项目settings.py文件,在INSTALLED_APPS中添加vote应用。
完成模型迁移之后,我们可以通过下面的SQL语句来添加学科和老师测试的数据。 完成模型迁移之后,我们可以通过下面的SQL语句来添加学科和老师测试的数据。
...@@ -69,10 +69,10 @@ VALUES ...@@ -69,10 +69,10 @@ VALUES
INSERT INTO `tb_teacher` (`no`,`name`,`gender`,`birth`,`intro`,`good_count`,`bad_count`,`photo`,`sno`) INSERT INTO `tb_teacher` (`no`,`name`,`gender`,`birth`,`intro`,`good_count`,`bad_count`,`photo`,`sno`)
VALUES VALUES
(1, '骆昊', 1, '1980-11-28', '10年以上软硬件产品设计、研发、架构和管理经验,2003年毕业于四川大学,四川大学Java技术俱乐部创始人,四川省优秀大学毕业生,在四川省网络通信技术重点实验室工作期间,参与了2项国家自然科学基金项目、1项中国科学院中长期研究项目和多项四川省科技攻关项目,在国际会议和国内顶级期刊上发表多篇论文(1篇被SCI收录,3篇被EI收录),大规模网络性能测量系统DMC-TS的设计者和开发者,perf-TTCN语言的发明者。国内最大程序员社区CSDN的博客专家,在Github上参与和维护了多个高质量开源项目,精通C/C++、Java、Python、R、Swift、JavaScript等编程语言,擅长OOAD、系统架构、算法设计、协议分析和网络测量,主持和参与过电子政务系统、KPI考核系统、P2P借贷平台等产品的研发,一直践行“用知识创造快乐”的教学理念,善于总结,乐于分享。', 0, 0, 'images/luohao.png', 1), (1, '骆昊', 1, '1980-11-28', '10年以上软硬件产品设计、研发、架构和管理经验,2003年毕业于四川大学,四川大学Java技术俱乐部创始人,四川省优秀大学毕业生,在四川省网络通信技术重点实验室工作期间,参与了2项国家自然科学基金项目、1项中国科学院中长期研究项目和多项四川省科技攻关项目,在国际会议和国内顶级期刊上发表多篇论文(1篇被SCI收录,3篇被EI收录),大规模网络性能测量系统DMC-TS的设计者和开发者,perf-TTCN语言的发明者。国内最大程序员社区CSDN的博客专家,在Github上参与和维护了多个高质量开源项目,精通C/C++、Java、Python、R、Swift、JavaScript等编程语言,擅长OOAD、系统架构、算法设计、协议分析和网络测量,主持和参与过电子政务系统、KPI考核系统、P2P借贷平台等产品的研发,一直践行“用知识创造快乐”的教学理念,善于总结,乐于分享。', 0, 0, 'images/luohao.png', 1),
(2, '王海飞', 1, '1993-05-24', '5年以上Python开发经验,先后参与了O2O商城、CRM系统、CMS平台、ERP系统等项目的设计与研发,曾在全国最大最专业的汽车领域相关服务网站担任Python高级研发工程师、项目经理等职务,擅长基于Python、Java、PHP等开发语言的企业级应用开发,全程参与了多个企业级应用从需求到上线所涉及的各种工作,精通Django、Flask等框架,熟悉基于微服务的企业级项目开发,拥有丰富的项目实战经验。善于用浅显易懂的方式在课堂上传授知识点,在授课过程中经常穿插企业开发的实际案例并分析其中的重点和难点,通过这种互动性极强的教学模式帮助学员找到解决问题的办法并提升学员的综合素质。', 0, 0, 'images/wangdachui.png', 1), (2, '王海飞', 1, '1993-05-24', '5年以上Python开发经验,先后参与了O2O商城、CRM系统、CMS平台、ERP系统等项目的设计与研发,曾在全国最大最专业的汽车领域相关服务网站担任Python高级研发工程师、项目经理等职务,擅长基于Python、Java、PHP等开发语言的企业级应用开发,全程参与了多个企业级应用从需求到上线所涉及的各种工作,精通Django、Flask等框架,熟悉基于微服务的企业级项目开发,拥有丰富的项目实战经验。善于用浅显易懂的方式在课堂上传授知识点,在授课过程中经常穿插企业开发的实际案例并分析其中的重点和难点,通过这种互动性极强的教学模式帮助学员找到解决问题的办法并提升学员的综合素质。', 0, 0, 'images/wanghaifei.png', 1),
(3, '余婷', 0, '1992-03-12', '5年以上移动互联网项目开发经验和教学经验,曾担任上市游戏公司高级软件研发工程师和移动端(iOS)技术负责人,参了多个企业级应用和游戏类应用的移动端开发和后台服务器开发,拥有丰富的开发经验和项目管理经验,以个人开发者和协作开发者的身份在苹果的AppStore上发布过多款App。精通Python、C、Objective-C、Swift等开发语言,熟悉iOS原生App开发、RESTful接口设计以及基于Cocos2d-x的游戏开发。授课条理清晰、细致入微,性格活泼开朗、有较强的亲和力,教学过程注重理论和实践的结合,在学员中有良好的口碑。', 0, 0, 'images/yuting.png', 1), (3, '余婷', 0, '1992-03-12', '5年以上移动互联网项目开发经验和教学经验,曾担任上市游戏公司高级软件研发工程师和移动端(iOS)技术负责人,参了多个企业级应用和游戏类应用的移动端开发和后台服务器开发,拥有丰富的开发经验和项目管理经验,以个人开发者和协作开发者的身份在苹果的AppStore上发布过多款App。精通Python、C、Objective-C、Swift等开发语言,熟悉iOS原生App开发、RESTful接口设计以及基于Cocos2d-x的游戏开发。授课条理清晰、细致入微,性格活泼开朗、有较强的亲和力,教学过程注重理论和实践的结合,在学员中有良好的口碑。', 0, 0, 'images/yuting.png', 1),
(4, '肖世荣', 1, '1977-07-02', '10年以上互联网和移动互联网产品设计、研发、技术架构和项目管理经验,曾在中国移动、symbio、ajinga.com、万达信息等公司担任架构师、项目经理、技术总监等职务,长期为苹果、保时捷、耐克、沃尔玛等国际客户以及国内的政府机构提供信息化服务,主导的项目曾获得“世界科技先锋”称号,个人作品“许愿吧”曾在腾讯应用市场生活类App排名前3,拥有百万级用户群体,运营的公众号“卵石坊”是国内知名的智能穿戴设备平台。精通Python、C++、Java、Ruby、JavaScript等开发语言,主导和参与了20多个企业级项目(含国家级重大项目和互联网创新项目),涉及的领域包括政务、社交、电信、卫生和金融,有极为丰富的项目实战经验。授课深入浅出、条理清晰,善于调动学员的学习热情并帮助学员理清思路和方法。', 0, 0, 'images/xiaoshirong.png', 1), (4, '肖世荣', 1, '1977-07-02', '10年以上互联网和移动互联网产品设计、研发、技术架构和项目管理经验,曾在中国移动、symbio、ajinga.com、万达信息等公司担任架构师、项目经理、技术总监等职务,长期为苹果、保时捷、耐克、沃尔玛等国际客户以及国内的政府机构提供信息化服务,主导的项目曾获得“世界科技先锋”称号,个人作品“许愿吧”曾在腾讯应用市场生活类App排名前3,拥有百万级用户群体,运营的公众号“卵石坊”是国内知名的智能穿戴设备平台。精通Python、C++、Java、Ruby、JavaScript等开发语言,主导和参与了20多个企业级项目(含国家级重大项目和互联网创新项目),涉及的领域包括政务、社交、电信、卫生和金融,有极为丰富的项目实战经验。授课深入浅出、条理清晰,善于调动学员的学习热情并帮助学员理清思路和方法。', 0, 0, 'images/xiaoshirong.png', 1),
(5, '张无忌', 1, '1987-07-07', '出生起便在冰火岛过着原始生活,踏入中土后因中玄冥神掌命危而带病习医,忍受寒毒煎熬七年最后因福缘际会练成“九阳神功”更在之后又练成了“乾坤大挪移”等盖世武功,几乎无敌于天下。 生性随和,宅心仁厚,精通医术和药理。20岁时便凭着盖世神功当上明教教主,率领百万教众及武林群雄反抗蒙古政权元朝的高压统治,最后推翻了元朝。由于擅长乾坤大挪移神功,上课遇到问题就转移话题,属于拉不出屎怪地球没有引力的类型。', 0, 0, 'images/zhangwuji.png', 5), (5, '张无忌', 1, '1987-07-07', '出生起便在冰火岛过着原始生活,踏入中土后因中玄冥神掌命危而带病习医,忍受寒毒煎熬七年最后因福缘际会练成“九阳神功”更在之后又练成了“乾坤大挪移”等盖世武功,几乎无敌于天下。 生性随和,宅心仁厚,精通医术和药理。20岁时便凭着盖世神功当上明教教主,率领百万教众及武林群雄反抗蒙古政权元朝的高压统治,最后推翻了元朝。由于擅长乾坤大挪移神功,上课遇到问题就转移话题。', 0, 0, 'images/zhangwuji.png', 5),
(6, '韦一笑', 1, '1975-12-15', '外号“青翼蝠王”,为明教四大护教法王之一。 身披青条子白色长袍,轻功十分了得。由于在修炼至阴至寒的“寒冰绵掌”时出了差错,经脉中郁积了至寒阴毒,只要运上内力,寒毒就会发作,如果不吸人血解毒,全身血脉就会凝结成冰,后得张无忌相助,以其高明医术配以“九阳神功”,终将寒毒驱去,摆脱了吸吮人血这一命运。由于轻功绝顶,学生一问问题就跑了。', 0, 0, 'images/weiyixiao.png', 3); (6, '韦一笑', 1, '1975-12-15', '外号“青翼蝠王”,为明教四大护教法王之一。 身披青条子白色长袍,轻功十分了得。由于在修炼至阴至寒的“寒冰绵掌”时出了差错,经脉中郁积了至寒阴毒,只要运上内力,寒毒就会发作,如果不吸人血解毒,全身血脉就会凝结成冰,后得张无忌相助,以其高明医术配以“九阳神功”,终将寒毒驱去,摆脱了吸吮人血这一命运。由于轻功绝顶,学生一问问题就跑了。', 0, 0, 'images/weiyixiao.png', 3);
``` ```
...@@ -239,7 +239,7 @@ urlpatterns = [ ...@@ -239,7 +239,7 @@ urlpatterns = [
### Ajax请求 ### Ajax请求
接下来就可以实现“好评”和“差评”的功能了,很明显如果能够在不刷新页面的情况下实现这两个功能会带来更好的用户体验,因此我们考虑使用[Ajax](https://zh.wikipedia.org/wiki/AJAX)技术来实现“好评”和“差评”,Ajax技术我们在之前的章节中已经介绍过了,此处不再赘述。 接下来就可以实现“好评”和“差评”的功能了,很明显如果能够在不刷新页面的情况下实现这两个功能会带来更好的用户体验,因此我们考虑使用[Ajax](https://zh.wikipedia.org/wiki/AJAX)技术来实现“好评”和“差评”,Ajax技术我们在Web前端部分已经介绍过了,此处不再赘述。
首先修改项目的urls.py文件,为“好评”和“差评”功能映射对应的URL。 首先修改项目的urls.py文件,为“好评”和“差评”功能映射对应的URL。
...@@ -266,7 +266,7 @@ def praise_or_criticize(request): ...@@ -266,7 +266,7 @@ def praise_or_criticize(request):
try: try:
tno = int(request.GET['tno']) tno = int(request.GET['tno'])
teacher = Teacher.objects.get(no=tno) teacher = Teacher.objects.get(no=tno)
if request.path.startswith('/prise'): if request.path.startswith('/praise'):
teacher.good_count += 1 teacher.good_count += 1
else: else:
teacher.bad_count += 1 teacher.bad_count += 1
...@@ -280,23 +280,60 @@ def praise_or_criticize(request): ...@@ -280,23 +280,60 @@ def praise_or_criticize(request):
修改显示老师信息的模板页,引入jQuery库来实现事件处理、Ajax请求和DOM操作。 修改显示老师信息的模板页,引入jQuery库来实现事件处理、Ajax请求和DOM操作。
```HTML ```HTML
<script src="{% static 'js/jquery.min.js' %}"></script> <!DOCTYPE html>
<script> {% load static %}
$(() => { <html lang="en">
$('.comment>a').on('click', (evt) => { <head>
evt.preventDefault(); <meta charset="UTF-8">
let a = $(evt.target) <title>老师信息</title>
let span = a.next() <style>/* 此处略去了层叠样式表的选择器 */</style>
$.getJSON(a.attr('href'), (json) => { </head>
if (json.code == 200) { <body>
span.text(parseInt(span.text()) + 1) <h1>{{ subject.name }}的老师信息</h1>
} else { <hr>
alert(json.hint) <div id="container">
} {% for teacher in teachers %}
<div class="teacher">
<div class="photo">
<img src="{% static teacher.photo %}" height="140" alt="">
</div>
<div class="info">
<div>
<span><strong>姓名:{{ teacher.name }}</strong></span>
<span>性别:{{ teacher.gender | yesno:'男,女' }}</span>
<span>出生日期:{{ teacher.birth }}</span>
</div>
<div class="intro">{{ teacher.intro }}</div>
<div class="comment">
<a href="/vote/praise/?tno={{ teacher.no }}">好评</a>
(<span>{{ teacher.good_count }}</span>)
&nbsp;&nbsp;
<a href="/vote/criticize/?tno={{ teacher.no }}">差评</a>
(<span>{{ teacher.bad_count }}</span>)
</div>
</div>
</div>
{% endfor %}
</div>
<script src="{% static 'js/jquery.min.js' %}"></script>
<script>
$(() => {
$('.comment > a').on('click', (evt) => {
evt.preventDefault()
let a = $(evt.target)
$.getJSON(a.attr('href'), (json) => {
if (json.code == 200) {
let span = a.next()
span.text(parseInt(span.text()) + 1)
} else {
alert(json.message)
}
})
}) })
}) })
}) </script>
</script> </body>
</html>
``` ```
### 小结 ### 小结
......
...@@ -20,8 +20,10 @@ class User(models.Model): ...@@ -20,8 +20,10 @@ class User(models.Model):
通过生成迁移和执行迁移操作,在数据库中创建对应的用户表。 通过生成迁移和执行迁移操作,在数据库中创建对应的用户表。
```Shell ```Shell
python manage.py makemigrations 应用名 (venv)$ python manage.py makemigrations vote
python manage.py migrate ...
(venv)$ python manage.py migrate
...
``` ```
定制一个非常简单的注册模板页面。 定制一个非常简单的注册模板页面。
...@@ -62,13 +64,13 @@ python manage.py migrate ...@@ -62,13 +64,13 @@ python manage.py migrate
</html> </html>
``` ```
注意,在上面的表单中,我们使用了模板指令`{% csrf_token %}`为表单添加一个隐藏域(type属性值为hidden的input标签),它的作用是在表单中生成一个随机令牌(token)来防范[跨站请求伪造](<https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0>)(通常简称为CSRF),这也是Django在提交表单时的硬性要求,除非我们专门设置了免除CSRF令牌。下图是一个关于CSRF简单生动的例子,它来自于[维基百科](<https://zh.wikipedia.org/wiki/Wikipedia:%E9%A6%96%E9%A1%B5>) 注意,在上面的表单中,我们使用了模板指令`{% csrf_token %}`为表单添加一个隐藏域(type属性值为hidden的input标签),它的作用是在表单中生成一个随机令牌(token)来防范[跨站请求伪造](<https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0>)(通常简称为CSRF),这也是Django在提交表单时的硬性要求,除非我们设置了免除CSRF令牌。下图是一个关于CSRF简单生动的例子,它来自于[维基百科](<https://zh.wikipedia.org/wiki/Wikipedia:%E9%A6%96%E9%A1%B5>)
![](./res/CSRF.png) ![](./res/CSRF.png)
用户在提交注册表单时,我们还需要对用户的输入进行验证,例如我们的网站要求用户名必须由字母、数字、下划线构成且长度在4-20个字符之间,密码的长度为8-20个字符,确认密码必须跟密码保持一致。这些验证操作首先可以通过浏览器中的JavaScript代码来完成,但是即便如此,在服务器端仍然要对用户输入再次进行验证来避免将无效的数据库交给数据库,因为用户可能会禁用浏览器的JavaScript功能,也有可能绕过浏览器的输入检查将注册数据提交给服务器,所以服务器端的用户输入检查仍然是必要的。 用户在提交注册表单时,我们还需要对用户的输入进行验证,例如我们的网站要求用户名必须由字母、数字、下划线构成且长度在4-20个字符之间,密码的长度为8-20个字符,确认密码必须跟密码保持一致。这些验证操作首先可以通过浏览器中的JavaScript代码来完成,但是即便如此,在服务器端仍然要对用户输入再次进行验证来避免将无效的数据库交给数据库,因为用户可能会禁用浏览器的JavaScript功能,也有可能绕过浏览器的输入检查将注册数据提交给服务器,所以服务器端的用户输入检查仍然是必要的。
我们可以利用Django框架封装的表单功能来对用户输入的有效性进行检查,虽然Django封装的表单还能帮助我们定制出页面上的表单元素,但这显然是一种灵活性很差的设计,这样的功能在实际开发中基本不考虑,所以表单主要的作用就在于数据验证,具体的做法如下所示。 我们可以利用Django框架封装的表单功能来对用户输入的有效性进行检查,虽然Django封装的表单还能帮助我们定制出页面上的表单元素,但这显然是一种灵活性很差的设计,这样的功能在实际开发中基本不考虑,所以表单主要的作用就在于数据验证,具体的做法如下所示。
```Python ```Python
USERNAME_PATTERN = re.compile(r'\w{4,20}') USERNAME_PATTERN = re.compile(r'\w{4,20}')
...@@ -99,7 +101,7 @@ class RegisterForm(forms.ModelForm): ...@@ -99,7 +101,7 @@ class RegisterForm(forms.ModelForm):
exclude = ('no', 'regdate') exclude = ('no', 'regdate')
``` ```
上面,我们定义了一个与User模型绑定的表单(继承自ModelForm),我们排除了用户编号(no)和注册日期(regdate)这两个属性,并添加了一个repassword属性用来接收从用户表单传给服务器的确认密码。我们在定义User模型时已经对用户名的最大长度进行了限制,上面我们又对确认密码的最小和最大长度进行了限制,但是这些都不足以完成我们对用户输入的验证。上面以`clean_`打头的方法就是我们自定义的验证规则。很明显,`clean_username`是对用户名的检查,而`clean_password`是对密码的检查。由于数据库二维表中不应该保存密码的原文,所以对密码做了一个简单的MD5摘要处理,实际开发中这样处理还不太够,因为有被实施反向查表法(利用彩虹表反向查询)破解用户密码的风险。为字符串生成MD5摘要的代码如下所示。 上面,我们定义了一个与User模型绑定的表单(继承自ModelForm),我们排除了用户编号(no)和注册日期(regdate)这两个属性,并添加了一个repassword属性用来接收从用户表单传给服务器的确认密码。我们在定义User模型时已经对用户名的最大长度进行了限制,上面我们又对确认密码的最小和最大长度进行了限制,但是这些都不足以完成我们对用户输入的验证。上面以`clean_`打头的方法就是我们自定义的验证规则。很明显,`clean_username`是对用户名的检查,而`clean_password`是对密码的检查。由于数据库二维表中不应该保存密码的原文,所以对密码做了一个简单的MD5摘要处理,实际开发中如果只做出这样的处理还不太够,因为即便使用了摘要,仍然有利用彩虹表反向查询破解用户密码的风险,如何做得更好我们会在后续的内容中讲到。为字符串生成MD5摘要的代码如下所示。
```Python ```Python
def to_md5_hex(message): def to_md5_hex(message):
...@@ -131,18 +133,13 @@ from django.urls import path ...@@ -131,18 +133,13 @@ from django.urls import path
from vote import views from vote import views
urlpatterns = [ urlpatterns = [
path('', views.show_subjects), # 此处省略上面的代码
path('captcha/', views.get_captcha),
path('teachers/', views.show_teachers),
path('prise/', views.praise_or_criticize),
path('criticize/', views.praise_or_criticize),
path('login/', views.login, name='login'),
path('register/', views.register, name='register'), path('register/', views.register, name='register'),
path('admin/', admin.site.urls), # 此处省略下面的代码
] ]
``` ```
> 说明:上面的代码中我们把待会要用到的登录和验证码的URL也顺便做了映射。`path`函数还可以通过name参数给URL绑定一个逆向解析的名字,也就是说,如果需要可以从后面给的名字逆向得到对应的URL。 > 说明:`path`函数可以通过name参数给URL绑定一个逆向解析的名字,也就是说,如果需要可以从后面给的名字逆向解析出对应的URL。
我们再来定制一个非常简单的登录页。 我们再来定制一个非常简单的登录页。
...@@ -159,7 +156,6 @@ urlpatterns = [ ...@@ -159,7 +156,6 @@ urlpatterns = [
<hr> <hr>
<p class="hint">{{ hint }}</p> <p class="hint">{{ hint }}</p>
<form action="/login/" method="post"> <form action="/login/" method="post">
<input type="hidden" name="backurl" value="{{ backurl }}">
{% csrf_token %} {% csrf_token %}
<div class="input"> <div class="input">
<label for="username">用户名:</label> <label for="username">用户名:</label>
...@@ -184,9 +180,9 @@ urlpatterns = [ ...@@ -184,9 +180,9 @@ urlpatterns = [
</html> </html>
``` ```
上面的登录页中,我们要求用户提供验证码,验证码全称是**全自动区分计算机和人类的公开图灵测试**,它是一种用来区分系统的使用者是计算机还是人类的程序。简单的说就是程序出一个只有人类能够回答的问题,由系统使用者来解答,由于计算机理论上无法解答程序提出的问题,所以回答出问题的用户就可以被认为是人类。大多数的网站都使用了不同类型的验证码技术来防范计算机自动注册用户或模拟用户登录(暴力破解用户密码),因为验证码具有一次消费性,而没有通过图灵测试的计算机是不能够注册或登录的。 上面的登录页中,我们要求用户提供验证码,验证码全称是**全自动区分计算机和人类的公开图灵测试**,它是一种用来区分系统的使用者是计算机还是人类的程序。简单的说就是程序出一个只有人类能够回答的问题,由系统使用者来解答,由于计算机理论上无法解答程序提出的问题,所以回答出问题的用户就可以被认为是人类。大多数的网站都使用了不同类型的验证码技术来防范用程序自动注册用户或模拟用户登录(暴力破解用户密码),因为验证码具有一次消费性,而没有通过图灵测试的程序是不能够完成注册或登录的。
在Python程序中生成验证码并不算特别复杂,但需要三方库pillow的支持(PIL的分支)。我们可以借鉴现有的方法用Python稍作封装即可。下面的代码已经实现了生成验证码图片并得到图片二进制数据的功能 在Python程序中生成验证码并不算特别复杂,但需要三方库Pillow的支持(PIL的分支),因为要对验证码图片进行旋转、扭曲、拉伸以及加入干扰信息来防范那些用OCR(光学文字识别)破解验证码的程序。下面的代码封装了生成验证码图片的功能,大家可以直接用这些代码来生成图片验证码,不要“重复发明轮子”
```Python ```Python
""" """
...@@ -234,15 +230,16 @@ class Captcha(object): ...@@ -234,15 +230,16 @@ class Captcha(object):
self._image = None self._image = None
self._fonts = fonts if fonts else \ self._fonts = fonts if fonts else \
[os.path.join(os.path.dirname(__file__), 'fonts', font) [os.path.join(os.path.dirname(__file__), 'fonts', font)
for font in ['Action.ttf', 'Silom.ttf', 'Verdana.ttf']] for font in ['ArialRB.ttf', 'ArialNI.ttf', 'Georgia.ttf', 'Kongxin.ttf']]
self._color = color if color else random_color(0, 200, random.randint(220, 255)) self._color = color if color else random_color(0, 200, random.randint(220, 255))
self._width, self._height = width, height self._width, self._height = width, height
@classmethod @classmethod
def instance(cls, width=200, height=75): def instance(cls, width=200, height=75):
if not hasattr(Captcha, "_instance"): prop_name = f'_instance_{width}_{height}'
cls._instance = cls(width, height) if not hasattr(cls, prop_name):
return cls._instance setattr(cls, prop_name, cls(width, height))
return getattr(cls, prop_name)
def background(self): def background(self):
"""绘制背景""" """绘制背景"""
...@@ -266,7 +263,7 @@ class Captcha(object): ...@@ -266,7 +263,7 @@ class Captcha(object):
for ps in zip(*path))) for ps in zip(*path)))
Draw(self._image).line(points, fill=color if color else self._color, width=width) Draw(self._image).line(points, fill=color if color else self._color, width=width)
def noise(self, number=62, level=2, color=None): def noise(self, number=50, level=2, color=None):
"""绘制扰码""" """绘制扰码"""
width, height = self._image.size width, height = self._image.size
dx, dy = width / 10, height / 10 dx, dy = width / 10, height / 10
...@@ -351,7 +348,9 @@ class Captcha(object): ...@@ -351,7 +348,9 @@ class Captcha(object):
self.background() self.background()
self.text(captcha_text, self._fonts, self.text(captcha_text, self._fonts,
drawings=['warp', 'rotate', 'offset']) drawings=['warp', 'rotate', 'offset'])
self.curve(), self.noise(), self.smooth() self.curve()
self.noise()
self.smooth()
image_bytes = BytesIO() image_bytes = BytesIO()
self._image.save(image_bytes, format=fmt) self._image.save(image_bytes, format=fmt)
return image_bytes.getvalue() return image_bytes.getvalue()
...@@ -445,4 +444,49 @@ def login(request): ...@@ -445,4 +444,49 @@ def login(request):
return render(request, 'login.html', {'hint': hint}) return render(request, 'login.html', {'hint': hint})
``` ```
需要指出,上面我们设定用户登录成功时直接返回首页,而且在用户登录时并没有验证用户输入的验证码是否正确,这些我们留到下一个单元再为大家讲解。 映射URL。
\ No newline at end of file
```Python
from django.contrib import admin
from django.urls import path
from vote import views
urlpatterns = [
# 此处省略上面的代码
path('login/', views.login, name='login'),
# 此处省略下面的代码
]
```
需要指出,上面我们设定用户登录成功时直接返回首页,而且在用户登录时并没有验证用户输入的验证码是否正确,这些我们留到下一个单元再为大家讲解。另外,如果要在Django自带的管理后台中进行表单验证,可以在admin.py的模型管理类中指定`form`属性为自定义的表单即可,例如:
```Python
class UserForm(forms.ModelForm):
password = forms.CharField(min_length=8, max_length=20,
widget=forms.PasswordInput, label='密码')
def clean_username(self):
username = self.cleaned_data['username']
if not USERNAME_PATTERN.fullmatch(username):
raise ValidationError('用户名由字母、数字和下划线构成且长度为4-20个字符')
return username
def clean_password(self):
password = self.cleaned_data['password']
return to_md5_hex(self.cleaned_data['password'])
class Meta:
model = User
exclude = ('no', )
class UserAdmin(admin.ModelAdmin):
list_display = ('no', 'username', 'password', 'email', 'tel')
ordering = ('no', )
form = UserForm
list_per_page = 10
admin.site.register(User, UserAdmin)
```
\ No newline at end of file
...@@ -12,13 +12,13 @@ ...@@ -12,13 +12,13 @@
2. 隐藏域(隐式表单域)。在提交表单的时候,可以通过在表单中设置隐藏域向服务器发送额外的数据。例如:`<input type="hidden" name="sessionid" value="123456">` 2. 隐藏域(隐式表单域)。在提交表单的时候,可以通过在表单中设置隐藏域向服务器发送额外的数据。例如:`<input type="hidden" name="sessionid" value="123456">`
3. Cookie。Cookie是保存在浏览器临时文件中的数据,每次请求时,请求头中会携带本站点的cookie到服务器,那么只要将sessionid写入cookie,下次请求时服务器只要读取请求头中的cookie就能够获得这个sessionid,如下图所示: 3. 本地存储。现在的浏览器都支持多种本地存储方案,包括:cookie、localStorage、sessionStorage、IndexedDB等。在这些方案中,cookie是历史最为悠久也是被诟病得最多的一种方案,也是我们接下来首先为大家讲解的一种方案。简单的说,cookie是一种以键值对方式保存在浏览器临时文件中的数据,每次请求时,请求头中会携带本站点的cookie到服务器,那么只要将sessionid写入cookie,下次请求时服务器只要读取请求头中的cookie就能够获得这个sessionid,如下图所示。
![](./res/sessionid_from_cookie.png) ![](./res/sessionid_from_cookie.png)
需要说明的是,在HTML5时代要想在浏览器中保存数据,除了使用cookie之外,还可以使用新的本地存储API,包括localStorage、sessionStorage、IndexedDB等,如下图所示。 在HTML5时代要,除了cookie,还可以使用新的本地存储API来保存数据,就是刚才提到的localStorage、sessionStorage、IndexedDB等技术,如下图所示。
![](./res/cookie_xstorage_indexeddb.png) ![](./res/cookie_xstorage_indexeddb.png)
### Django框架对session的支持 ### Django框架对session的支持
...@@ -57,7 +57,7 @@ def login(request: HttpRequest): ...@@ -57,7 +57,7 @@ def login(request: HttpRequest):
user = User.objects.filter(username=username, password=password).first() user = User.objects.filter(username=username, password=password).first()
if user: if user:
# 登录成功后将用户编号和用户名保存在session中 # 登录成功后将用户编号和用户名保存在session中
request.session['no'] = user.no request.session['userid'] = user.no
request.session['username'] = user.username request.session['username'] = user.username
return redirect('/') return redirect('/')
else: else:
...@@ -67,11 +67,11 @@ def login(request: HttpRequest): ...@@ -67,11 +67,11 @@ def login(request: HttpRequest):
return render(request, 'login.html', {'hint': hint}) return render(request, 'login.html', {'hint': hint})
``` ```
上面的代码中,我们设定了登录成功后会在session中保存用户的编号(`no`)和用户名(`username`),页面会重定向到首页。接下来我们可以稍微对首页的代码进行调整,在页面的右上角显示出登录用户的用户名。我们将这段代码单独写成了一个名为header.html的HTML文件,首页中可以通过在`<body>`标签中添加`{% include 'header.html' %}`来包含这个页面,代码如下所示。 上面的代码中,我们设定了登录成功后会在session中保存用户的编号(`userid`)和用户名(`username`),页面会重定向到首页。接下来我们可以稍微对首页的代码进行调整,在页面的右上角显示出登录用户的用户名。我们将这段代码单独写成了一个名为header.html的HTML文件,首页中可以通过在`<body>`标签中添加`{% include 'header.html' %}`来包含这个页面,代码如下所示。
```HTML ```HTML
<div class="user"> <div class="user">
{% if request.session.no %} {% if request.session.userid %}
<span>{{ request.session.username }}</span> <span>{{ request.session.username }}</span>
<a href="/vote/logout">注销</a> <a href="/vote/logout">注销</a>
{% else %} {% else %}
...@@ -81,7 +81,7 @@ def login(request: HttpRequest): ...@@ -81,7 +81,7 @@ def login(request: HttpRequest):
</div> </div>
``` ```
如果用户没有登录,页面会显示登录和注册的超链接;而用户登录成功后,页面上会显示用户名和注销的链接,注销链接对应的视图函数如下所示。 如果用户没有登录,页面会显示登录和注册的超链接;而用户登录成功后,页面上会显示用户名和注销的链接,注销链接对应的视图函数如下所示,URL的映射与之前讲过的类似,不再赘述
```Python ```Python
def logout(request): def logout(request):
......
...@@ -9,7 +9,7 @@ def praise_or_criticize(request: HttpRequest): ...@@ -9,7 +9,7 @@ def praise_or_criticize(request: HttpRequest):
try: try:
tno = int(request.GET.get('tno', '0')) tno = int(request.GET.get('tno', '0'))
teacher = Teacher.objects.get(no=tno) teacher = Teacher.objects.get(no=tno)
if request.path.startswith('/vote/praise'): if request.path.startswith('/praise'):
teacher.good_count += 1 teacher.good_count += 1
else: else:
teacher.bad_count += 1 teacher.bad_count += 1
...@@ -30,19 +30,14 @@ def praise_or_criticize(request: HttpRequest): ...@@ -30,19 +30,14 @@ def praise_or_criticize(request: HttpRequest):
$('.comment > a').on('click', (evt) => { $('.comment > a').on('click', (evt) => {
evt.preventDefault() evt.preventDefault()
let a = $(evt.target) let a = $(evt.target)
$.ajax({ $.getJSON(a.attr('href'), (json) => {
url: a.attr('href'), if (json.code == 200) {
type: 'get', let span = a.next()
dataType: 'json', span.text(parseInt(span.text()) + 1)
success: (json) => { } else if (json.code == 401) {
if (json.code == 200) { location.href = '/login/?backurl=' + location.href
let span = a.next() } else {
span.text(parseInt(span.text()) + 1) alert(json.message)
} else if (json.code == 401) {
location.href = '/vote/login/?backurl=' + location.href
} else {
alert(json.message)
}
} }
}) })
}) })
......
## 日志和调试 ## 日志和调试
在项目开发阶段,显示足够的调试信息以辅助开发人员调试代码还是非常必要的. 项目开发阶段,显示足够的调试信息以辅助开发人员调试代码还是非常必要的;项目上线以后,将系统运行时出现的警告、错误等信息记录下来以备相关人员了解系统运行状况并维护代码也是很有必要的。要做好这两件事件,我们需要为Django项目配置日志。
### 配置日志
Django的日志配置基本可以参照官方文档再结合项目实际需求来进行,这些内容基本上可以从官方文档上复制下来,然后进行局部的调整即可,下面给出一些参考配置。
```Python
LOGGING = {
'version': 1,
# 是否禁用已经存在的日志器
'disable_existing_loggers': False,
# 日志格式化器
'formatters': {
'simple': {
'format': '%(asctime)s %(module)s.%(funcName)s: %(message)s',
'datefmt': '%Y-%m-%d %H:%M:%S',
},
'verbose': {
'format': '%(asctime)s %(levelname)s [%(process)d-%(threadName)s] '
'%(module)s.%(funcName)s line %(lineno)d: %(message)s',
'datefmt': '%Y-%m-%d %H:%M:%S',
}
},
# 日志过滤器
'filters': {
# 只有在Django配置文件中DEBUG值为True时才起作用
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',
},
},
# 日志处理器
'handlers': {
# 输出到控制台
'console': {
'class': 'logging.StreamHandler',
'level': 'DEBUG',
'filters': ['require_debug_true'],
'formatter': 'simple',
},
# 输出到文件(每周切割一次)
'file1': {
'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': 'access.log',
'when': 'W0',
'backupCount': 12,
'formatter': 'simple',
'level': 'INFO',
},
# 输出到文件(每天切割一次)
'file2': {
'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': 'error.log',
'when': 'D',
'backupCount': 31,
'formatter': 'verbose',
'level': 'WARNING',
},
},
# 日志器记录器
'loggers': {
'django': {
# 需要使用的日志处理器
'handlers': ['console', 'file1', 'file2'],
# 是否向上传播日志信息
'propagate': True,
# 日志级别(不一定是最终的日志级别)
'level': 'DEBUG',
},
}
}
```
大家可能已经注意到了,上面日志配置中的formatters是**日志格式化器**,它代表了如何格式化输出日志,其中格式占位符分别表示:
1. %(name)s - 记录器的名称
2. %(levelno)s - 数字形式的日志记录级别
3. %(levelname)s - 日志记录级别的文本名称
4. %(filename)s - 执行日志记录调用的源文件的文件名称
5. %(pathname)s - 执行日志记录调用的源文件的路径名称
6. %(funcName)s - 执行日志记录调用的函数名称
7. %(module)s - 执行日志记录调用的模块名称
8. %(lineno)s - 执行日志记录调用的行号
9. %(created)s - 执行日志记录的时间
10. %(asctime)s - 日期和时间
11. %(msecs)s - 毫秒部分
12. %(thread)d - 线程ID(整数)
13. %(threadName)s - 线程名称
14. %(process)d - 进程ID (整数)
日志配置中的handlers用来指定**日志处理器**,简单的说就是指定将日志输出到控制台还是文件又或者是网络上的服务器,可用的处理器包括:
1. logging.StreamHandler(stream=None) - 可以向类似与sys.stdout或者sys.stderr的任何文件对象输出信息
2. logging.FileHandler(filename, mode='a', encoding=None, delay=False) - 将日志消息写入文件
3. logging.handlers.DatagramHandler(host, port) - 使用UDP协议,将日志信息发送到指定主机和端口的网络主机上
4. logging.handlers.HTTPHandler(host, url) - 使用HTTP的GET或POST方法将日志消息上传到一台HTTP 服务器
5. logging.handlers.RotatingFileHandler(filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=False) - 将日志消息写入文件,如果文件的大小超出maxBytes指定的值,那么将重新生成一个文件来记录日志
6. logging.handlers.SocketHandler(host, port) - 使用TCP协议,将日志信息发送到指定主机和端口的网络主机上
7. logging.handlers.SMTPHandler(mailhost, fromaddr, toaddrs, subject, credentials=None, secure=None, timeout=1.0) - 将日志输出到指定的邮件地址
8. logging.MemoryHandler(capacity, flushLevel=ERROR, target=None, flushOnClose=True) - 将日志输出到内存指定的缓冲区中
上面每个日志处理器都指定了一个名为“level”的属性,它代表了日志的级别,不同的日志级别反映出日志中记录信息的严重性。Python中定义了六个级别的日志,按照从低到高的顺序依次是:NOTSET、DEBUG、INFO、WARNING、ERROR、CRITICAL。
最后配置的**日志记录器**是用来真正输出日志的,Django框架提供了如下所示的内置记录器:
1. django - 在Django层次结构中的所有消息记录器
2. django.request - 与请求处理相关的日志消息。5xx响应被视为错误消息;4xx响应被视为为警告消息
3. django.server - 与通过runserver调用的服务器所接收的请求相关的日志消息。5xx响应被视为错误消息;4xx响应被记录为警告消息;其他一切都被记录为INFO
4. django.template - 与模板渲染相关的日志消息
5. django.db.backends - 有与数据库交互产生的日志消息,如果希望显示ORM框架执行的SQL语句,就可以使用该日志记录器。
日志记录器中配置的日志级别有可能不是最终的日志级别,因为还要参考日志处理器中配置的日志级别,取二者中级别较高者作为最终的日志级别。
### 配置Django-Debug-Toolbar
Django-Debug-Toolbar是辅助Django项目开发的神器,只要配置了它,就可以很方便的查看到以下内容,这些信息对了解项目的运行状况以及优化Web应用性能都是至关重要的。
| 项目 | 说明 |
| ----------- | --------------------------------- |
| Versions | Django的版本 |
| Time | 显示视图耗费的时间 |
| Settings | 配置文件中设置的值 |
| Headers | HTTP请求头和响应头的信息 |
| Request | 和请求相关的各种变量及其信息 |
| StaticFiles | 静态文件加载情况 |
| Templates | 模板的相关信息 |
| Cache | 缓存的使用情况 |
| Signals | Django内置的信号信息 |
| Logging | 被记录的日志信息 |
| SQL | 向数据库发送的SQL语句及其执行时间 |
1. 安装Django-Debug-Toolbar。
```Shell
pip install django-debug-toolbar
```
2. 配置 - 修改settings.py。
```Python
INSTALLED_APPS = [
'debug_toolbar',
]
MIDDLEWARE = [
'debug_toolbar.middleware.DebugToolbarMiddleware',
]
DEBUG_TOOLBAR_CONFIG = {
# 引入jQuery库
'JQUERY_URL': 'https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js',
# 工具栏是否折叠
'SHOW_COLLAPSED': True,
# 是否显示工具栏
'SHOW_TOOLBAR_CALLBACK': lambda x: True,
}
```
3. 配置 - 修改urls.py。
```Python
if settings.DEBUG:
import debug_toolbar
urlpatterns.insert(0, path('__debug__/', include(debug_toolbar.urls)))
```
4. 使用 - 在页面右侧可以看到一个调试工具栏,上面包括了如前所述的调试信息,包括:执行时间、项目设置、请求头、SQL、静态资源、模板、缓存、信号等,查看起来非常的方便。
\ No newline at end of file
...@@ -182,7 +182,7 @@ ipython最直观的优点: ...@@ -182,7 +182,7 @@ ipython最直观的优点:
resp = requests.get('http://api.tianapi.com/allnews/?key=请使用自己申请的Key&col=7&num=50') resp = requests.get('http://api.tianapi.com/allnews/?key=请使用自己申请的Key&col=7&num=50')
``` ```
> 说明:上面使用了天行数据提供的数据接口,需要的话可以自行去[天行数据](<https://www.tianapi.com/>)的网站注册开通。 > 说明:上面使用了天行数据提供的数据接口,需要的话可以自行去[天行数据](<https://www.tianapi.com/>)的网站注册开通,调用接口的时候要填写注册成功后系统分配给你的key
3. 使用反序列化将JSON字符串解析为字典并获取新闻列表。 3. 使用反序列化将JSON字符串解析为字典并获取新闻列表。
...@@ -225,5 +225,5 @@ ipython最直观的优点: ...@@ -225,5 +225,5 @@ ipython最直观的优点:
) )
``` ```
> 说明:上面的代码使用了[螺丝帽](<https://luosimao.com/>)的短信网关,利用短信网关发送短信是需要支付费用的,但是一般的平台都会提供免费测试的短信,但是发送短信必须遵守平台的规则,违规的短信是无法发送的。上面发短信时使用的短信模板(“发现一条您可能感兴趣的新闻 - ###,详情点击https://news.china.com/### 查看。”)和短信签名(“【Python小课】”)需要登录螺丝帽管理平台进行配置,如果不清楚如何配置,这些平台都是有客服的哟 > 说明:上面的代码使用了[螺丝帽](<https://luosimao.com/>)提供的短信网关服务,利用短信网关发送短信是需要支付费用的,但是一般的平台都会提供若干条免费的测试短信。发送短信必须遵守平台的规则,违规的短信是无法发送的。上面发短信时使用的短信模板(“发现一条您可能感兴趣的新闻 - ###,详情点击https://news.china.com/### 查看。”)和短信签名(“【Python小课】”)需要登录螺丝帽管理平台进行配置,如果不清楚如何配置,可以联系平台的客服人员进行咨询
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册