更新了部分文档和代码
|
@ -32,7 +32,7 @@
|
||||||
| \| | 分支 | foo\|bar | 可以匹配foo或者bar |
|
| \| | 分支 | foo\|bar | 可以匹配foo或者bar |
|
||||||
| (?#) | 注释 | | |
|
| (?#) | 注释 | | |
|
||||||
| (exp) | 匹配exp并捕获到自动命名的组中 | | |
|
| (exp) | 匹配exp并捕获到自动命名的组中 | | |
|
||||||
| (? <name>exp) | 匹配exp并捕获到名为name的组中 | | |
|
| (?<name>exp) | 匹配exp并捕获到名为name的组中 | | |
|
||||||
| (?:exp) | 匹配exp但是不捕获匹配的文本 | | |
|
| (?:exp) | 匹配exp但是不捕获匹配的文本 | | |
|
||||||
| (?=exp) | 匹配exp前面的位置 | \\b\\w+(?=ing) | 可以匹配I'm dancing中的danc |
|
| (?=exp) | 匹配exp前面的位置 | \\b\\w+(?=ing) | 可以匹配I'm dancing中的danc |
|
||||||
| (?<=exp) | 匹配exp后面的位置 | (?<=\\bdanc)\\w+\\b | 可以匹配I love dancing and reading中的第一个ing |
|
| (?<=exp) | 匹配exp后面的位置 | (?<=\\bdanc)\\w+\\b | 可以匹配I love dancing and reading中的第一个ing |
|
||||||
|
|
|
@ -68,7 +68,7 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div>
|
<div>
|
||||||
<input @keydown.enter="addItem()" type="text" id="fname" v-model="fname">
|
<input @keydown.enter="addItem()" type="text" id="fname" v-model.trim="fname">
|
||||||
<button id="ok" @click="addItem()">确定</button>
|
<button id="ok" @click="addItem()">确定</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -82,8 +82,8 @@
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
addItem() {
|
addItem() {
|
||||||
if (this.fname.trim().length > 0) {
|
if (this.fname.length > 0) {
|
||||||
this.fruits.push(this.fname.trim())
|
this.fruits.push(this.fname)
|
||||||
}
|
}
|
||||||
this.fname = ''
|
this.fname = ''
|
||||||
},
|
},
|
||||||
|
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
@ -65,7 +65,7 @@
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<!-- type属性是text的input标签代表文本框 可以接收用户输入 -->
|
<!-- type属性是text的input标签代表文本框 可以接收用户输入 -->
|
||||||
<!-- placeholder是文本框的输入提示 -->
|
<!-- placeholder是文本框的输入提示 -->
|
||||||
<input type="text" placeholder="请输入垃圾名字" v-model="word" @keydown.enter="search()">
|
<input type="text" placeholder="请输入垃圾名字" v-model.trim="word" @keydown.enter="search()">
|
||||||
<!-- button代表按钮 点击可以开始查询 -->
|
<!-- button代表按钮 点击可以开始查询 -->
|
||||||
<button @click="search()">查询</button>
|
<button @click="search()">查询</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -75,9 +75,9 @@
|
||||||
<div v-for="result in results">
|
<div v-for="result in results">
|
||||||
<p>
|
<p>
|
||||||
<!-- img是图像标签 可以用来实现图片-->
|
<!-- img是图像标签 可以用来实现图片-->
|
||||||
<img :src="pictures[result.type]" width="56" :alt="types[result.type]">
|
<img :src="'images/' + pictures[result.type]" width="56" :alt="types[result.type]">
|
||||||
|
|
||||||
<!-- span是跨度标签 代表一个逻辑区域(不分段)-->
|
<!-- span是跨度标签 代表一个逻辑区域-->
|
||||||
<span>{{ result.name }}</span>
|
<span>{{ result.name }}</span>
|
||||||
|
|
||||||
<span class="pre" v-if="result.aipre == 1">(预测结果)</span>
|
<span class="pre" v-if="result.aipre == 1">(预测结果)</span>
|
||||||
|
@ -102,7 +102,7 @@
|
||||||
// 查询垃圾分类的函数
|
// 查询垃圾分类的函数
|
||||||
search() {
|
search() {
|
||||||
if (this.word.trim().length > 0) {
|
if (this.word.trim().length > 0) {
|
||||||
let key = '9aeb28ee8858a167c1755f856f830e22'
|
let key = '9636cec76ee2593ba6b195e5b770b394'
|
||||||
let url = `http://api.tianapi.com/txapi/lajifenlei/?key=${key}&word=${this.word}`
|
let url = `http://api.tianapi.com/txapi/lajifenlei/?key=${key}&word=${this.word}`
|
||||||
fetch(url)
|
fetch(url)
|
||||||
.then(resp => resp.json())
|
.then(resp => resp.json())
|
|
@ -74,6 +74,8 @@ MySQL在过去由于性能高、成本低、可靠性好,已经成为最流行
|
||||||
```Shell
|
```Shell
|
||||||
rpm -ivh mysql-community-common-5.7.26-1.el7.x86_64.rpm
|
rpm -ivh mysql-community-common-5.7.26-1.el7.x86_64.rpm
|
||||||
rpm -ivh mysql-community-libs-5.7.26-1.el7.x86_64.rpm
|
rpm -ivh mysql-community-libs-5.7.26-1.el7.x86_64.rpm
|
||||||
|
rpm -ivh mysql-community-libs-compat-5.7.26-1.el7.x86_64.rpm
|
||||||
|
rpm -ivh mysql-community-devel-5.7.26-1.el7.x86_64.rpm
|
||||||
rpm -ivh mysql-community-client-5.7.26-1.el7.x86_64.rpm
|
rpm -ivh mysql-community-client-5.7.26-1.el7.x86_64.rpm
|
||||||
rpm -ivh mysql-community-server-5.7.26-1.el7.x86_64.rpm
|
rpm -ivh mysql-community-server-5.7.26-1.el7.x86_64.rpm
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
drop database if exists hrs;
|
drop database if exists hrs;
|
||||||
create database hrs default charset utf8;
|
create database hrs default charset utf8mb4;
|
||||||
|
|
||||||
use hrs;
|
use hrs;
|
||||||
|
|
||||||
|
@ -14,6 +14,8 @@ dloc varchar(20) not null comment '所在地',
|
||||||
primary key (dno)
|
primary key (dno)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- alter table tb_dept add constraint pk_dept_dno primary key(dno);
|
||||||
|
|
||||||
insert into tb_dept values
|
insert into tb_dept values
|
||||||
(10, '会计部', '北京'),
|
(10, '会计部', '北京'),
|
||||||
(20, '研发部', '成都'),
|
(20, '研发部', '成都'),
|
||||||
|
@ -29,11 +31,13 @@ mgr int comment '主管编号',
|
||||||
sal int not null comment '员工月薪',
|
sal int not null comment '员工月薪',
|
||||||
comm int comment '每月补贴',
|
comm int comment '每月补贴',
|
||||||
dno int comment '所在部门编号',
|
dno int comment '所在部门编号',
|
||||||
primary key (eno)
|
primary key (eno),
|
||||||
|
foreign key (dno) references tb_dept(dno),
|
||||||
|
foreign key (mgr) references tb_emp(eno)
|
||||||
);
|
);
|
||||||
|
|
||||||
alter table tb_emp add constraint fk_emp_mgr foreign key (mgr) references tb_emp (eno);
|
-- alter table tb_emp add constraint fk_emp_mgr foreign key (mgr) references tb_emp (eno);
|
||||||
alter table tb_emp add constraint fk_emp_dno foreign key (dno) references tb_dept (dno);
|
-- alter table tb_emp add constraint fk_emp_dno foreign key (dno) references tb_dept (dno);
|
||||||
|
|
||||||
insert into tb_emp values
|
insert into tb_emp values
|
||||||
(7800, '张三丰', '总裁', null, 9000, 1200, 20),
|
(7800, '张三丰', '总裁', null, 9000, 1200, 20),
|
||||||
|
@ -70,4 +74,4 @@ insert into tb_emp values
|
||||||
|
|
||||||
-- 查询主管的姓名和职位
|
-- 查询主管的姓名和职位
|
||||||
|
|
||||||
-- 查询月薪排名4~6名的员工姓名和月薪
|
-- 查询月薪排名4~6名的员工排名、姓名和月薪
|
||||||
|
|
|
@ -0,0 +1,339 @@
|
||||||
|
## Django快速上手
|
||||||
|
|
||||||
|
Web开发的早期阶段,开发者需要手动编写每个页面,例如一个新闻门户网站,每天都要修改它的HTML页面,随着网站规模和体量的增大,这种做法一定是非常糟糕的。为了解决这个问题,开发人员想到了用程序来为Web服务器生成动态内容,也就是说网页中的动态内容不再通过手动编写而是通过程序自动生成。最早的时候,这项技术被称为CGI(公共网关接口),当然随着时间的推移,CGI暴露出的问题也越来越多,例如大量重复的样板代码,总体性能较为低下等。在时代呼唤新英雄的背景下,PHP、ASP、JSP这类Web应用开发技术在上世纪90年代中后期如雨后春笋般涌现。通常我们说的Web应用是指通过浏览器来访问网络资源的应用程序,因为浏览器的普及性以及易用性,Web应用使用起来方便简单,免除了安装和更新应用程序带来的麻烦;站在开发者的角度,也不用关心用户使用什么样的操作系统,甚至不用区分是PC端还是移动端。
|
||||||
|
|
||||||
|
### Web应用机制和术语
|
||||||
|
|
||||||
|
下图向我们展示了Web应用的工作流程,其中涉及到的术语如下表所示。
|
||||||
|
|
||||||
|
![](./res/web-application.png)
|
||||||
|
|
||||||
|
> 说明:相信有经验的读者会发现,这张图中其实还少了很多东西,例如反向代理服务器、数据库服务器、防火墙等,而且图中的每个节点在实际项目部署时可能是一组节点组成的集群。当然,如果你对这些没有什么概念也不要紧,继续下去就行了,后面会给大家一一讲解的。
|
||||||
|
|
||||||
|
| 术语 | 解释 |
|
||||||
|
| ------------- | ------------------------------------------------------------ |
|
||||||
|
| **URL/URI** | 统一资源定位符/统一资源标识符,网络资源的唯一标识 |
|
||||||
|
| **域名** | 与Web服务器地址对应的一个易于记忆的字符串名字 |
|
||||||
|
| **DNS** | 域名解析服务,可以将域名转换成对应的IP地址 |
|
||||||
|
| **IP地址** | 网络上的主机的身份标识,通过IP地址可以区分不同的主机 |
|
||||||
|
| **HTTP** | 超文本传输协议,构建在TCP之上的应用级协议,万维网数据通信的基础 |
|
||||||
|
| **反向代理** | 代理客户端向服务器发出请求,然后将服务器返回的资源返回给客户端 |
|
||||||
|
| **Web服务器** | 接受HTTP请求,然后返回HTML文件、纯文本文件、图像等资源给请求者 |
|
||||||
|
| **Nginx** | 高性能的Web服务器,也可以用作[反向代理](https://zh.wikipedia.org/wiki/%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86),[负载均衡](https://zh.wikipedia.org/wiki/%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1) 和 [HTTP缓存](https://zh.wikipedia.org/wiki/HTTP%E7%BC%93%E5%AD%98) |
|
||||||
|
|
||||||
|
#### 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)这几篇文章进行了解。下图是我在四川省网络通信技术重点实验室学习和工作期间使用开源协议分析工具Ethereal(抓包工具WireShark的前身)截取的访问百度首页时的HTTP请求和响应的报文(协议数据),由于Ethereal截取的是经过网络适配器的数据,因此可以清晰的看到从物理链路层到应用层的协议数据。
|
||||||
|
|
||||||
|
HTTP请求(请求行+请求头+空行+[消息体]):
|
||||||
|
|
||||||
|
![](./res/http-request.png)
|
||||||
|
|
||||||
|
HTTP响应(响应行+响应头+空行+消息体):
|
||||||
|
|
||||||
|
![](./res/http-response.png)
|
||||||
|
|
||||||
|
> **说明**:这两张图是在2009年9月10日凌晨获得的,但愿这两张如同泛黄的照片般的截图能帮助你了解HTTP到底是什么样子的。当然,如果没有专业的抓包工具,也可以通过浏览器提供的“开发者工具”来查看HTTP请求和响应的数据格式。
|
||||||
|
|
||||||
|
### Django概述
|
||||||
|
|
||||||
|
Python的Web框架有上百个,比它的关键字还要多。所谓Web框架,就是用于开发Web服务器端应用的基础设施,说得通俗一点就是一系列封装好的模块和工具。事实上,即便没有Web框架,我们仍然可以通过socket或[CGI](https://zh.wikipedia.org/wiki/%E9%80%9A%E7%94%A8%E7%BD%91%E5%85%B3%E6%8E%A5%E5%8F%A3)来开发Web服务器端应用,但是这样做的成本和代价在商业项目中通常是不能接受的。通过Web框架,我们可以化繁为简,降低创建、更新、扩展应用程序的工作量。刚才我们说到Python有上百个Web框架,这些框架包括Django、Flask、Tornado、Sanic、Pyramid、Bottle、Web2py、web.py等。
|
||||||
|
|
||||||
|
在上述Python的Web框架中,Django无疑是最有代表性的重量级选手,开发者可以基于Django快速的开发可靠的Web应用程序,因为它减少了Web开发中不必要的开销,对常用的设计和开发模式进行了封装,并对MVC架构提供了支持(Django中称之为MTV架构)。MVC是软件系统开发领域中一种放之四海而皆准的架构,它将系统中的组件分为模型(Model)、视图(View)和控制器(Controller)三个部分并借此实现模型(数据)和视图(显示)的解耦合。由于模型和视图进行了分离,所以需要一个中间人将解耦合的模型和视图联系起来,扮演这个角色的就是控制器。稍具规模的软件系统都会使用MVC架构(或者是从MVC演进出的其他架构),Django项目中我们称之为MTV,MTV中的M跟MVC中的M没有区别,就是代表数据的模型,T代表了网页模板(显示数据的视图),而V代表了视图函数,在Django框架中,视图函数和Django框架本身一起扮演了MVC中C的角色。
|
||||||
|
|
||||||
|
![](./res/mvc.png)
|
||||||
|
|
||||||
|
Django框架诞生于2003年,它是一个在真正的应用中成长起来的项目,由劳伦斯出版集团旗下在线新闻网站的内容管理系统(CMS)研发团队(主要是Adrian Holovaty和Simon Willison)开发,以比利时的吉普赛爵士吉他手Django Reinhardt来命名。Django框架在2005年夏天作为开源框架发布,使用Django框架能用很短的时间构建出功能完备的网站,因为它代替程序员完成了那些重复乏味的劳动,剩下真正有意义的核心业务给程序员来开发,这一点就是对DRY(Don't Repeat Yourself)理念的最好践行。许多成功的网站和应用都是基于Python语言进行开发的,国内比较有代表性的网站包括:知乎、豆瓣网、果壳网、搜狐闪电邮箱、101围棋网、海报时尚网、背书吧、堆糖、手机搜狐网、咕咚、爱福窝、果库等,其中不乏使用了Django框架的产品。
|
||||||
|
|
||||||
|
### 快速上手
|
||||||
|
|
||||||
|
#### 第一个Django项目
|
||||||
|
|
||||||
|
1. 检查Python环境:Django 1.11需要Python 2.7或Python 3.4以上的版本;Django 2.0需要Python 3.4以上的版本;Django 2.1和2.2需要Python 3.5以上的版本;Django 3.0需要Python 3.6以上版本。
|
||||||
|
|
||||||
|
> **说明**:Django框架不同版本所需的Python解释器环境,可以在Django官方文档的[FAQ](https://docs.djangoproject.com/zh-hans/3.0/faq/install/#faq-python-version-support)中找到。
|
||||||
|
|
||||||
|
可以在macOS的终端中输入下面的命令检查Python解释器版本,Windows系统可以在命令行提示符中输入`python --version`。
|
||||||
|
|
||||||
|
```Bash
|
||||||
|
python3 --version
|
||||||
|
```
|
||||||
|
|
||||||
|
也可以在Python的交互式环境中执行下面的代码来查看Python解释器的版本。
|
||||||
|
|
||||||
|
```Shell
|
||||||
|
import sys
|
||||||
|
sys.version
|
||||||
|
sys.version_info
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 更新包管理工具并安装Django环境(用于创建Django项目)。
|
||||||
|
|
||||||
|
> **说明**:在更新这个文档时,Django最新的正式版本是3.0.7,Django 3.0提供了对ASGI的支持,可以实现全双工的异步通信,但是目前的使用体验一般,所以暂时不推荐大家使用Django 3.0,下面我们安装的是Django 2.2.13版本。使用`pip`安装三方库和工具时,可以通过`==`来指定安装的版本。
|
||||||
|
|
||||||
|
```Bash
|
||||||
|
pip3 install -U pip
|
||||||
|
pip3 install django==2.2.13
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 检查Django环境并使用`django-admin`命令创建Django项目(项目名称为hellodjango)。
|
||||||
|
|
||||||
|
```Shell
|
||||||
|
django-admin --version
|
||||||
|
django-admin startproject hellodjango
|
||||||
|
```
|
||||||
|
|
||||||
|
4. 用PyCharm打开创建好的Djang项目,并为其添加虚拟环境。
|
||||||
|
|
||||||
|
![](res/pycharm-django-project.png)
|
||||||
|
|
||||||
|
如上图所示,PyCharm的项目浏览器中,最顶层的文件夹`hellodjango`是Python项目文件夹,这个文件夹的名字并不重要,Django项目也不关心这个文件夹叫什么名字。该文件夹下有一个同名的文件夹,它是Django项目文件夹,其中包含了`__init__.py`、`settings.py`、`urls.py`、`wsgi.py`四个文件,与名为`hellodjango`的Django项目文件夹同级的还有一个名为`manage.py` 的文件,这些文件的作用如下所示:
|
||||||
|
|
||||||
|
- `hellodjango/__init__.py`:空文件,告诉Python解释器这个目录应该被视为一个Python的包。
|
||||||
|
- `hellodjango/settings.py`:Django项目的配置文件。
|
||||||
|
- `hellodjango/urls.py`:Django项目的URL映射声明,就像是网站的“目录”。
|
||||||
|
- `hellodjango/wsgi.py`:项目运行在WSGI兼容Web服务器上的入口文件。
|
||||||
|
- `manage.py`: 管理Django项目的脚本程序。
|
||||||
|
|
||||||
|
> 说明: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)或框架之间的一种简单而通用的接口”。
|
||||||
|
|
||||||
|
创建虚拟环境的界面如下图所示。
|
||||||
|
|
||||||
|
![pycharm-django-virtual-environment](res/pycharm-django-virtual-environment.png)
|
||||||
|
|
||||||
|
5. 安装项目依赖项。
|
||||||
|
|
||||||
|
方法一:打开PyCharm的终端,在终端中通过`pip`命令安装Django项目的依赖项。
|
||||||
|
|
||||||
|
> **说明**:由于已经基于Python 3解释器环境为项目创建了虚拟环境,所以虚拟环境中的`python`命令对应的是Python 3的解释器,而`pip`命令对应的是Python 3的包管理工具。
|
||||||
|
|
||||||
|
```Shell
|
||||||
|
pip install django==2.2.13
|
||||||
|
```
|
||||||
|
|
||||||
|
方法二:在PyCharm的偏好设置中,可以找到项目的解释器环境和已经安装的三方库,可以通过点击添加按钮来安装新的依赖项,需要提醒大家的是在安装Django依赖项时,需要指定版本号,否则将默认安装更新本文时最新的3.0.7版本。
|
||||||
|
|
||||||
|
![](res/pycharm-install-django.png)
|
||||||
|
|
||||||
|
下图展示了Django版本和Python版本的对应关系,请大家自行对号入座。
|
||||||
|
|
||||||
|
| Django版本 | Python版本 |
|
||||||
|
| ---------- | ----------------------------------------- |
|
||||||
|
| 1.8 | 2.7、3.2、3.3、3.4、3.5 |
|
||||||
|
| 1.9、1.10 | 2.7、3.4、3.5 |
|
||||||
|
| 1.11 | 2.7、3.4、3.5、3.6、3.7(Django 1.11.17) |
|
||||||
|
| 2.0 | 3.4、3.5、3.6、3.7 |
|
||||||
|
| 2.1 | 3.5、3.6、3.7 |
|
||||||
|
| 2.2 | 3.5、3.6、3.7、3.8(Django 2.2.8) |
|
||||||
|
| 3.0 | 3.6、3.7、3.8 |
|
||||||
|
|
||||||
|
6. 启动Django自带的服务器运行项目。
|
||||||
|
|
||||||
|
方法一:在“Run”菜单选择“Edit Configuration”,配置“Django server”运行项目(适用于专业版PyCharm)。
|
||||||
|
|
||||||
|
![](res/pycharm-django-server.png)
|
||||||
|
|
||||||
|
方法二:在“Run”菜单选择“Edit Configuration”,配置运行“Python”程序运行项目(适用于专业版和社区版PyCharm)。
|
||||||
|
|
||||||
|
![](res/pycharm-python-manage.png)
|
||||||
|
|
||||||
|
方法三:在PyCharm的终端(Terminal)中通过命令运行项目(适用于专业版和社区版PyCharm)。
|
||||||
|
|
||||||
|
```Shell
|
||||||
|
python manage.py runserver
|
||||||
|
```
|
||||||
|
|
||||||
|
7. 查看运行效果。
|
||||||
|
|
||||||
|
在浏览器中输入`http://127.0.0.1:8000`访问我们的服务器,效果如下图所示。
|
||||||
|
|
||||||
|
![](./res/django-index-1.png)
|
||||||
|
|
||||||
|
> **说明**:
|
||||||
|
>
|
||||||
|
> 1. 刚刚启动的Django自带的服务器只能用于开发和测试环境,因为这个服务器是纯Python编写的轻量级Web服务器,不适合在生产环境中使用。
|
||||||
|
> 2. 如果修改了代码,不需要为了让修改的代码生效而重新启动Django自带的服务器。但是,在添加新的项目文件时,该服务器不会自动重新加载,这个时候就得手动重启服务器。
|
||||||
|
> 3. 可以在终端中通过`python manage.py help`命令查看Django管理脚本程序可用的命令参数。
|
||||||
|
> 4. 使用`python manage.py runserver`启动服务器时,可以在后面添加参数来指定IP地址和端口号,默认情况下启动的服务器将运行在本机的`8000`端口。
|
||||||
|
> 5. 在终端中运行的服务器,可以通过Ctrl+C来停止它 。通过PyCharm的“运行配置”运行的服务器直接点击窗口上的关闭按钮就可以终止服务器的运行。
|
||||||
|
> 6. 不能在同一个端口上启动多个服务器,因为会导致地址的冲突(端口是对IP地址的扩展,也是计算机网络地址的一部分)。
|
||||||
|
8. 修改项目的配置文件`settings.py`。
|
||||||
|
|
||||||
|
Django是一个支持国际化和本地化的框架,因此刚才我们看到的Django项目的默认首页也是支持国际化的,我们可以通过修改配置文件将默认语言修改为中文,时区设置为东八区。
|
||||||
|
|
||||||
|
找到修改前的配置(在`settings.py`文件第100行以后)。
|
||||||
|
|
||||||
|
```Python
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
```
|
||||||
|
|
||||||
|
修改为以下内容。
|
||||||
|
|
||||||
|
```Python
|
||||||
|
LANGUAGE_CODE = 'zh-hans'
|
||||||
|
TIME_ZONE = 'Asia/Chongqing'
|
||||||
|
```
|
||||||
|
|
||||||
|
刷新刚才的页面,可以看到修改语言代码和时区之后的结果。
|
||||||
|
|
||||||
|
![](./res/django-index-2.png)
|
||||||
|
|
||||||
|
#### 创建自己的应用
|
||||||
|
|
||||||
|
如果要开发自己的Web应用,需要先在Django项目中创建“应用”,一个Django项目可以包含一个或多个应用。
|
||||||
|
|
||||||
|
1. 在PyCharm的终端中执行下面的命令,创建名为`first`的应用。
|
||||||
|
|
||||||
|
```Shell
|
||||||
|
python manage.py startapp first
|
||||||
|
```
|
||||||
|
|
||||||
|
执行上面的命令会在当前路径下创建`first`目录,其目录结构如下所示:
|
||||||
|
|
||||||
|
- `__init__.py`:一个空文件,告诉Python解释器这个目录应该被视为一个Python的包。
|
||||||
|
- `admin.py`:可以用来注册模型,用于在Django框架自带的管理后台中管理模型。
|
||||||
|
- `apps.py`:当前应用的配置文件。
|
||||||
|
- `migrations`:存放与模型有关的数据库迁移信息。
|
||||||
|
- `__init__.py`:一个空文件,告诉Python解释器这个目录应该被视为一个Python的包。
|
||||||
|
- `models.py`:存放应用的数据模型(MTV中的M)。
|
||||||
|
- `tests.py`:包含测试应用各项功能的测试类和测试函数。
|
||||||
|
- `views.py`:处理用户HTTP请求并返回HTTP响应的函数或类(MTV中的V)。
|
||||||
|
|
||||||
|
2. 修改应用目录下的视图文件`views.py`。
|
||||||
|
|
||||||
|
```Python
|
||||||
|
from django.http import HttpResponse
|
||||||
|
|
||||||
|
|
||||||
|
def show_index(request):
|
||||||
|
return HttpResponse('<h1>Hello, Django!</h1>')
|
||||||
|
```
|
||||||
|
|
||||||
|
4. 修改Django项目目录下的`urls.py`文件,将视图函数和用户在浏览器中请求的路径对应。
|
||||||
|
|
||||||
|
```Python
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path, include
|
||||||
|
|
||||||
|
from first.views import show_index
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
path('hello/', show_index),
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
5. 重新运行项目,并打开浏览器中访问`http://127.0.0.1:8000/hello/`。
|
||||||
|
|
||||||
|
5. 上面我们通过代码为浏览器生成了内容,但仍然是静态内容,如果要生成动态内容,可以修改`views.py`文件并添加如下所示的代码。
|
||||||
|
|
||||||
|
```Python
|
||||||
|
from random import sample
|
||||||
|
|
||||||
|
from django.http import HttpResponse
|
||||||
|
|
||||||
|
|
||||||
|
def show_index(request):
|
||||||
|
fruits = [
|
||||||
|
'Apple', 'Orange', 'Pitaya', 'Durian', 'Waxberry', 'Blueberry',
|
||||||
|
'Grape', 'Peach', 'Pear', 'Banana', 'Watermelon', 'Mango'
|
||||||
|
]
|
||||||
|
selected_fruits = sample(fruits, 3)
|
||||||
|
content = '<h3>今天推荐的水果是:</h3>'
|
||||||
|
content += '<hr>'
|
||||||
|
content += '<ul>'
|
||||||
|
for fruit in selected_fruits:
|
||||||
|
content += f'<li>{fruit}</li>'
|
||||||
|
content += '</ul>'
|
||||||
|
return HttpResponse(content)
|
||||||
|
```
|
||||||
|
|
||||||
|
6. 刷新页面查看程序的运行结果,看看每次刷新的网页的时候,是不是可以看到不一样的内容。
|
||||||
|
|
||||||
|
|
||||||
|
#### 使用模板
|
||||||
|
|
||||||
|
上面通过拼接HTML代码的方式为浏览器生成动态内容的做法在实际开发中是无能接受的,因为实际项目中的前端页面可能非常复杂,无法用这种拼接动态内容的方式来完成,这一点大家一定能够想到。为了解决这个问题,我们可以提前准备一个模板页(MTV中的T),所谓模板页就是一个带占位符和模板指令的HTML页面。
|
||||||
|
|
||||||
|
Django框架中有一个名为`render`的便捷函数可以来完成渲染模板的操作。所谓的渲染就是用数据替换掉模板页中的模板指令和占位符,当然这里的渲染称为后端渲染,即在服务器端完成页面的渲染再输出到浏览器中。后端渲染的做法在Web应用的访问量较大时,会让服务器承受较大的负担,所以越来越多的Web应用会选择前端渲染的方式,即服务器只提供页面所需的数据(通常是JSON格式),在浏览器中通过JavaScript代码获取这些数据并渲染页面上。关于前端渲染的内容,我们会在后续的课程中为大家讲解,目前我们使用的是通过模板页进行后端渲染的做法,具体步骤如下所示。
|
||||||
|
|
||||||
|
使用模板页的步骤如下所示。
|
||||||
|
|
||||||
|
1. 在项目目录下创建名为templates文件夹。
|
||||||
|
|
||||||
|
![](res/pycharm-django-template.png)
|
||||||
|
|
||||||
|
2. 添加模板页`index.html`。
|
||||||
|
|
||||||
|
> **说明**:实际项目开发中,静态页由前端开发者提供,后端开发者需要将静态页修改为模板页,以便通过Python程序对其进行渲染,这种做法就是上面提到的后端渲染。
|
||||||
|
|
||||||
|
```HTML
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>首页</title>
|
||||||
|
<style>
|
||||||
|
#fruits {
|
||||||
|
font-size: 1.25em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>今天推荐的水果是:</h1>
|
||||||
|
<hr>
|
||||||
|
<ul id="fruits">
|
||||||
|
{% for fruit in fruits %}
|
||||||
|
<li>{{ fruit }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
在上面的模板页中我们使用了`{{ fruit }}`这样的模板占位符语法,也使用了`{% for %}`这样的模板指令,这些都是Django模板语言(DTL)的一部分。关于模板语法和指令,大家可以看看官方文档,相信这些内容还是很容易理解的,并不需要过多的赘述,大家也可以参考[官方文档]()了解模板指令和语法。
|
||||||
|
|
||||||
|
3. 修改`views.py`文件,调用`render`函数渲染模板页。
|
||||||
|
|
||||||
|
```Python
|
||||||
|
from random import sample
|
||||||
|
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
|
||||||
|
def show_index(request):
|
||||||
|
fruits = [
|
||||||
|
'Apple', 'Orange', 'Pitaya', 'Durian', 'Waxberry', 'Blueberry',
|
||||||
|
'Grape', 'Peach', 'Pear', 'Banana', 'Watermelon', 'Mango'
|
||||||
|
]
|
||||||
|
selected_fruits = sample(fruits, 3)
|
||||||
|
return render(request, 'index.html', {'fruits': selected_fruits})
|
||||||
|
```
|
||||||
|
|
||||||
|
`render`函数的第一个参数是请求对象request,第二个参数是我们要渲染的模板页的名字,第三个参数是要渲染到页面上的数据,我们通过一个字典将数据交给模板页,字典中的键就是模板页中使用的模板指令或占位符中的变量名。
|
||||||
|
|
||||||
|
4. 到此为止,视图函数中的`render`还无法找到模板文件`index.html`,需要修改`settings.py`文件,配置模板文件所在的路径。修改`settings.py`文件,找到`TEMPLATES`配置,修改其中的`DIRS`配置。
|
||||||
|
|
||||||
|
```Python
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [os.path.join(BASE_DIR, 'templates'), ],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
5. 重新运行项目或直接刷新页面查看结果。
|
||||||
|
|
||||||
|
### 总结
|
||||||
|
|
||||||
|
至此,我们已经利用Django框架完成了一个非常小的Web应用,虽然它并没有任何的实际价值,但是可以通过这个项目对Django框架有一个感性的认识。学习Django最好的资料肯定是它的[官方文档](https://docs.djangoproject.com/zh-hans/2.0/),官方文档提供了对多国语言的支持,而且有新手教程引导初学者学习使用Django框架,建议大家通过阅读Django的官方文档来学习和使用这个框架。当然图灵社区出版的[《Django基础教程》](http://www.ituring.com.cn/book/2630)也是非常适合初学者的入门级读物,有兴趣的读者可以点击链接进行购买。
|
|
@ -1,393 +0,0 @@
|
||||||
## 快速上手
|
|
||||||
|
|
||||||
Web开发的早期阶段,开发者需要手动编写每个页面,例如一个新闻门户网站,每天都要修改它的HTML页面,随着网站规模和体量的增大,这种方式就变得极度糟糕。为了解决这个问题,开发人员想到了用外部程序来为Web服务器生成动态内容,也就是说HTML页面以及页面中的动态内容不再通过手动编写而是通过程序自动生成。最早的时候,这项技术被称为CGI(公共网关接口),当然随着时间的推移,CGI暴露出的问题也越来越多,例如大量重复的样板代码,总体性能较为低下等,因此在时代呼唤新英雄的背景下,PHP、ASP、JSP这类Web应用开发技术在上世纪90年代中后期如雨后春笋般涌现。通常我们说的Web应用是指通过浏览器来访问网络资源的应用程序,因为浏览器的普及性以及易用性,Web应用使用起来方便简单,免除了安装和更新应用程序带来的麻烦,而且也不用关心用户到底用的是什么操作系统,甚至不用区分是PC端还是移动端。
|
|
||||||
|
|
||||||
### Web应用机制和术语
|
|
||||||
|
|
||||||
下图向我们展示了Web应用的工作流程,其中涉及到的术语如下表所示。
|
|
||||||
|
|
||||||
![](./res/web-application.png)
|
|
||||||
|
|
||||||
> 说明:相信有经验的读者会发现,这张图中其实还少了很多东西,例如反向代理服务器、数据库服务器、防火墙等,而且图中的每个节点在实际项目部署时可能是一组节点组成的集群。当然,如果你对这些没有什么概念也不要紧,继续下去就行了,后面会给大家一一讲解的。
|
|
||||||
|
|
||||||
| 术语 | 解释 |
|
|
||||||
| ------------- | ------------------------------------------------------------ |
|
|
||||||
| **URL/URI** | 统一资源定位符/统一资源标识符,网络资源的唯一标识 |
|
|
||||||
| **域名** | 与Web服务器地址对应的一个易于记忆的字符串名字 |
|
|
||||||
| **DNS** | 域名解析服务,可以将域名转换成对应的IP地址 |
|
|
||||||
| **IP地址** | 网络上的主机的身份标识,通过IP地址可以区分不同的主机 |
|
|
||||||
| **HTTP** | 超文本传输协议,构建在TCP之上的应用级协议,万维网数据通信的基础 |
|
|
||||||
| **反向代理** | 代理客户端向服务器发出请求,然后将服务器返回的资源返回给客户端 |
|
|
||||||
| **Web服务器** | 接受HTTP请求,然后返回HTML文件、纯文本文件、图像等资源给请求者 |
|
|
||||||
| **Nginx** | 高性能的Web服务器,也可以用作[反向代理](https://zh.wikipedia.org/wiki/%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86),[负载均衡](https://zh.wikipedia.org/wiki/%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1) 和 [HTTP缓存](https://zh.wikipedia.org/wiki/HTTP%E7%BC%93%E5%AD%98) |
|
|
||||||
|
|
||||||
#### 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)进行了解。下图是我在四川省网络通信技术重点实验室学习和工作期间使用开源协议分析工具Ethereal(抓包工具WireShark的前身)截取的访问百度首页时的HTTP请求和响应的报文(协议数据),由于Ethereal截取的是经过网络适配器的数据,因此可以清晰的看到从物理链路层到应用层的协议数据。
|
|
||||||
|
|
||||||
HTTP请求(请求行+请求头+空行+[消息体]):
|
|
||||||
|
|
||||||
![](./res/http-request.png)
|
|
||||||
|
|
||||||
HTTP响应(响应行+响应头+空行+消息体):
|
|
||||||
|
|
||||||
![](./res/http-response.png)
|
|
||||||
|
|
||||||
> 说明:这两张图是在2009年9月10日凌晨获得的,但愿这两张如同泛黄的照片般的截图能帮助你了解HTTP到底是什么样子的。
|
|
||||||
|
|
||||||
### Django概述
|
|
||||||
|
|
||||||
Python的Web框架有上百个,比它的关键字还要多。所谓Web框架,就是用于开发Web服务器端应用的基础设施,说得通俗一点就是一系列封装好的模块和工具。事实上,即便没有Web框架,我们仍然可以通过socket或[CGI](https://zh.wikipedia.org/wiki/%E9%80%9A%E7%94%A8%E7%BD%91%E5%85%B3%E6%8E%A5%E5%8F%A3)来开发Web服务器端应用,但是这样做的成本和代价在商业项目中通常是不能接受的。通过Web框架,我们可以化繁为简,降低创建、更新、扩展应用程序的工作量。刚才我们说到Python有上百个Web框架,这些框架包括Django、Flask、Tornado、Sanic、Pyramid、Bottle、Web2py、web.py等。
|
|
||||||
|
|
||||||
在上述Python的Web框架中,Django无疑是最有代表性的重量级选手,开发者可以基于Django快速的开发可靠的Web应用程序,因为它减少了Web开发中不必要的开销,对常用的设计和开发模式进行了封装,并对MVC架构提供了支持(Django中称之为MTV架构)。许多成功的网站和应用都是基于Django框架构建的,国内比较有代表性的网站包括:知乎、豆瓣网、果壳网、搜狐闪电邮箱、101围棋网、海报时尚网、背书吧、堆糖、手机搜狐网、咕咚、爱福窝、果库等。
|
|
||||||
|
|
||||||
![](./res/mvc.png)
|
|
||||||
|
|
||||||
Django诞生于2003年,它是一个在真正的应用中成长起来的项目,由劳伦斯出版集团旗下在线新闻网站的内容管理系统(CMS)研发团队编写(主要是Adrian Holovaty和Simon Willison),以比利时的吉普赛爵士吉他手Django Reinhardt来命名,在2005年夏天作为开源框架发布。使用Django能用很短的时间构建出功能完备的网站,因为它代替程序员完成了所有乏味和重复的劳动,剩下真正有意义的核心业务给程序员,这一点就是对DRY(Don't Repeat Yourself)理念的最好践行。
|
|
||||||
|
|
||||||
### 快速上手
|
|
||||||
|
|
||||||
#### 准备工作
|
|
||||||
|
|
||||||
1. 检查Python环境:Django 1.11需要Python 2.7或Python 3.4以上的版本;Django 2.0需要Python 3.4以上的版本;Django 2.1需要Python 3.5以上的版本。
|
|
||||||
|
|
||||||
> 说明:我自己平时使用macOS和Linux系统做开发,macOS和Linux系统在命令的使用上跟Windows系统还是有一些差别,如果使用Windows平台做开发,要使用Windows平台对应的命令。
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
$ python3 --version
|
|
||||||
```
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
$ python3
|
|
||||||
>>> import sys
|
|
||||||
>>> sys.version
|
|
||||||
>>> sys.version_info
|
|
||||||
```
|
|
||||||
|
|
||||||
2. 更新包管理工具并安装Django管理工具。
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
$ pip3 install -U pip
|
|
||||||
$ pip3 install django
|
|
||||||
```
|
|
||||||
|
|
||||||
3. 使用Django管理工具创建Django项目(项目名称为hellodjango)。
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
$ django-admin startproject hellodjango
|
|
||||||
```
|
|
||||||
> 说明:上面使用了Python自带的venv模块完成了虚拟环境的创建,当然也可以使用virtualenv或pipenv这样的工具。要激活虚拟环境,在Windows环境下可以通过"venv/Scripts/activate"执行批处理文件来实现。
|
|
||||||
|
|
||||||
4. 进入项目文件夹,创建并激活虚拟环境。
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
$ cd hellodjango
|
|
||||||
$ python3 -m venv venv
|
|
||||||
$ source venv/bin/activate
|
|
||||||
```
|
|
||||||
|
|
||||||
> **提示**:上面使用了Python 3自带的`venv`模块来创建虚拟环境,当然也可以使用如`virtualenv`这样的三方工具来创建虚拟环境;激活虚拟环境后请注意终端中提示符的变化,在虚拟环境下使用Python解释器和包管理工具时,对应的命令是`python`和`pip`,而不再需要键入`python3`和`pip3`。
|
|
||||||
|
|
||||||
5. 在虚拟环境中安装项目依赖项。
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
(venv)$ pip install django mysqlclient django-redis pillow requests
|
|
||||||
```
|
|
||||||
|
|
||||||
> **提示**:使用`pip`安装三方库时,可以通过如`django==1.11.27`的方式来指定三方库的版本。
|
|
||||||
|
|
||||||
下图展示了Django版本和Python版本的对应关系,如果在安装时没有指定版本号,将自动选择最新的版本(在写作这段内容时,Django最新的版本是2.2)。
|
|
||||||
|
|
||||||
| Django版本 | Python版本 |
|
|
||||||
| ---------- | ----------------------- |
|
|
||||||
| 1.8 | 2.7、3.2、3.3、3.4、3.5 |
|
|
||||||
| 1.9、1.10 | 2.7、3.4、3.5 |
|
|
||||||
| 1.11 | 2.7、3.4、3.5、3.6、3.7 |
|
|
||||||
| 2.0 | 3.4、3.5、3.6、3.7 |
|
|
||||||
| 2.1、2.2 | 3.5、3.6、3.7 |
|
|
||||||
|
|
||||||
刚才创建的Django项目其文件和文件夹如下所示:
|
|
||||||
|
|
||||||
- `manage.py`: 一个让你可以管理Django项目的工具程序。
|
|
||||||
- `hellodjango/__init__.py`:一个空文件,告诉Python解释器这个目录应该被视为一个Python的包。
|
|
||||||
- `hellodjango/settings.py`:Django项目的配置文件。
|
|
||||||
- `hellodjango/urls.py`:Django项目的URL声明(URL映射),就像是你的网站的“目录”。
|
|
||||||
- `hellodjango/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)或框架之间的一种简单而通用的接口”。
|
|
||||||
|
|
||||||
6. 启动Django自带的服务器运行项目。
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
(venv)$ python manage.py runserver
|
|
||||||
```
|
|
||||||
|
|
||||||
在浏览器中输入<http://127.0.0.1:8000>访问我们的服务器,效果如下图所示。
|
|
||||||
|
|
||||||
> **说明1**:刚刚启动的是Django自带的用于开发和测试的服务器,它是一个用纯Python编写的轻量级Web服务器,但它并不是真正意义上的生产级别的服务器,千万不要将这个服务器用于和生产环境相关的任何地方。
|
|
||||||
>
|
|
||||||
> **说明2**:用于开发的服务器在需要的情况下会对每一次的访问请求重新载入一遍Python代码。所以你不需要为了让修改的代码生效而频繁的重新启动服务器。然而,一些动作,比如添加新文件,将不会触发自动重新加载,这时你得自己手动重启服务器。
|
|
||||||
>
|
|
||||||
> **说明3**:可以通过`python manage.py help`命令查看可用命令列表;在启动服务器时,也可以通过`python manage.py runserver 1.2.3.4:5678`来指定将服务器运行于哪个IP地址和端口。
|
|
||||||
>
|
|
||||||
> **说明4**:可以通过Ctrl+C来终止服务器的运行。
|
|
||||||
|
|
||||||
![](./res/django-index-1.png)
|
|
||||||
|
|
||||||
7. 修改项目的配置文件settings.py,Django是一个支持国际化和本地化的框架,因此刚才我们看到的默认首页也是支持国际化的,我们将默认语言修改为中文,时区设置为东八区。
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
(venv)$ vim hellodjango/settings.py
|
|
||||||
```
|
|
||||||
|
|
||||||
```Python
|
|
||||||
# 此处省略上面的内容
|
|
||||||
|
|
||||||
# 设置语言代码
|
|
||||||
LANGUAGE_CODE = 'zh-hans'
|
|
||||||
# 设置时区
|
|
||||||
TIME_ZONE = 'Asia/Chongqing'
|
|
||||||
|
|
||||||
# 此处省略下面的内容
|
|
||||||
```
|
|
||||||
|
|
||||||
刷新刚才的页面,可以看到修改语言代码和时区之后的结果。
|
|
||||||
|
|
||||||
![](./res/django-index-2.png)
|
|
||||||
|
|
||||||
|
|
||||||
#### 动态页面
|
|
||||||
|
|
||||||
1. 创建名为hrs(人力资源系统)的应用,一个Django项目可以包含一个或多个应用。
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
(venv)$ python manage.py startapp hrs
|
|
||||||
```
|
|
||||||
|
|
||||||
执行上面的命令会在当前路径下创建hrs目录,其目录结构如下所示:
|
|
||||||
|
|
||||||
- `__init__.py`:一个空文件,告诉Python解释器这个目录应该被视为一个Python的包。
|
|
||||||
- `admin.py`:可以用来注册模型,用于在Django的管理界面管理模型。
|
|
||||||
- `apps.py`:当前应用的配置文件。
|
|
||||||
- `migrations`:存放与模型有关的数据库迁移信息。
|
|
||||||
- `__init__.py`:一个空文件,告诉Python解释器这个目录应该被视为一个Python的包。
|
|
||||||
- `models.py`:存放应用的数据模型,即实体类及其之间的关系(MVC/MTV中的M)。
|
|
||||||
- `tests.py`:包含测试应用各项功能的测试类和测试函数。
|
|
||||||
- `views.py`:处理请求并返回响应的函数(MVC中的C,MTV中的V)。
|
|
||||||
|
|
||||||
2. 修改应用目录下的视图文件views.py。
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
(venv)$ vim hrs/views.py
|
|
||||||
```
|
|
||||||
|
|
||||||
```Python
|
|
||||||
from django.http import HttpResponse
|
|
||||||
|
|
||||||
|
|
||||||
def index(request):
|
|
||||||
return HttpResponse('<h1>Hello, Django!</h1>')
|
|
||||||
```
|
|
||||||
|
|
||||||
3. 在应用目录创建一个urls.py文件并映射URL。
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
(venv)$ touch hrs/urls.py
|
|
||||||
(venv)$ vim hrs/urls.py
|
|
||||||
```
|
|
||||||
|
|
||||||
```Python
|
|
||||||
from django.urls import path
|
|
||||||
|
|
||||||
from hrs import views
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path('', views.index, name='index'),
|
|
||||||
]
|
|
||||||
```
|
|
||||||
> 说明:上面使用的`path`函数是Django 2.x中新添加的函数,除此之外还可以使用支持正则表达式的URL映射函数`re_path`函数;Django 1.x中是用名为`url`函数来设定URL映射。
|
|
||||||
|
|
||||||
4. 修改项目目录下的urls.py文件,对应用中设定的URL进行合并。
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
(venv) $ vim oa/urls.py
|
|
||||||
```
|
|
||||||
|
|
||||||
```Python
|
|
||||||
from django.contrib import admin
|
|
||||||
from django.urls import path, include
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path('admin/', admin.site.urls),
|
|
||||||
path('hrs/', include('hrs.urls')),
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
> 说明:上面的代码通过`include`函数将hrs应用中配置URL的文件包含到项目的URL配置中,并映射到`hrs/`路径下。
|
|
||||||
|
|
||||||
5. 重新运行项目,并打开浏览器中访问<http://localhost:8000/hrs>。
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
(venv)$ python manage.py runserver
|
|
||||||
```
|
|
||||||
|
|
||||||
6. 修改views.py生成动态内容。
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
(venv)$ vim hrs/views.py
|
|
||||||
```
|
|
||||||
|
|
||||||
```Python
|
|
||||||
from io import StringIO
|
|
||||||
|
|
||||||
from django.http import HttpResponse
|
|
||||||
|
|
||||||
depts_list = [
|
|
||||||
{'no': 10, 'name': '财务部', 'location': '北京'},
|
|
||||||
{'no': 20, 'name': '研发部', 'location': '成都'},
|
|
||||||
{'no': 30, 'name': '销售部', 'location': '上海'},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def index(request):
|
|
||||||
output = StringIO()
|
|
||||||
output.write('<html>\n')
|
|
||||||
output.write('<head>\n')
|
|
||||||
output.write('\t<meta charset="utf-8">\n')
|
|
||||||
output.write('\t<title>首页</title>')
|
|
||||||
output.write('</head>\n')
|
|
||||||
output.write('<body>\n')
|
|
||||||
output.write('\t<h1>部门信息</h1>\n')
|
|
||||||
output.write('\t<hr>\n')
|
|
||||||
output.write('\t<table>\n')
|
|
||||||
output.write('\t\t<tr>\n')
|
|
||||||
output.write('\t\t\t<th width=120>部门编号</th>\n')
|
|
||||||
output.write('\t\t\t<th width=180>部门名称</th>\n')
|
|
||||||
output.write('\t\t\t<th width=180>所在地</th>\n')
|
|
||||||
output.write('\t\t</tr>\n')
|
|
||||||
for dept in depts_list:
|
|
||||||
output.write('\t\t<tr>\n')
|
|
||||||
output.write(f'\t\t\t<td align=center>{dept["no"]}</td>\n')
|
|
||||||
output.write(f'\t\t\t<td align=center>{dept["name"]}</td>\n')
|
|
||||||
output.write(f'\t\t\t<td align=center>{dept["location"]}</td>\n')
|
|
||||||
output.write('\t\t</tr>\n')
|
|
||||||
output.write('\t</table>\n')
|
|
||||||
output.write('</body>\n')
|
|
||||||
output.write('</html>\n')
|
|
||||||
return HttpResponse(output.getvalue())
|
|
||||||
```
|
|
||||||
|
|
||||||
7. 刷新页面查看程序的运行结果。
|
|
||||||
|
|
||||||
![](./res/show-depts.png)
|
|
||||||
|
|
||||||
#### 使用视图模板
|
|
||||||
|
|
||||||
上面通过拼接HTML代码的方式生成动态视图的做法在实际开发中是无能接受的,这一点大家一定能够想到。为了解决这个问题,我们可以提前准备一个模板页,所谓模板页就是一个带占位符的HTML页面,当我们将程序中获得的数据替换掉页面中的占位符时,一个动态页面就产生了。
|
|
||||||
|
|
||||||
我们可以用Django框架中template模块的Template类创建模板对象,通过模板对象的render方法实现对模板的渲染,在Django框架中还有一个名为`render`的便捷函数可以来完成渲染模板的操作。所谓的渲染就是用数据替换掉模板页中的占位符,当然这里的渲染称为后端渲染,即在服务器端完成页面的渲染再输出到浏览器中,这种做法的主要坏处是当并发访问量较大时,服务器会承受较大的负担,所以今天有很多的Web应用都使用了前端渲染,即服务器只提供所需的数据(通常是JSON格式),在浏览器中通过JavaScript获取这些数据并渲染到页面上,这个我们在后面的内容中会讲到。
|
|
||||||
|
|
||||||
1. 先回到manage.py文件所在的目录创建名为templates文件夹。
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
(venv)$ mkdir templates
|
|
||||||
```
|
|
||||||
|
|
||||||
2. 创建模板页index.html。
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
(venv)$ touch templates/index.html
|
|
||||||
(venv)$ vim templates/index.html
|
|
||||||
```
|
|
||||||
```HTML
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>首页</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>部门信息</h1>
|
|
||||||
<hr>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<th>部门编号</th>
|
|
||||||
<th>部门名称</th>
|
|
||||||
<th>所在地</th>
|
|
||||||
</tr>
|
|
||||||
{% for dept in depts_list %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ dept.no }}</td>
|
|
||||||
<td>{{ dept.name }}</td>
|
|
||||||
<td>{{ dept.location }}</td>
|
|
||||||
<tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
在上面的模板页中我们使用了`{{ greeting }}`这样的模板占位符语法,也使用了`{% for %}`这样的模板指令,这些都是Django模板语言(DTL)的一部分。如果对此不熟悉并不要紧,我们会在后续的内容中进一步的讲解,而且我们刚才也说到了,渲染页面还有更好的选择就是使用前端渲染,当然这是后话。
|
|
||||||
|
|
||||||
3. 回到应用目录,修改views.py文件。
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
(venv)$ vim hrs/views.py
|
|
||||||
```
|
|
||||||
|
|
||||||
```Python
|
|
||||||
from django.shortcuts import render
|
|
||||||
|
|
||||||
depts_list = [
|
|
||||||
{'no': 10, 'name': '财务部', 'location': '北京'},
|
|
||||||
{'no': 20, 'name': '研发部', 'location': '成都'},
|
|
||||||
{'no': 30, 'name': '销售部', 'location': '上海'},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def index(request):
|
|
||||||
return render(request, 'index.html', {'depts_list': depts_list})
|
|
||||||
```
|
|
||||||
|
|
||||||
> 说明:Django框架通过shortcuts模块的便捷函数`render`简化了渲染模板的操作,有了这个函数,就不用先创建`Template`对象再去调用`render`方法。。
|
|
||||||
|
|
||||||
到此为止,我们还没有办法让views.py中的`render`函数找到模板文件index.html,为此我们需要修改settings.py文件,配置模板文件所在的路径。
|
|
||||||
|
|
||||||
4. 切换到项目目录修改settings.py文件。
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
(venv)$ vim oa/settings.py
|
|
||||||
```
|
|
||||||
|
|
||||||
```Python
|
|
||||||
# 此处省略上面的内容
|
|
||||||
|
|
||||||
TEMPLATES = [
|
|
||||||
{
|
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
||||||
'DIRS': [os.path.join(BASE_DIR, 'templates')],
|
|
||||||
'APP_DIRS': True,
|
|
||||||
'OPTIONS': {
|
|
||||||
'context_processors': [
|
|
||||||
'django.template.context_processors.debug',
|
|
||||||
'django.template.context_processors.request',
|
|
||||||
'django.contrib.auth.context_processors.auth',
|
|
||||||
'django.contrib.messages.context_processors.messages',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
# 此处省略下面的内容
|
|
||||||
```
|
|
||||||
|
|
||||||
5. 重新运行项目或直接刷新页面查看结果。
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
(venv)$ python manage.py runserver
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### 总结
|
|
||||||
|
|
||||||
至此,我们已经利用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)也是非常适合初学者的入门级读物。
|
|
||||||
|
|
|
@ -1,20 +1,76 @@
|
||||||
## 深入模型
|
## 深入模型
|
||||||
|
|
||||||
在上一个章节中,我们提到了Django是基于MVC架构的Web框架,MVC架构追求的是“模型”和“视图”的解耦合。所谓“模型”说得更直白一些就是数据(的表示),所以通常也被称作“数据模型”。在实际的项目中,数据模型通常通过数据库实现持久化操作,而关系型数据库在过去和当下都是持久化的首选方案,下面我们以MySQL为例来说明如何使用关系型数据库来实现持久化操作。
|
在上一个章节中,我们提到了Django是基于MVC架构的Web框架,MVC架构追求的是“模型”和“视图”的解耦合。所谓“模型”说得更直白一些就是数据(的表示),所以通常也被称作“数据模型”。在实际的项目中,数据模型通常通过数据库实现持久化操作,而关系型数据库在过去和当下都是持久化的首选方案,下面我们通过完成一个投票项目来讲解和模型相关的知识点。投票项目的首页会展示某在线教育平台所有的学科;点击学科可以查看到该学科的老师及其信息;用户登录后在查看老师的页面为老师投票,可以投赞成票和反对票;未登录的用户可以通过登录页进行登录;尚未注册的用户可以通过注册页输入个人信息进行注册。在这个项目中,我们使用MySQL数据库来实现数据持久化操作。
|
||||||
|
|
||||||
|
### 创建项目和应用
|
||||||
|
|
||||||
|
我们首先创建Django项目`vote`并为其添加虚拟环境和依赖项。接下来,在项目下创建名为`polls`的应用和保存模板页的文件夹`tempaltes`,项目文件夹的结构如下所示。
|
||||||
|
|
||||||
|
![](res/pycharm-vote-project.png)
|
||||||
|
|
||||||
|
根据上面描述的项目需求,我们准备了四个静态页面,分别是展示学科的页面`subjects.html`,显示学科老师的页面`teachers.html`,登录页面`login.html`,注册页面`register.html`,稍后我们会将静态页修改为Django项目所需的模板页。
|
||||||
|
|
||||||
### 配置关系型数据库MySQL
|
### 配置关系型数据库MySQL
|
||||||
|
|
||||||
我们继续来完善上一个章节中的OA项目,首先从配置项目使用的数据库开始。
|
1. 在MySQL中创建数据库,创建用户,授权用户访问该数据库。
|
||||||
|
|
||||||
1. 修改项目的settings.py文件,首先将我们之前创建的应用hrs添加已安装的项目中,然后配置MySQL作为持久化方案。
|
```SQL
|
||||||
|
create database vote default charset utf8;
|
||||||
```Shell
|
create user 'hellokitty'@'%' identified by 'Hellokitty.618';
|
||||||
(venv)$ vim oa/settings.py
|
grant all privileges on vote.* to 'hellokitty'@'%';
|
||||||
|
flush privileges;
|
||||||
```
|
```
|
||||||
|
|
||||||
```Python
|
2. 在MySQL中创建保存学科和老师信息的二维表(保存用户信息的表稍后处理)。
|
||||||
# 此处省略上面的代码
|
|
||||||
|
|
||||||
|
```SQL
|
||||||
|
use vote;
|
||||||
|
|
||||||
|
-- 创建学科表
|
||||||
|
create table `tb_subject`
|
||||||
|
(
|
||||||
|
`no` integer auto_increment comment '学科编号',
|
||||||
|
`name` varchar(50) not null comment '学科名称',
|
||||||
|
`intro` varchar(1000) not null default '' comment '学科介绍',
|
||||||
|
`is_hot` boolean not null default 0 comment '是不是热门学科',
|
||||||
|
primary key (`no`)
|
||||||
|
);
|
||||||
|
-- 创建老师表
|
||||||
|
create table `tb_teacher`
|
||||||
|
(
|
||||||
|
`no` integer auto_increment comment '老师编号',
|
||||||
|
`name` varchar(20) not null comment '老师姓名',
|
||||||
|
`sex` boolean not null default 1 comment '老师性别',
|
||||||
|
`birth` date not null comment '出生日期',
|
||||||
|
`intro` varchar(1000) not null default '' comment '老师介绍',
|
||||||
|
`photo` varchar(255) not null default '' comment '老师照片',
|
||||||
|
`gcount` integer not null default 0 comment '好评数',
|
||||||
|
`bcount` integer not null default 0 comment '差评数',
|
||||||
|
`sno` integer not null comment '所属学科',
|
||||||
|
primary key (`no`),
|
||||||
|
foreign key (`sno`) references `tb_subject` (`no`)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 在虚拟环境中安装连接MySQL数据库所需的依赖项。
|
||||||
|
|
||||||
|
```Bash
|
||||||
|
pip install mysqlclient
|
||||||
|
```
|
||||||
|
|
||||||
|
> **说明**:如果因为某些原因无法安装`mysqlclient`三方库,可以使用它的替代品`pymysql`,`pymysql`是用纯Python开发的连接MySQL的Python库,安装更容易成功,但是需要在Django项目文件夹的`__init__.py`中添加如下所示的代码。
|
||||||
|
>
|
||||||
|
> ```Python
|
||||||
|
> import pymysql
|
||||||
|
>
|
||||||
|
> pymysql.install_as_MySQLdb()
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> 如果使用Django 2.2及以上版本,还会遇到PyMySQL跟Django框架的兼容性问题,兼容性问题会导致项目无法运行,需要按照GitHub上PyMySQL仓库[Issues](https://github.com/PyMySQL/PyMySQL/issues/790)中提供的方法进行处理。总体来说,使用`pymysql`会比较麻烦,强烈建议大家首选安装`mysqlclient`。
|
||||||
|
|
||||||
|
4. 修改项目的settings.py文件,首先将我们创建的应用`polls`添加已安装的项目(`INSTALLED_APPS`)中,然后配置MySQL作为持久化方案。
|
||||||
|
|
||||||
|
```Python
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
|
@ -22,55 +78,197 @@
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'hrs',
|
'polls',
|
||||||
]
|
]
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
|
# 数据库引擎配置
|
||||||
'ENGINE': 'django.db.backends.mysql',
|
'ENGINE': 'django.db.backends.mysql',
|
||||||
'NAME': 'oa',
|
# 数据库的名字
|
||||||
'HOST': '1.2.3.4',
|
'NAME': 'vote',
|
||||||
|
# 数据库服务器的IP地址(本机可以写localhost或127.0.0.1)
|
||||||
|
'HOST': 'localhost',
|
||||||
|
# 启动MySQL服务的端口号
|
||||||
'PORT': 3306,
|
'PORT': 3306,
|
||||||
'USER': 'yourname',
|
# 数据库用户名和口令
|
||||||
'PASSWORD': 'yourpass',
|
'USER': 'hellokitty',
|
||||||
|
'PASSWORD': 'Hellokitty.618',
|
||||||
|
# 数据库使用的字符集
|
||||||
|
'CHARSET': 'utf8',
|
||||||
|
# 数据库时间日期的时区设定
|
||||||
|
'TIME_ZONE': 'Asia/Chongqing',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# 此处省略下面的代码
|
|
||||||
```
|
```
|
||||||
|
|
||||||
在配置ENGINE属性时,常用的可选值包括:
|
在配置ENGINE属性时,常用的可选值包括:
|
||||||
|
|
||||||
- `'django.db.backends.sqlite3'`:SQLite嵌入式数据库。
|
- `'django.db.backends.sqlite3'`:SQLite嵌入式数据库。
|
||||||
- `'django.db.backends.postgresql'`:BSD许可证下发行的开源关系型数据库产品。
|
- `'django.db.backends.postgresql'`:BSD许可证下发行的开源关系型数据库产品。
|
||||||
- `'django.db.backends.mysql'`:转手多次目前属于甲骨文公司的经济高效的数据库产品。
|
- `'django.db.backends.mysql'`:甲骨文公司经济高效的数据库产品。
|
||||||
- `'django.db.backends.oracle'`:甲骨文公司的关系型数据库旗舰产品。
|
- `'django.db.backends.oracle'`:甲骨文公司关系型数据库旗舰产品。
|
||||||
|
|
||||||
其他的配置可以参考官方文档中[数据库配置](https://docs.djangoproject.com/zh-hans/2.0/ref/databases/#third-party-notes)的部分。
|
其他的配置可以参考官方文档中[数据库配置](https://docs.djangoproject.com/zh-hans/2.0/ref/databases/#third-party-notes)的部分。
|
||||||
|
|
||||||
NAME属性代表数据库的名称,如果使用SQLite它对应着一个文件,在这种情况下NAME的属性值应该是一个绝对路径;使用其他关系型数据库,则要配置对应的HOST(主机)、PORT(端口)、USER(用户名)、PASSWORD(口令)等属性。
|
5. Django框架提供了ORM来解决数据持久化问题,ORM翻译成中文叫“对象关系映射”。因为Python是面向对象的编程语言,我们在Python程序中使用对象模型来保存数据,而关系型数据库使用关系模型,用二维表来保存数据,这两种模型并不匹配。使用ORM是为了实现对象模型到关系模型的**双向转换**,这样就不用在Python代码中书写SQL语句和游标操作,因为这些都会由ORM自动完成。利用Django的ORM,我们可以直接将刚才创建的学科表和老师表变成Django中的模型类。
|
||||||
|
|
||||||
2. 安装Python操作MySQL的依赖库,Python 3中通常使用PyMySQL,Python 2中通常用MySQLdb。
|
```Bash
|
||||||
|
python manage.py inspectdb > polls/models.py
|
||||||
```Shell
|
|
||||||
(venv)$ pip install pymysql
|
|
||||||
```
|
```
|
||||||
|
|
||||||
如果使用Python 3需要修改**项目目录**下的`__init__.py`文件并加入如下所示的代码,这段代码的作用是将PyMySQL视为MySQLdb来使用,从而避免Django找不到连接MySQL的客户端工具而询问你:“Did you install mysqlclient? ”(你安装了mysqlclient吗?)。
|
我们可以对自动生成的模型类稍作调整,代码如下所示。
|
||||||
|
|
||||||
```Python
|
```Python
|
||||||
import pymysql
|
from django.db import models
|
||||||
|
|
||||||
pymysql.install_as_MySQLdb()
|
|
||||||
|
class Subject(models.Model):
|
||||||
|
no = models.AutoField(primary_key=True, verbose_name='编号')
|
||||||
|
name = models.CharField(max_length=50, verbose_name='名称')
|
||||||
|
intro = models.CharField(max_length=1000, verbose_name='介绍')
|
||||||
|
is_hot = models.BooleanField(verbose_name='是否热门')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
managed = False
|
||||||
|
db_table = 'tb_subject'
|
||||||
|
verbose_name = '学科'
|
||||||
|
verbose_name_plural = '学科'
|
||||||
|
|
||||||
|
|
||||||
|
class Teacher(models.Model):
|
||||||
|
no = models.AutoField(primary_key=True, verbose_name='编号')
|
||||||
|
name = models.CharField(max_length=20, verbose_name='姓名')
|
||||||
|
sex = models.BooleanField(default=True, verbose_name='性别')
|
||||||
|
birth = models.DateField(verbose_name='出生日期')
|
||||||
|
intro = models.CharField(max_length=1000, verbose_name='个人介绍')
|
||||||
|
photo = models.ImageField(max_length=255, verbose_name='照片')
|
||||||
|
good_count = models.IntegerField(default=0, db_column='gcount', verbose_name='好评数')
|
||||||
|
bad_count = models.IntegerField(default=0, db_column='bcount', verbose_name='差评数')
|
||||||
|
subject = models.ForeignKey(Subject, models.DO_NOTHING, db_column='sno')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
managed = False
|
||||||
|
db_table = 'tb_teacher'
|
||||||
```
|
```
|
||||||
|
|
||||||
3. 如果之前没有为应用程序创建数据库,那么现在是时候创建名为oa的数据库了。在MySQL中创建数据库的SQL语句如下所示:
|
> **说明**:模型类都直接或间接继承自`Model`类,模型类跟关系型数据库的二维表对应,模型对象跟表中的记录对应,模型对象的属性跟表中的字段对应。如果对上面模型类的属性定义不是特别理解,可以看看本文后面提供的“模型定义参考”部分的内容。
|
||||||
|
|
||||||
```SQL
|
### 使用ORM完成模型的CRUD操作
|
||||||
create database oa default charset utf8;
|
|
||||||
|
有了Django框架的ORM,我们可以直接使用面向对象的方式来实现对数据的CRUD(增删改查)操作。我们可以在PyCharm的终端中输入下面的命令进入到Django项目的交互式环境,然后尝试对模型的操作。
|
||||||
|
|
||||||
|
```Bash
|
||||||
|
python manage.py shell
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Django框架本身有自带的数据模型,我们稍后会用到这些模型,为此我们先做一次迁移操作。所谓迁移,就是根据模型自动生成关系数据库中的二维表,命令如下所示:
|
#### 新增
|
||||||
|
|
||||||
|
```Python
|
||||||
|
from polls.models import Subject
|
||||||
|
|
||||||
|
subject1 = Subject(name='Python全栈开发', intro='当下最热门的学科', is_hot=True)
|
||||||
|
subject1.save()
|
||||||
|
subject2 = Subject(name='全栈软件测试', intro='学习自动化测试的学科', is_hot=False)
|
||||||
|
subject2.save()
|
||||||
|
subject3 = Subject(name='JavaEE分布式开发', intro='基于Java语言的服务器应用开发', is_hot=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 删除
|
||||||
|
|
||||||
|
```Python
|
||||||
|
subject = Subject.objects.get(no=2)
|
||||||
|
subject.delete()
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 更新
|
||||||
|
|
||||||
|
```Shell
|
||||||
|
subject = Subject.objects.get(no=1)
|
||||||
|
subject.name = 'Python全栈+人工智能'
|
||||||
|
subject.save()
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 查询
|
||||||
|
|
||||||
|
1. 查询所有对象。
|
||||||
|
|
||||||
|
```Shell
|
||||||
|
Subjects.objects.all()
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 过滤数据。
|
||||||
|
|
||||||
|
```Shell
|
||||||
|
# 查询名称为“Python全栈+人工智能”的学科
|
||||||
|
Subject.objects.filter(name='Python全栈+人工智能')
|
||||||
|
|
||||||
|
# 查询名称包含“全栈”的学科(模糊查询)
|
||||||
|
Subject.objects.filter(name__contains='全栈')
|
||||||
|
|
||||||
|
# 查询所有热门学科
|
||||||
|
Subject.objects.filter(is_hot=True)
|
||||||
|
|
||||||
|
# 查询编号大于3小于10的学科
|
||||||
|
Subject.objects.filter(no__gt=3).filter(no__lt=10)
|
||||||
|
Subject.objects.filter(no__gt=3, no__lt=10)
|
||||||
|
|
||||||
|
# 查询编号在3到7之间的学科
|
||||||
|
Subject.objects.filter(no__ge=3, no__le=7)
|
||||||
|
Subject.objects.filter(no__range=(3, 7))
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 查询单个对象。
|
||||||
|
|
||||||
|
```Shell
|
||||||
|
# 查询主键为1的学科
|
||||||
|
Subject.objects.get(pk=1)
|
||||||
|
Subject.objects.get(no=1)
|
||||||
|
Subject.objects.filter(no=1).first()
|
||||||
|
Subject.objects.filter(no=1).last()
|
||||||
|
```
|
||||||
|
|
||||||
|
4. 排序。
|
||||||
|
|
||||||
|
```Shell
|
||||||
|
# 查询所有学科按编号升序排列
|
||||||
|
Subject.objects.order_by('no')
|
||||||
|
# 查询所有部门按部门编号降序排列
|
||||||
|
Subject.objects.order_by('-no')
|
||||||
|
```
|
||||||
|
|
||||||
|
5. 切片(分页查询)。
|
||||||
|
|
||||||
|
```Shell
|
||||||
|
# 按编号从小到大查询前3个学科
|
||||||
|
Subject.objects.order_by('no')[:3]
|
||||||
|
```
|
||||||
|
|
||||||
|
6. 计数。
|
||||||
|
|
||||||
|
```Python
|
||||||
|
# 查询一共有多少个学科
|
||||||
|
Subject.objects.count()
|
||||||
|
```
|
||||||
|
|
||||||
|
7. 高级查询。
|
||||||
|
|
||||||
|
```Shell
|
||||||
|
# 查询编号为1的学科的老师
|
||||||
|
Teacher.objects.filter(subject__no=1)
|
||||||
|
Subject.objects.get(pk=1).teacher_set.all()
|
||||||
|
|
||||||
|
# 查询学科名称有“全栈”二字的学科的老师
|
||||||
|
Teacher.objects.filter(subject__name__contains='全栈')
|
||||||
|
```
|
||||||
|
|
||||||
|
> **说明1**:由于老师与学科之间存在多对一外键关联,所以能通过学科反向查询到该学科的老师(从一对多关系中“一”的一方查询“多”的一方),反向查询属性默认的名字是`类名小写_set`(如上面例子中的`teacher_set`),当然也可以在创建模型时通过`ForeingKey`的`related_name`属性指定反向查询属性的名字。如果不希望执行反向查询可以将`related_name`属性设置为`'+'`或者以`'+'`开头的字符串。
|
||||||
|
|
||||||
|
> **说明2**:ORM查询多个对象时会返回QuerySet对象,QuerySet使用了惰性查询,即在创建QuerySet对象的过程中不涉及任何数据库活动,等真正用到对象时(对QuerySet求值)才向数据库发送SQL语句并获取对应的结果,这一点在实际开发中需要引起注意!
|
||||||
|
|
||||||
|
> **说明3**:如果希望更新多条数据,不用先逐一获取模型对象再修改对象属性,可以直接使用QuerySet对象的`update()`方法一次性更新多条数据。
|
||||||
|
|
||||||
|
1. Django框架本身有自带的数据模型,我们稍后会用到这些模型,为此我们先做一次迁移操作。所谓迁移,就是根据模型自动生成关系数据库中的二维表,命令如下所示:
|
||||||
|
|
||||||
```Shell
|
```Shell
|
||||||
(venv)$ python manage.py migrate
|
(venv)$ python manage.py migrate
|
||||||
|
@ -93,309 +291,85 @@
|
||||||
Applying sessions.0001_initial... OK
|
Applying sessions.0001_initial... OK
|
||||||
```
|
```
|
||||||
|
|
||||||
5. 接下来,我们为自己的应用创建数据模型。如果要在hrs应用中实现对部门和员工的管理,我们可以先创建部门和员工数据模型,代码如下所示。
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
(venv)$ vim hrs/models.py
|
|
||||||
```
|
|
||||||
|
|
||||||
```Python
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
class Dept(models.Model):
|
|
||||||
"""部门类"""
|
|
||||||
|
|
||||||
no = models.IntegerField(primary_key=True, db_column='dno', verbose_name='部门编号')
|
|
||||||
name = models.CharField(max_length=20, db_column='dname', verbose_name='部门名称')
|
|
||||||
location = models.CharField(max_length=10, db_column='dloc', verbose_name='部门所在地')
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
db_table = 'tb_dept'
|
|
||||||
|
|
||||||
|
|
||||||
class Emp(models.Model):
|
|
||||||
"""员工类"""
|
|
||||||
|
|
||||||
no = models.IntegerField(primary_key=True, db_column='eno', verbose_name='员工编号')
|
|
||||||
name = models.CharField(max_length=20, db_column='ename', 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='主管')
|
|
||||||
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='补贴')
|
|
||||||
# 多对一外键关联(参照部门模型)
|
|
||||||
dept = models.ForeignKey(Dept, db_column='dno', on_delete=models.PROTECT, verbose_name='所在部门')
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
db_table = 'tb_emp'
|
|
||||||
```
|
|
||||||
> 说明:上面定义模型时使用了字段类及其属性,其中IntegerField对应数据库中的integer类型,CharField对应数据库的varchar类型,DecimalField对应数据库的decimal类型,ForeignKey用来建立多对一外键关联。字段属性primary_key用于设置主键,max_length用来设置字段的最大长度,db_column用来设置数据库中与字段对应的列,verbose_name则设置了Django后台管理系统中该字段显示的名称。如果对这些东西感到很困惑也不要紧,文末提供了字段类、字段属性、元数据选项等设置的相关说明,不清楚的读者可以稍后查看对应的参考指南。
|
|
||||||
|
|
||||||
6. 再次执行迁移操作,先通过模型生成迁移文件,再执行迁移创建二维表。
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
(venv)$ python manage.py makemigrations hrs
|
|
||||||
Migrations for 'hrs':
|
|
||||||
hrs/migrations/0001_initial.py
|
|
||||||
- Create model Dept
|
|
||||||
- Create model Emp
|
|
||||||
(venv)$ python manage.py migrate
|
|
||||||
Operations to perform:
|
|
||||||
Apply all migrations: admin, auth, contenttypes, hrs, sessions
|
|
||||||
Running migrations:
|
|
||||||
Applying hrs.0001_initial... OK
|
|
||||||
```
|
|
||||||
|
|
||||||
执行完数据模型迁移操作之后,可以在通过图形化的MySQL客户端工具查看到E-R图(实体关系图)。
|
|
||||||
|
|
||||||
![](./res/er-graph.png)
|
|
||||||
|
|
||||||
### 利用Django后台管理模型
|
### 利用Django后台管理模型
|
||||||
|
|
||||||
Django框架有自带的后台管理系统来实现对模型的管理。虽然实际应用中,这个后台可能并不能满足我们的需求,但是在学习Django框架时,我们暂时可以利用Django自带的后台管理系统来管理我们的模型,同时也可以了解一个项目的后台管理系统到底需要哪些功能。
|
在创建好模型类之后,可以通过Django框架自带的后台管理应用(`admin`应用)实现对模型的管理。虽然实际应用中,这个后台可能并不能满足我们的需求,但是在学习Django框架时,我们可以利用`admin`应用来管理我们的模型,同时也通过它来了解一个项目的后台管理系统需要哪些功能。使用Django自带的`admin`应用步骤如下所示。
|
||||||
|
|
||||||
1. 创建超级管理员账号。
|
1. 将`admin`应用所需的表迁移到数据库中。`admin`应用本身也需要数据库的支持,而且在`admin`应用中已经定义好了相关的数据模型类,我们只需要通过模型迁移操作就能自动在数据库中生成所需的二维表。
|
||||||
|
|
||||||
```Shell
|
```Bash
|
||||||
(venv)$ python manage.py createsuperuser
|
python manage.py migrate
|
||||||
Username (leave blank to use 'hao'): jackfrued
|
|
||||||
Email address: jackfrued@126.com
|
|
||||||
Password:
|
|
||||||
Password (again):
|
|
||||||
Superuser created successfully.
|
|
||||||
```
|
```
|
||||||
|
|
||||||
2. 启动Web服务器,登录后台管理系统。
|
2. 创建访问`admin`应用的超级用户账号,这里需要输入用户名、邮箱和口令。
|
||||||
|
|
||||||
```Shell
|
```Shell
|
||||||
(venv)$ python manage.py runserver
|
python manage.py createsuperuser
|
||||||
```
|
```
|
||||||
|
|
||||||
访问<http://127.0.0.1:8000/admin>,会来到如下图所示的登录界面。
|
> **说明**:输入口令时没有回显也不能退格,需要一气呵成完成输入。
|
||||||
|
|
||||||
![](./res/admin-login.png)
|
3. 运行项目,在浏览器中访问`http://127.0.0.1:8000/admin`,输入刚才创建的超级用户账号和密码进行登录。
|
||||||
|
|
||||||
|
![](./res/django-admin-login.png)
|
||||||
|
|
||||||
登录后进入管理员操作平台。
|
登录后进入管理员操作平台。
|
||||||
|
|
||||||
![](./res/admin-welcome.png)
|
![](./res/django-admin-apps.png)
|
||||||
|
|
||||||
至此我们还没有看到之前创建的模型类,需要在应用的admin.py文件中模型进行注册。
|
注意,我们暂时还没能在`admin`应用中看到之前创建的模型类,为此需要在`polls`应用的`admin.py`文件中对需要管理的模型进行注册。
|
||||||
|
|
||||||
3. 注册模型类。
|
4. 注册模型类。
|
||||||
|
|
||||||
```Shell
|
|
||||||
(venv)$ vim hrs/admin.py
|
|
||||||
```
|
|
||||||
|
|
||||||
```Python
|
```Python
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from hrs.models import Emp, Dept
|
```
|
||||||
|
|
||||||
admin.site.register(Dept)
|
from polls.models import Subject, Teacher
|
||||||
admin.site.register(Emp)
|
|
||||||
|
admin.site.register(Subject)
|
||||||
|
admin.site.register(Teacher)
|
||||||
```
|
```
|
||||||
|
|
||||||
注册模型类后,就可以在后台管理系统中看到它们。
|
注册模型类后,就可以在后台管理系统中看到它们。
|
||||||
|
|
||||||
![](./res/admin-model.png)
|
![](./res/django-admin-models.png)
|
||||||
|
|
||||||
4. 对模型进行CRUD操作。
|
5. 对模型进行CRUD操作。
|
||||||
|
|
||||||
可以在管理员平台对模型进行C(新增)、R(查看)、U(更新)、D(删除)操作,如下图所示。
|
可以在管理员平台对模型进行C(新增)、R(查看)、U(更新)、D(删除)操作,如下图所示。
|
||||||
|
|
||||||
- 添加新的部门。
|
- 添加学科。
|
||||||
|
|
||||||
![](./res/admin-model-create.png)
|
![](./res/django-admin-create.png)
|
||||||
|
|
||||||
- 查看所有部门。
|
- 查看所有学科。
|
||||||
|
|
||||||
![](./res/admin-model-read.png)
|
![](./res/django-admin-read.png)
|
||||||
|
|
||||||
- 更新和删除部门。
|
- 删除和更新学科。
|
||||||
|
|
||||||
![](./res/admin-model-delete-and-update.png)
|
![](./res/django-admin-delete-update.png)
|
||||||
|
|
||||||
5. 注册模型管理类。
|
6. 注册模型管理类。
|
||||||
|
|
||||||
可能大家已经注意到了,刚才在后台查看部门信息的时候,显示的部门信息并不直观,为此我们再修改admin.py文件,通过注册模型管理类,可以在后台管理系统中更好的管理模型。
|
可能大家已经注意到了,刚才在后台查看部门信息的时候,显示的部门信息并不直观,为此我们再修改`admin.py`文件,通过注册模型管理类,可以在后台管理系统中更好的管理模型。
|
||||||
|
|
||||||
```Python
|
```Python
|
||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
from hrs.models import Emp, Dept
|
|
||||||
|
|
||||||
|
|
||||||
class DeptAdmin(admin.ModelAdmin):
|
|
||||||
|
|
||||||
list_display = ('no', 'name', 'location')
|
|
||||||
ordering = ('no', )
|
|
||||||
|
|
||||||
|
|
||||||
class EmpAdmin(admin.ModelAdmin):
|
|
||||||
|
|
||||||
list_display = ('no', 'name', 'job', 'mgr', 'sal', 'comm', 'dept')
|
|
||||||
search_fields = ('name', 'job')
|
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Dept, DeptAdmin)
|
|
||||||
admin.site.register(Emp, EmpAdmin)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
![](./res/admin-model-depts.png)
|
![](./res/django-admin-models-detail.png)
|
||||||
|
|
||||||
![](./res/admin-model-emps.png)
|
为了更好的查看模型数据,可以为`Subject`和`Teacher`两个模型类添加`__str__`魔法方法。修改代码后的效果如下图所示。
|
||||||
|
|
||||||
为了更好的查看模型数据,可以为Dept和Emp两个模型类添加`__str__`魔法方法。
|
![](./res/django-amdin-models-detail-modified.png)
|
||||||
|
|
||||||
```Python
|
### 补充内容
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
#### Django模型最佳实践
|
||||||
class Dept(models.Model):
|
|
||||||
"""部门类"""
|
|
||||||
|
|
||||||
# 此处省略上面的代码
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
# 此处省略下面的代码
|
|
||||||
|
|
||||||
|
|
||||||
class Emp(models.Model):
|
|
||||||
"""员工类"""
|
|
||||||
|
|
||||||
# 此处省略上面的代码
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
# 此处省略下面的代码
|
|
||||||
```
|
|
||||||
|
|
||||||
修改代码后刷新查看Emp模型的页面,效果如下图所示。
|
|
||||||
|
|
||||||
![](./res/admin-model-emps-modified.png)
|
|
||||||
|
|
||||||
### 使用ORM完成模型的CRUD操作
|
|
||||||
|
|
||||||
在了解了Django提供的模型管理平台之后,我们来看看如何从代码层面完成对模型的CRUD(Create / Read / Update / Delete)操作。我们可以通过manage.py开启Shell交互式环境,然后使用Django内置的ORM框架对模型进行CRUD操作。
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
(venv)$ python manage.py shell
|
|
||||||
Python 3.6.4 (v3.6.4:d48ecebad5, Dec 18 2017, 21:07:28)
|
|
||||||
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
|
|
||||||
Type "help", "copyright", "credits" or "license" for more information.
|
|
||||||
(InteractiveConsole)
|
|
||||||
>>>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 新增
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
>>> from hrs.models import Dept, Emp
|
|
||||||
>>>
|
|
||||||
>>> dept = Dept(40, '研发2部', '深圳')
|
|
||||||
>>> dept.save()
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 更新
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
>>> dept.name = '研发3部'
|
|
||||||
>>> dept.save()
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 查询
|
|
||||||
|
|
||||||
1. 查询所有对象。
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
>>> Dept.objects.all()
|
|
||||||
<QuerySet [<Dept: 研发1部>, <Dept: 销售1部>, <Dept: 运维1部>, <Dept: 研发3部>]>
|
|
||||||
```
|
|
||||||
|
|
||||||
2. 过滤数据。
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
>>> Dept.objects.filter(name='研发3部') # 查询部门名称为“研发3部”的部门
|
|
||||||
<QuerySet [<Dept: 研发3部>]>
|
|
||||||
>>>
|
|
||||||
>>> Dept.objects.filter(name__contains='研发') # 查询部门名称包含“研发”的部门(模糊查询)
|
|
||||||
<QuerySet [<Dept: 研发1部>, <Dept: 研发3部>]>
|
|
||||||
>>>
|
|
||||||
>>> Dept.objects.filter(no__gt=10).filter(no__lt=40) # 查询部门编号大于10小于40的部门
|
|
||||||
<QuerySet [<Dept: 销售1部>, <Dept: 运维1部>]>
|
|
||||||
>>>
|
|
||||||
>>> Dept.objects.filter(no__range=(10, 30)) # 查询部门编号在10到30之间的部门
|
|
||||||
<QuerySet [<Dept: 研发1部>, <Dept: 销售1部>, <Dept: 运维1部>]>
|
|
||||||
```
|
|
||||||
|
|
||||||
3. 查询单个对象。
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
>>> Dept.objects.get(pk=10)
|
|
||||||
<Dept: 研发1部>
|
|
||||||
>>>
|
|
||||||
>>> Dept.objects.get(no=20)
|
|
||||||
<Dept: 销售1部>
|
|
||||||
>>>
|
|
||||||
>>> Dept.objects.get(no__exact=30)
|
|
||||||
<Dept: 运维1部>
|
|
||||||
>>>
|
|
||||||
>>> Dept.objects.filter(no=10).first()
|
|
||||||
<Dept: 研发1部>
|
|
||||||
```
|
|
||||||
|
|
||||||
4. 排序数据。
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
>>> Dept.objects.order_by('no') # 查询所有部门按部门编号升序排列
|
|
||||||
<QuerySet [<Dept: 研发1部>, <Dept: 销售1部>, <Dept: 运维1部>, <Dept: 研发3部>]>
|
|
||||||
>>>
|
|
||||||
>>> Dept.objects.order_by('-no') # 查询所有部门按部门编号降序排列
|
|
||||||
<QuerySet [<Dept: 研发3部>, <Dept: 运维1部>, <Dept: 销售1部>, <Dept: 研发1部>]>
|
|
||||||
```
|
|
||||||
|
|
||||||
5. 数据切片(分页查询)。
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
>>> Dept.objects.order_by('no')[0:2] # 按部门编号排序查询1~2部门
|
|
||||||
<QuerySet [<Dept: 研发1部>, <Dept: 销售1部>]>
|
|
||||||
>>>
|
|
||||||
>>> Dept.objects.order_by('no')[2:4] # 按部门编号排序查询3~4部门
|
|
||||||
<QuerySet [<Dept: 运维1部>, <Dept: 研发3部>]>
|
|
||||||
```
|
|
||||||
|
|
||||||
6. 高级查询。
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
>>> Emp.objects.filter(dept__no=10) # 根据部门编号查询该部门的员工
|
|
||||||
<QuerySet [<Emp: 乔峰>, <Emp: 张无忌>, <Emp: 张三丰>]>
|
|
||||||
>>>
|
|
||||||
>>> Emp.objects.filter(dept__name__contains='销售') # 查询名字包含“销售”的部门的员工
|
|
||||||
<QuerySet [<Emp: 黄蓉>]>
|
|
||||||
>>>
|
|
||||||
>>> Dept.objects.get(pk=10).emp_set.all() # 通过部门反查部门所有的员工
|
|
||||||
<QuerySet [<Emp: 乔峰>, <Emp: 张无忌>, <Emp: 张三丰>]>
|
|
||||||
```
|
|
||||||
|
|
||||||
> 说明1:由于员工与部门之间存在多对一外键关联,所以也能通过部门反向查询该部门的员工(从一对多关系中“一”的一方查询“多”的一方),反向查询属性默认的名字是`类名小写_set`(如上面例子中的`emp_set`),当然也可以在创建模型时通过`ForeingKey`的`related_name`属性指定反向查询属性的名字。如果不希望执行反向查询可以将`related_name`属性设置为`'+'`或以`'+'`开头的字符串。
|
|
||||||
|
|
||||||
> 说明2:查询多个对象的时候返回的是QuerySet对象,QuerySet使用了惰性查询,即在创建QuerySet对象的过程中不涉及任何数据库活动,等真正用到对象时(求值QuerySet)才向数据库发送SQL语句并获取对应的结果,这一点在实际开发中需要引起注意!
|
|
||||||
|
|
||||||
> 说明3:可以在QuerySet上使用`update()`方法一次更新多个对象。
|
|
||||||
|
|
||||||
#### 删除
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
>>> Dept.objects.get(pk=40).delete()
|
|
||||||
(1, {'hrs.Dept': 1})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Django模型最佳实践
|
|
||||||
|
|
||||||
1. 正确的为模型和关系字段命名。
|
1. 正确的为模型和关系字段命名。
|
||||||
2. 设置适当的`related_name`属性。
|
2. 设置适当的`related_name`属性。
|
||||||
|
@ -414,9 +388,9 @@ Type "help", "copyright", "credits" or "license" for more information.
|
||||||
|
|
||||||
> 说明:以上内容来自于STEELKIWI网站的[*Best Practice working with Django models in Python*](https://steelkiwi.com/blog/best-practices-working-django-models-python/),有兴趣的小伙伴可以阅读原文。
|
> 说明:以上内容来自于STEELKIWI网站的[*Best Practice working with Django models in Python*](https://steelkiwi.com/blog/best-practices-working-django-models-python/),有兴趣的小伙伴可以阅读原文。
|
||||||
|
|
||||||
### 模型定义参考
|
#### 模型定义参考
|
||||||
|
|
||||||
#### 字段
|
##### 字段
|
||||||
|
|
||||||
对字段名称的限制
|
对字段名称的限制
|
||||||
|
|
||||||
|
@ -426,113 +400,102 @@ Type "help", "copyright", "credits" or "license" for more information.
|
||||||
Django模型字段类
|
Django模型字段类
|
||||||
|
|
||||||
| 字段类 | 说明 |
|
| 字段类 | 说明 |
|
||||||
| --------------------- | ------------------------------------------------------------ |
|
| ----------------------- | ------------------------------------------------------------ |
|
||||||
| AutoField |自增ID字段 |
|
| `AutoField` | 自增ID字段 |
|
||||||
| BigIntegerField |64位有符号整数 |
|
| `BigIntegerField` | 64位有符号整数 |
|
||||||
| BinaryField | 存储二进制数据的字段,对应Python的bytes类型 |
|
| `BinaryField` | 存储二进制数据的字段,对应Python的`bytes`类型 |
|
||||||
| BooleanField | 存储True或False |
|
| `BooleanField` | 存储`True`或`False` |
|
||||||
| CharField | 长度较小的字符串 |
|
| `CharField` | 长度较小的字符串 |
|
||||||
| DateField | 存储日期,有auto_now和auto_now_add属性 |
|
| `DateField` | 存储日期,有`auto_now`和`auto_now_add`属性 |
|
||||||
| DateTimeField | 存储日期和日期,两个附加属性同上 |
|
| `DateTimeField` | 存储日期和日期,两个附加属性同上 |
|
||||||
| DecimalField |存储固定精度小数,有max_digits(有效位数)和decimal_places(小数点后面)两个必要的参数 |
|
| `DecimalField` | 存储固定精度小数,有`max_digits`(有效位数)和`decimal_places`(小数点后面)两个必要的参数 |
|
||||||
| DurationField |存储时间跨度 |
|
| `DurationField` | 存储时间跨度 |
|
||||||
| EmailField | 与CharField相同,可以用EmailValidator验证 |
|
| `EmailField` | 与`CharField`相同,可以用`EmailValidator`验证 |
|
||||||
| FileField | 文件上传字段 |
|
| `FileField` | 文件上传字段 |
|
||||||
| FloatField | 存储浮点数 |
|
| `FloatField` | 存储浮点数 |
|
||||||
| ImageField | 其他同FileFiled,要验证上传的是不是有效图像 |
|
| `ImageField` | 其他同`FileFiled`,要验证上传的是不是有效图像 |
|
||||||
| IntegerField | 存储32位有符号整数。 |
|
| `IntegerField` | 存储32位有符号整数。 |
|
||||||
| GenericIPAddressField | 存储IPv4或IPv6地址 |
|
| `GenericIPAddressField` | 存储IPv4或IPv6地址 |
|
||||||
| NullBooleanField | 存储True、False或null值 |
|
| `NullBooleanField` | 存储`True`、`False`或`null`值 |
|
||||||
| PositiveIntegerField | 存储无符号整数(只能存储正数) |
|
| `PositiveIntegerField` | 存储无符号整数(只能存储正数) |
|
||||||
| SlugField | 存储slug(简短标注) |
|
| `SlugField` | 存储slug(简短标注) |
|
||||||
| SmallIntegerField | 存储16位有符号整数 |
|
| `SmallIntegerField` | 存储16位有符号整数 |
|
||||||
| TextField | 存储数据量较大的文本 |
|
| `TextField` | 存储数据量较大的文本 |
|
||||||
| TimeField | 存储时间 |
|
| `TimeField` | 存储时间 |
|
||||||
| URLField | 存储URL的CharField |
|
| `URLField` | 存储URL的`CharField` |
|
||||||
| UUIDField | 存储全局唯一标识符 |
|
| `UUIDField` | 存储全局唯一标识符 |
|
||||||
|
|
||||||
#### 字段属性
|
##### 字段属性
|
||||||
|
|
||||||
通用字段属性
|
通用字段属性
|
||||||
|
|
||||||
| 选项 | 说明 |
|
| 选项 | 说明 |
|
||||||
| -------------- | ------------------------------------------------------------ |
|
| ---------------- | ------------------------------------------------------------ |
|
||||||
| null | 数据库中对应的字段是否允许为NULL,默认为False |
|
| `null` | 数据库中对应的字段是否允许为`NULL`,默认为`False` |
|
||||||
| blank | 后台模型管理验证数据时,是否允许为NULL,默认为False |
|
| `blank` | 后台模型管理验证数据时,是否允许为`NULL`,默认为`False` |
|
||||||
| choices | 设定字段的选项,各元组中的第一个值是设置在模型上的值,第二值是人类可读的值 |
|
| `choices` | 设定字段的选项,各元组中的第一个值是设置在模型上的值,第二值是人类可读的值 |
|
||||||
| db_column | 字段对应到数据库表中的列名,未指定时直接使用字段的名称 |
|
| `db_column` | 字段对应到数据库表中的列名,未指定时直接使用字段的名称 |
|
||||||
| db_index | 设置为True时将在该字段创建索引 |
|
| `db_index` | 设置为`True`时将在该字段创建索引 |
|
||||||
| db_tablespace | 为有索引的字段设置使用的表空间,默认为DEFAULT_INDEX_TABLESPACE |
|
| `db_tablespace` | 为有索引的字段设置使用的表空间,默认为`DEFAULT_INDEX_TABLESPACE` |
|
||||||
| default | 字段的默认值 |
|
| `default` | 字段的默认值 |
|
||||||
| editable | 字段在后台模型管理或ModelForm中是否显示,默认为True |
|
| `editable` | 字段在后台模型管理或`ModelForm`中是否显示,默认为`True` |
|
||||||
| error_messages | 设定字段抛出异常时的默认消息的字典,其中的键包括null、blank、invalid、invalid_choice、unique和unique_for_date |
|
| `error_messages` | 设定字段抛出异常时的默认消息的字典,其中的键包括`null`、`blank`、`invalid`、`invalid_choice`、`unique`和`unique_for_date` |
|
||||||
| help_text | 表单小组件旁边显示的额外的帮助文本。 |
|
| `help_text` | 表单小组件旁边显示的额外的帮助文本。 |
|
||||||
| primary_key | 将字段指定为模型的主键,未指定时会自动添加AutoField用于主键,只读。 |
|
| `primary_key` | 将字段指定为模型的主键,未指定时会自动添加`AutoField`用于主键,只读。 |
|
||||||
| unique | 设置为True时,表中字段的值必须是唯一的 |
|
| `unique` | 设置为`True`时,表中字段的值必须是唯一的 |
|
||||||
| verbose_name | 字段在后台模型管理显示的名称,未指定时使用字段的名称 |
|
| `verbose_name` | 字段在后台模型管理显示的名称,未指定时使用字段的名称 |
|
||||||
|
|
||||||
ForeignKey属性
|
`ForeignKey`属性
|
||||||
|
|
||||||
1. limit_choices_to:值是一个Q对象或返回一个Q对象,用于限制后台显示哪些对象。
|
1. `limit_choices_to`:值是一个Q对象或返回一个Q对象,用于限制后台显示哪些对象。
|
||||||
2. related_name:用于获取关联对象的关联管理器对象(反向查询),如果不允许反向,该属性应该被设置为`'+'`,或者以`'+'`结尾。
|
2. `related_name`:用于获取关联对象的关联管理器对象(反向查询),如果不允许反向,该属性应该被设置为`'+'`,或者以`'+'`结尾。
|
||||||
3. to_field:指定关联的字段,默认关联对象的主键字段。
|
3. `to_field`:指定关联的字段,默认关联对象的主键字段。
|
||||||
4. db_constraint:是否为外键创建约束,默认值为True。
|
4. `db_constraint`:是否为外键创建约束,默认值为`True`。
|
||||||
5. on_delete:外键关联的对象被删除时对应的动作,可取的值包括django.db.models中定义的:
|
5. `on_delete`:外键关联的对象被删除时对应的动作,可取的值包括`django.db.models`中定义的:
|
||||||
- CASCADE:级联删除。
|
- `CASCADE`:级联删除。
|
||||||
- PROTECT:抛出ProtectedError异常,阻止删除引用的对象。
|
- `PROTECT`:抛出`ProtectedError`异常,阻止删除引用的对象。
|
||||||
- SET_NULL:把外键设置为null,当null属性被设置为True时才能这么做。
|
- `SET_NULL`:把外键设置为`null`,当`null`属性被设置为`True`时才能这么做。
|
||||||
- SET_DEFAULT:把外键设置为默认值,提供了默认值才能这么做。
|
- `SET_DEFAULT`:把外键设置为默认值,提供了默认值才能这么做。
|
||||||
|
|
||||||
ManyToManyField属性
|
`ManyToManyField`属性
|
||||||
|
|
||||||
1. symmetrical:是否建立对称的多对多关系。
|
1. `symmetrical`:是否建立对称的多对多关系。
|
||||||
2. through:指定维持多对多关系的中间表的Django模型。
|
2. `through`:指定维持多对多关系的中间表的Django模型。
|
||||||
3. throughfields:定义了中间模型时可以指定建立多对多关系的字段。
|
3. `throughfields`:定义了中间模型时可以指定建立多对多关系的字段。
|
||||||
4. db_table:指定维持多对多关系的中间表的表名。
|
4. `db_table`:指定维持多对多关系的中间表的表名。
|
||||||
|
|
||||||
#### 模型元数据选项
|
##### 模型元数据选项
|
||||||
|
|
||||||
| 选项 | 说明 |
|
| 选项 | 说明 |
|
||||||
| --------------------- | ------------------------------------------------------------ |
|
| ----------------------- | ------------------------------------------------------------ |
|
||||||
| abstract | 设置为True时模型是抽象父类 |
|
| `abstract` | 设置为True时模型是抽象父类 |
|
||||||
| app_label | 如果定义模型的应用不在INSTALLED_APPS中可以用该属性指定 |
|
| `app_label` | 如果定义模型的应用不在INSTALLED_APPS中可以用该属性指定 |
|
||||||
| db_table | 模型使用的数据表名称 |
|
| `db_table` | 模型使用的数据表名称 |
|
||||||
| db_tablespace | 模型使用的数据表空间 |
|
| `db_tablespace` | 模型使用的数据表空间 |
|
||||||
| default_related_name | 关联对象回指这个模型时默认使用的名称,默认为<model_name>_set |
|
| `default_related_name` | 关联对象回指这个模型时默认使用的名称,默认为<model_name>_set |
|
||||||
| get_latest_by | 模型中可排序字段的名称。 |
|
| `get_latest_by` | 模型中可排序字段的名称。 |
|
||||||
| managed | 设置为True时,Django在迁移中创建数据表并在执行flush管理命令时把表移除 |
|
| `managed` | 设置为True时,Django在迁移中创建数据表并在执行flush管理命令时把表移除 |
|
||||||
| order_with_respect_to | 标记对象为可排序的 |
|
| `order_with_respect_to` | 标记对象为可排序的 |
|
||||||
| ordering | 对象的默认排序 |
|
| `ordering` | 对象的默认排序 |
|
||||||
| permissions | 创建对象时写入权限表的额外权限 |
|
| `permissions` | 创建对象时写入权限表的额外权限 |
|
||||||
| default_permissions | 默认为`('add', 'change', 'delete')` |
|
| `default_permissions` | 默认为`('add', 'change', 'delete')` |
|
||||||
| unique_together | 设定组合在一起时必须独一无二的字段名 |
|
| `unique_together` | 设定组合在一起时必须独一无二的字段名 |
|
||||||
| index_together | 设定一起建立索引的多个字段名 |
|
| `index_together` | 设定一起建立索引的多个字段名 |
|
||||||
| verbose_name | 为对象设定人类可读的名称 |
|
| `verbose_name` | 为对象设定人类可读的名称 |
|
||||||
| verbose_name_plural | 设定对象的复数名称 |
|
| `verbose_name_plural` | 设定对象的复数名称 |
|
||||||
|
|
||||||
### 查询参考
|
#### 查询参考
|
||||||
|
|
||||||
按字段查找可以用的条件:
|
##### 按字段查找可以用的条件
|
||||||
|
|
||||||
1. exact / iexact:精确匹配/忽略大小写的精确匹配查询
|
1. `exact` / `iexact`:精确匹配/忽略大小写的精确匹配查询
|
||||||
2. contains / icontains / startswith / istartswith / endswith / iendswith:基于`like`的模糊查询
|
2. `contains` / `icontains` / `startswith` / `istartswith` / `endswith` / `iendswith`:基于`like`的模糊查询
|
||||||
3. in:集合运算
|
3. `in` :集合运算
|
||||||
4. gt / gte / lt / lte:大于/大于等于/小于/小于等于关系运算
|
4. `gt` / `gte` / `lt` / `lte`:大于/大于等于/小于/小于等于关系运算
|
||||||
5. range:指定范围查询(SQL中的`between…and…`)
|
5. `range`:指定范围查询(SQL中的`between…and…`)
|
||||||
6. year / month / day / week_day / hour / minute / second:查询时间日期
|
6. `year` / `month` / `day` / `week_day` / `hour` / `minute` / `second`:查询时间日期
|
||||||
7. isnull:查询空值(True)或非空值(False)
|
7. `isnull`:查询空值(True)或非空值(False)
|
||||||
8. search:基于全文索引的全文检索
|
8. `search`:基于全文索引的全文检索(一般很少使用)
|
||||||
9. regex / iregex:基于正则表达式的模糊匹配查询
|
9. `regex` / `iregex`:基于正则表达式的模糊匹配查询
|
||||||
|
|
||||||
Q对象(用于执行复杂查询)的使用:
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
>>> from django.db.models import Q
|
|
||||||
>>> Emp.objects.filter(
|
|
||||||
... Q(name__startswith='张'),
|
|
||||||
... Q(sal__gte=5000) | Q(comm__gte=1000)
|
|
||||||
... ) # 查询名字以“张”开头且工资大于等于5000或补贴大于等于1000的员工
|
|
||||||
<QuerySet [<Emp: 张三丰>]>
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ class Teacher(models.Model):
|
||||||
|
|
||||||
> 注意:为了给vote应用生成迁移文件,需要修改Django项目settings.py文件,在INSTALLED_APPS中添加vote应用。
|
> 注意:为了给vote应用生成迁移文件,需要修改Django项目settings.py文件,在INSTALLED_APPS中添加vote应用。
|
||||||
|
|
||||||
完成模型迁移之后,我们可以直接使用Django提供的后台管理来添加学科和老师信息,这需要先注册模型类和模型管理类。
|
完成模型迁移之后,我们可以直接使用Django提供的后台管理来添加学科和老师信息,这需要先注册模型类和模型管理类,可以通过修改``。
|
||||||
|
|
||||||
```SQL
|
```SQL
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
|
@ -0,0 +1,5 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class FirstConfig(AppConfig):
|
||||||
|
name = 'first'
|
|
@ -0,0 +1,3 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
|
@ -0,0 +1,11 @@
|
||||||
|
from random import sample
|
||||||
|
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
|
||||||
|
def show_index(request):
|
||||||
|
fruits = [
|
||||||
|
'Apple', 'Orange', 'Pitaya', 'Durian', 'Waxberry', 'Blueberry',
|
||||||
|
'Grape', 'Peach', 'Pear', 'Banana', 'Watermelon', 'Mango'
|
||||||
|
]
|
||||||
|
return render(request, 'index.html', {'fruits': sample(fruits, 3)})
|
|
@ -1,13 +1,13 @@
|
||||||
"""
|
"""
|
||||||
Django settings for shop project.
|
Django settings for hellodjango project.
|
||||||
|
|
||||||
Generated by 'django-admin startproject' using Django 2.0.5.
|
Generated by 'django-admin startproject' using Django 2.2.13.
|
||||||
|
|
||||||
For more information on this file, see
|
For more information on this file, see
|
||||||
https://docs.djangoproject.com/en/2.0/topics/settings/
|
https://docs.djangoproject.com/en/2.2/topics/settings/
|
||||||
|
|
||||||
For the full list of settings and their values, see
|
For the full list of settings and their values, see
|
||||||
https://docs.djangoproject.com/en/2.0/ref/settings/
|
https://docs.djangoproject.com/en/2.2/ref/settings/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
@ -17,10 +17,10 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
SECRET_KEY = '+gqc54!5+uhvc^o0)fjvihmg&5uu^u+#s5m*fc+e+@bw*(+!w*'
|
SECRET_KEY = 'x)q$(0m0^ttqii@^zn^9bdbh&%l$)wzjm=nv&_y+^y9e!37=-z'
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
@ -37,7 +37,6 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'cart',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
@ -50,13 +49,12 @@ MIDDLEWARE = [
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'shop.urls'
|
ROOT_URLCONF = 'hellodjango.urls'
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
'DIRS': [os.path.join(BASE_DIR, 'templates')]
|
'DIRS': [os.path.join(BASE_DIR, 'templates')],
|
||||||
,
|
|
||||||
'APP_DIRS': True,
|
'APP_DIRS': True,
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
'context_processors': [
|
'context_processors': [
|
||||||
|
@ -69,26 +67,22 @@ TEMPLATES = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
WSGI_APPLICATION = 'shop.wsgi.application'
|
WSGI_APPLICATION = 'hellodjango.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases
|
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.mysql',
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
'NAME': 'Shop',
|
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||||
'HOST': 'localhost',
|
|
||||||
'PORT': 3306,
|
|
||||||
'USER': 'yourname',
|
|
||||||
'PASSWORD': 'yourpass',
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# Password validation
|
# Password validation
|
||||||
# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators
|
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
{
|
{
|
||||||
|
@ -107,11 +101,11 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
|
||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/2.0/topics/i18n/
|
# https://docs.djangoproject.com/en/2.2/topics/i18n/
|
||||||
|
|
||||||
LANGUAGE_CODE = 'en-us'
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
TIME_ZONE = 'Asia/Chongqing'
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
||||||
|
@ -121,6 +115,6 @@ USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/2.0/howto/static-files/
|
# https://docs.djangoproject.com/en/2.2/howto/static-files/
|
||||||
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
|
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
|
@ -1,7 +1,7 @@
|
||||||
"""shop URL Configuration
|
"""hellodjango URL Configuration
|
||||||
|
|
||||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
https://docs.djangoproject.com/en/2.0/topics/http/urls/
|
https://docs.djangoproject.com/en/2.2/topics/http/urls/
|
||||||
Examples:
|
Examples:
|
||||||
Function views
|
Function views
|
||||||
1. Add an import: from my_app import views
|
1. Add an import: from my_app import views
|
||||||
|
@ -16,11 +16,9 @@ Including another URLconf
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from cart import views
|
from first.views import show_index
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.index),
|
|
||||||
path('show_cart', views.show_cart),
|
|
||||||
path('add_to_cart/<int:no>', views.add_to_cart),
|
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
|
path('hello/', show_index),
|
||||||
]
|
]
|
|
@ -1,16 +1,16 @@
|
||||||
"""
|
"""
|
||||||
WSGI config for shop project.
|
WSGI config for hellodjango project.
|
||||||
|
|
||||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
For more information on this file, see
|
For more information on this file, see
|
||||||
https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/
|
https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from django.core.wsgi import get_wsgi_application
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "shop.settings")
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hellodjango.settings')
|
||||||
|
|
||||||
application = get_wsgi_application()
|
application = get_wsgi_application()
|
|
@ -1,9 +1,11 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "shop.settings")
|
def main():
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hellodjango.settings')
|
||||||
try:
|
try:
|
||||||
from django.core.management import execute_from_command_line
|
from django.core.management import execute_from_command_line
|
||||||
except ImportError as exc:
|
except ImportError as exc:
|
||||||
|
@ -13,3 +15,7 @@ if __name__ == "__main__":
|
||||||
"forget to activate a virtual environment?"
|
"forget to activate a virtual environment?"
|
||||||
) from exc
|
) from exc
|
||||||
execute_from_command_line(sys.argv)
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -0,0 +1,21 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>首页</title>
|
||||||
|
<style>
|
||||||
|
#fruits {
|
||||||
|
font-size: 1.25em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>今天推荐的水果是:</h1>
|
||||||
|
<hr>
|
||||||
|
<ul id="fruits">
|
||||||
|
{% for fruit in fruits %}
|
||||||
|
<li>{{ fruit }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 125 KiB |
Before Width: | Height: | Size: 110 KiB |
Before Width: | Height: | Size: 166 KiB |
Before Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 122 KiB |
Before Width: | Height: | Size: 131 KiB |
|
@ -1,73 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>用户登录</title>
|
|
||||||
<style>
|
|
||||||
#container {
|
|
||||||
width: 400px;
|
|
||||||
margin: 10px auto;
|
|
||||||
}
|
|
||||||
.input, form+a {
|
|
||||||
margin: 20px 20px;
|
|
||||||
width: 250px;
|
|
||||||
}
|
|
||||||
.input>label {
|
|
||||||
display: inline-block;
|
|
||||||
width: 70px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
.input:last-child {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
input[type=text], input[type=password] {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
input[type=submit], input[type=reset] {
|
|
||||||
width: 60px;
|
|
||||||
}
|
|
||||||
form+a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: blue;
|
|
||||||
}
|
|
||||||
.captcha {
|
|
||||||
width: 380px;
|
|
||||||
}
|
|
||||||
.captcha>img {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
.hint {
|
|
||||||
margin-left: 20px;
|
|
||||||
color: red;
|
|
||||||
font-size: 0.8em;
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="container">
|
|
||||||
<h1>用户登录</h1>
|
|
||||||
<hr>
|
|
||||||
<form action="" method="post">
|
|
||||||
<div class="input">
|
|
||||||
<label>用户名:</label>
|
|
||||||
<input type="text" name="username">
|
|
||||||
</div>
|
|
||||||
<div class="input">
|
|
||||||
<label>密码:</label>
|
|
||||||
<input type="password" name="password">
|
|
||||||
</div>
|
|
||||||
<div class="input captcha">
|
|
||||||
<label>验证码:</label>
|
|
||||||
<input type="text" name="captcha">
|
|
||||||
<img id="code" src="images/captcha.jpg" width="120">
|
|
||||||
</div>
|
|
||||||
<div class="input">
|
|
||||||
<input type="submit" value="登录">
|
|
||||||
<input type="reset" value="重置">
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<a href="register.html">注册新用户</a>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,87 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>用户注册</title>
|
|
||||||
<style>
|
|
||||||
#container {
|
|
||||||
width: 400px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
.input, form+a {
|
|
||||||
margin: 20px 20px;
|
|
||||||
width: 250px;
|
|
||||||
}
|
|
||||||
.input>label {
|
|
||||||
display: inline-block;
|
|
||||||
width: 80px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
.input:last-child {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
input[type=text], input[type=password] {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
input[type=submit], input[type=reset] {
|
|
||||||
width: 60px;
|
|
||||||
}
|
|
||||||
form+a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: blue;
|
|
||||||
}
|
|
||||||
.hint {
|
|
||||||
margin-left: 20px;
|
|
||||||
color: red;
|
|
||||||
font-size: 0.8em;
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
.mobile {
|
|
||||||
width: 300px;
|
|
||||||
}
|
|
||||||
.photo {
|
|
||||||
width: 350px;
|
|
||||||
}
|
|
||||||
.preview {
|
|
||||||
margin: 10px 50px;
|
|
||||||
}
|
|
||||||
.intro {
|
|
||||||
width: 720px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="container">
|
|
||||||
<h1>用户注册</h1>
|
|
||||||
<hr>
|
|
||||||
<form action="" method="post">
|
|
||||||
<div class="input">
|
|
||||||
<label>用户名:</label>
|
|
||||||
<input type="text" name="username" required minlength="4" maxlength="20">
|
|
||||||
</div>
|
|
||||||
<div class="input">
|
|
||||||
<label>密码:</label>
|
|
||||||
<input type="password" name="password">
|
|
||||||
</div>
|
|
||||||
<div class="input">
|
|
||||||
<label>确认密码:</label>
|
|
||||||
<input type="password" name="repassword">
|
|
||||||
</div>
|
|
||||||
<div class="input mobile">
|
|
||||||
<label>手机号:</label>
|
|
||||||
<input type="tel" name="tel">
|
|
||||||
<input type="button" id="sendBtn" value="发送验证码">
|
|
||||||
</div>
|
|
||||||
<div class="input">
|
|
||||||
<label>验证码:</label>
|
|
||||||
<input type="text" name="mobilecode">
|
|
||||||
</div>
|
|
||||||
<div class="input">
|
|
||||||
<input type="submit" value="注册">
|
|
||||||
<input type="reset" value="重置">
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<a href="login.html">返回登录</a>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,67 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>学科信息</title>
|
|
||||||
<style>
|
|
||||||
#container {
|
|
||||||
width: 80%;
|
|
||||||
margin: 10px auto;
|
|
||||||
}
|
|
||||||
#main dt {
|
|
||||||
font-size: 1.5em;
|
|
||||||
}
|
|
||||||
#main dd {
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
#main a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: blue;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="container">
|
|
||||||
<h1>千锋教育成都校区所有学科</h1>
|
|
||||||
<hr>
|
|
||||||
<div id="main">
|
|
||||||
<dl>
|
|
||||||
<dt>
|
|
||||||
<a href="teachers.html">Python全栈+人工智能</a>
|
|
||||||
<img src="images/hot-icon-small.png">
|
|
||||||
</dt>
|
|
||||||
<dd>
|
|
||||||
Python(英国发音:/ˈpaɪθən/ 美国发音:/ˈpaɪθɑːn/)是一种广泛使用的解释型、高级编程、通用型编程语言,
|
|
||||||
由吉多·范罗苏姆创造,第一版发布于1991年。可以视之为一种改良(加入一些其他编程语言的优点,如面向对象)的LISP。
|
|
||||||
Python的设计哲学强调代码的可读性和简洁的语法(尤其是使用空格缩进划分代码块,而非使用大括号或者关键词)。
|
|
||||||
相比于C++或Java,Python让开发者能够用更少的代码表达想法。不管是小型还是大型程序,该语言都试图让程序的结构清晰明了。
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
<dl>
|
|
||||||
<dt>
|
|
||||||
<a href="">全栈软件测试</a>
|
|
||||||
</dt>
|
|
||||||
<dd>
|
|
||||||
软件测试在规定的条件下对程序进行操作,以发现程序错误,衡量软件质量,并对其是否能满足设计要求进行评估的过程。
|
|
||||||
软件测试有许多方法,但对复杂的产品运行有效测试不仅仅是研究过程,更是创造并严格遵守某些呆板步骤的大事。
|
|
||||||
测试的其中一个定义:为了评估而质疑产品的过程;这里的“质疑”是测试员试着对产品做的事,而产品以测试者脚本行为反应作为回答。
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
<dl>
|
|
||||||
<dt>
|
|
||||||
<a href="">JavaEE+分布式开发</a>
|
|
||||||
<img src="images/hot-icon-small.png">
|
|
||||||
</dt>
|
|
||||||
<dd>
|
|
||||||
Java是一种广泛使用的计算机编程语言,拥有跨平台、面向对象、泛型编程的特性,广泛应用于企业级Web应用开发和移动应用开发。
|
|
||||||
该语言由当时任职于太阳微系统的詹姆斯·高斯林等人于1990年代初开发Java语言的雏形,最初被命名为Oak,
|
|
||||||
目标设置在家用电器等小型系统的编程语言,应用在电视机、电话、闹钟、烤面包机等家用电器的控制和通信。
|
|
||||||
由于这些智能化家电的市场需求没有预期的高,太阳计算机系统(Sun公司)放弃了该项计划。
|
|
||||||
随着1990年代互联网的发展,Sun公司看见Oak在互联网上应用的前景,于是改造了Oak,于1995年5月以Java的名称正式发布。
|
|
||||||
Java伴随着互联网的迅猛发展而发展,逐渐成为重要的网络编程语言。
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,110 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>老师信息</title>
|
|
||||||
<style>
|
|
||||||
#container {
|
|
||||||
width: 80%;
|
|
||||||
margin: 10px auto;
|
|
||||||
}
|
|
||||||
.teacher {
|
|
||||||
width: 100%;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 10px 0;
|
|
||||||
border-bottom: 1px dashed gray;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
.teacher div {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
.photo {
|
|
||||||
height: 140px;
|
|
||||||
border-radius: 75px;
|
|
||||||
overflow: hidden;
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
.info {
|
|
||||||
width: 75%;
|
|
||||||
margin-left: 30px;
|
|
||||||
}
|
|
||||||
.info div {
|
|
||||||
clear: both;
|
|
||||||
margin: 5px 10px;
|
|
||||||
}
|
|
||||||
.info span {
|
|
||||||
margin-right: 25px;
|
|
||||||
}
|
|
||||||
.info a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: blue;
|
|
||||||
}
|
|
||||||
.user {
|
|
||||||
float: right;
|
|
||||||
margin: 10px 10px;
|
|
||||||
}
|
|
||||||
.user+h1 {
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
#photo {
|
|
||||||
border-radius: 32px 32px 32px 32px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="container">
|
|
||||||
<h1>Python全栈+人工智能学科的老师信息</h1>
|
|
||||||
<hr>
|
|
||||||
<div class="teacher">
|
|
||||||
<div class="photo">
|
|
||||||
<img src="images/luohao.png" height="140" alt="">
|
|
||||||
</div>
|
|
||||||
<div class="info">
|
|
||||||
<div>
|
|
||||||
<span><strong>姓名:骆昊</strong></span>
|
|
||||||
<span>性别:男</span>
|
|
||||||
<span>出生日期:1980年11月28日</span>
|
|
||||||
</div>
|
|
||||||
<div class="intro">
|
|
||||||
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借贷平台等产品的研发,
|
|
||||||
一直践行“用知识创造快乐”的教学理念,善于总结,乐于分享。
|
|
||||||
</div>
|
|
||||||
<div class="comment">
|
|
||||||
<a href="">好评</a> (<strong>100</strong>)
|
|
||||||
|
|
||||||
<a href="">差评</a> (<strong>50</strong>)
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="teacher">
|
|
||||||
<div class="photo">
|
|
||||||
<img src="images/yuting.png" height="140" alt="">
|
|
||||||
</div>
|
|
||||||
<div class="info">
|
|
||||||
<div>
|
|
||||||
<span><strong>姓名:余婷</strong></span>
|
|
||||||
<span>性别:女</span>
|
|
||||||
<span>出生日期:1992年9月20日</span>
|
|
||||||
</div>
|
|
||||||
<div class="intro">
|
|
||||||
5年以上移动互联网项目开发经验和教学经验,曾担任上市游戏公司高级软件研发工程师和移动端(iOS)技术负责人,
|
|
||||||
参了多个企业级应用和游戏类应用的移动端开发和后台服务器开发,拥有丰富的开发经验和项目管理经验,
|
|
||||||
以个人开发者和协作开发者的身份在苹果的AppStore上发布过多款App。精通Python、C、Objective-C、Swift等开发语言,
|
|
||||||
熟悉iOS原生App开发、RESTful接口设计以及基于Cocos2d-x的游戏开发。授课条理清晰、细致入微,
|
|
||||||
性格活泼开朗、有较强的亲和力,教学过程注重理论和实践的结合,在学员中有良好的口碑。
|
|
||||||
</div>
|
|
||||||
<div class="comment">
|
|
||||||
<a href="">好评</a> (<strong>50</strong>)
|
|
||||||
|
|
||||||
<a href="">差评</a> (<strong>100</strong>)
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,12 +0,0 @@
|
||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
from cart.models import Goods
|
|
||||||
|
|
||||||
|
|
||||||
class GoodsAdmin(admin.ModelAdmin):
|
|
||||||
|
|
||||||
list_display = ('id', 'name', 'price', 'image')
|
|
||||||
search_fields = ('name', )
|
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Goods, GoodsAdmin)
|
|
|
@ -1,5 +0,0 @@
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class CartConfig(AppConfig):
|
|
||||||
name = 'cart'
|
|
|
@ -1,27 +0,0 @@
|
||||||
# Generated by Django 2.0.5 on 2018-05-25 06:28
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Goods',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(db_column='gid', primary_key=True, serialize=False)),
|
|
||||||
('name', models.CharField(db_column='gname', max_length=50)),
|
|
||||||
('price', models.DecimalField(db_column='gprice', decimal_places=2, max_digits=10)),
|
|
||||||
('image', models.CharField(db_column='gimage', max_length=255)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'db_table': 'tb_goods',
|
|
||||||
'ordering': ('id',),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,15 +0,0 @@
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
class Goods(models.Model):
|
|
||||||
"""商品模型类"""
|
|
||||||
|
|
||||||
id = models.AutoField(primary_key=True, db_column='gid')
|
|
||||||
name = models.CharField(max_length=50, db_column='gname')
|
|
||||||
price = models.DecimalField(max_digits=10, decimal_places=2, db_column='gprice')
|
|
||||||
image = models.CharField(max_length=255, db_column='gimage')
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
|
|
||||||
db_table = 'tb_goods'
|
|
||||||
ordering = ('id', )
|
|
|
@ -1,77 +0,0 @@
|
||||||
from django.core import serializers
|
|
||||||
from django.shortcuts import render, redirect
|
|
||||||
|
|
||||||
from cart.models import Goods
|
|
||||||
|
|
||||||
|
|
||||||
def index(request):
|
|
||||||
goods_list = list(Goods.objects.all())
|
|
||||||
return render(request, 'goods.html', {'goods_list': goods_list})
|
|
||||||
|
|
||||||
|
|
||||||
class CartItem(object):
|
|
||||||
"""购物车中的商品项"""
|
|
||||||
|
|
||||||
def __init__(self, goods, amount=1):
|
|
||||||
self.goods = goods
|
|
||||||
self.amount = amount
|
|
||||||
|
|
||||||
@property
|
|
||||||
def total(self):
|
|
||||||
return self.goods.price * self.amount
|
|
||||||
|
|
||||||
|
|
||||||
class ShoppingCart(object):
|
|
||||||
"""购物车"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.items = {}
|
|
||||||
self.index = 0
|
|
||||||
|
|
||||||
def add_item(self, item):
|
|
||||||
if item.goods.id in self.items:
|
|
||||||
self.items[item.goods.id].amount += item.amount
|
|
||||||
else:
|
|
||||||
self.items[item.goods.id] = item
|
|
||||||
|
|
||||||
def remove_item(self, id):
|
|
||||||
if id in self.items:
|
|
||||||
self.items.remove(id)
|
|
||||||
|
|
||||||
def clear_all_items(self):
|
|
||||||
self.items.clear()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cart_items(self):
|
|
||||||
return self.items.values()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def total(self):
|
|
||||||
val = 0
|
|
||||||
for item in self.items.values():
|
|
||||||
val += item.total
|
|
||||||
return val
|
|
||||||
|
|
||||||
|
|
||||||
def add_to_cart(request, id):
|
|
||||||
goods = Goods.objects.get(pk=id)
|
|
||||||
# 通过request对象的session属性可以获取到session
|
|
||||||
# session相当于是服务器端用来保存用户数据的一个字典
|
|
||||||
# session利用了Cookie保存sessionid
|
|
||||||
# 通过sessionid就可以获取与某个用户对应的会话(也就是用户数据)
|
|
||||||
# 如果在浏览器中清除了Cookie那么也就清除了sessionid
|
|
||||||
# 再次访问服务器时服务器会重新分配新的sessionid这也就意味着之前的用户数据无法找回
|
|
||||||
# 默认情况下Django的session被设定为持久会话而非浏览器续存期会话
|
|
||||||
# 通过SESSION_EXPIRE_AT_BROWSER_CLOSE和SESSION_COOKIE_AGE参数可以修改默认设定
|
|
||||||
# Django中的session是进行了持久化处理的因此需要设定session的序列化方式
|
|
||||||
# 1.6版开始Django默认的session序列化器是JsonSerializer
|
|
||||||
# 可以通过SESSION_SERIALIZER来设定其他的序列化器(例如PickleSerializer)
|
|
||||||
cart = request.session.get('cart', ShoppingCart())
|
|
||||||
cart.add_item(CartItem(goods))
|
|
||||||
request.session['cart'] = cart
|
|
||||||
return redirect('/')
|
|
||||||
|
|
||||||
|
|
||||||
def show_cart(request):
|
|
||||||
cart = serializers.deserialize(request.session.get('cart'))
|
|
||||||
return render(request, 'cart.html', {'cart': cart})
|
|
|
@ -1,15 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "shop.settings")
|
|
||||||
try:
|
|
||||||
from django.core.management import execute_from_command_line
|
|
||||||
except ImportError as exc:
|
|
||||||
raise ImportError(
|
|
||||||
"Couldn't import Django. Are you sure it's installed and "
|
|
||||||
"available on your PYTHONPATH environment variable? Did you "
|
|
||||||
"forget to activate a virtual environment?"
|
|
||||||
) from exc
|
|
||||||
execute_from_command_line(sys.argv)
|
|
|
@ -1,3 +0,0 @@
|
||||||
import pymysql
|
|
||||||
|
|
||||||
pymysql.install_as_MySQLdb()
|
|
|
@ -1,129 +0,0 @@
|
||||||
"""
|
|
||||||
Django settings for shop project.
|
|
||||||
|
|
||||||
Generated by 'django-admin startproject' using Django 2.0.5.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/2.0/topics/settings/
|
|
||||||
|
|
||||||
For the full list of settings and their values, see
|
|
||||||
https://docs.djangoproject.com/en/2.0/ref/settings/
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
|
||||||
# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/
|
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
|
||||||
SECRET_KEY = '3(n^av%_kt*^2zhz0!iwkxv6_wp^ed7-dpow*vqr7ck0_6=9^e'
|
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
|
||||||
DEBUG = True
|
|
||||||
|
|
||||||
ALLOWED_HOSTS = []
|
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
|
||||||
'django.contrib.admin',
|
|
||||||
'django.contrib.auth',
|
|
||||||
'django.contrib.contenttypes',
|
|
||||||
'django.contrib.sessions',
|
|
||||||
'django.contrib.messages',
|
|
||||||
'django.contrib.staticfiles',
|
|
||||||
'cart.apps.CartConfig',
|
|
||||||
]
|
|
||||||
|
|
||||||
MIDDLEWARE = [
|
|
||||||
'django.middleware.security.SecurityMiddleware',
|
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
||||||
'django.middleware.common.CommonMiddleware',
|
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
|
||||||
]
|
|
||||||
|
|
||||||
ROOT_URLCONF = 'shop.urls'
|
|
||||||
|
|
||||||
TEMPLATES = [
|
|
||||||
{
|
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
||||||
'DIRS': [os.path.join(BASE_DIR, 'templates')]
|
|
||||||
,
|
|
||||||
'APP_DIRS': True,
|
|
||||||
'OPTIONS': {
|
|
||||||
'context_processors': [
|
|
||||||
'django.template.context_processors.debug',
|
|
||||||
'django.template.context_processors.request',
|
|
||||||
'django.contrib.auth.context_processors.auth',
|
|
||||||
'django.contrib.messages.context_processors.messages',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
WSGI_APPLICATION = 'shop.wsgi.application'
|
|
||||||
|
|
||||||
|
|
||||||
# Database
|
|
||||||
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases
|
|
||||||
|
|
||||||
DATABASES = {
|
|
||||||
'default': {
|
|
||||||
'ENGINE': 'django.db.backends.mysql',
|
|
||||||
'NAME': 'shop',
|
|
||||||
'HOST': 'localhost',
|
|
||||||
'PORT': 3306,
|
|
||||||
'USER': 'yourname',
|
|
||||||
'PASSWORD': 'yourpass',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Password validation
|
|
||||||
# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators
|
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
# SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
|
|
||||||
|
|
||||||
# Internationalization
|
|
||||||
# https://docs.djangoproject.com/en/2.0/topics/i18n/
|
|
||||||
|
|
||||||
LANGUAGE_CODE = 'zh-hans'
|
|
||||||
|
|
||||||
TIME_ZONE = 'Asia/Chongqing'
|
|
||||||
|
|
||||||
USE_I18N = True
|
|
||||||
|
|
||||||
USE_L10N = True
|
|
||||||
|
|
||||||
USE_TZ = True
|
|
||||||
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
|
||||||
# https://docs.djangoproject.com/en/2.0/howto/static-files/
|
|
||||||
|
|
||||||
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'),]
|
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
|
|
@ -1,26 +0,0 @@
|
||||||
"""shop URL Configuration
|
|
||||||
|
|
||||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
|
||||||
https://docs.djangoproject.com/en/2.0/topics/http/urls/
|
|
||||||
Examples:
|
|
||||||
Function views
|
|
||||||
1. Add an import: from my_app import views
|
|
||||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
|
||||||
Class-based views
|
|
||||||
1. Add an import: from other_app.views import Home
|
|
||||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
|
||||||
Including another URLconf
|
|
||||||
1. Import the include() function: from django.urls import include, path
|
|
||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
|
||||||
"""
|
|
||||||
from django.contrib import admin
|
|
||||||
from django.urls import path
|
|
||||||
|
|
||||||
from cart import views
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path('', views.index),
|
|
||||||
path('add_to_cart/<int:id>', views.add_to_cart),
|
|
||||||
path('show_cart', views.show_cart),
|
|
||||||
path('admin/', admin.site.urls),
|
|
||||||
]
|
|
Before Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 10 KiB |
|
@ -1,55 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<style>
|
|
||||||
th, td { margin: 0; padding: 0; width: 180px; text-align: left; }
|
|
||||||
.name { font-size: 14px; font-weight: bolder; width: 280px; }
|
|
||||||
.price { color: red; font-size: 18px; }
|
|
||||||
a { display: inline-block; text-align: center; background-color: red; }
|
|
||||||
.back { width: 120px; height: 30px; line-height: 30px; }
|
|
||||||
.del { width: 60px; height: 20px; line-height: 20px; }
|
|
||||||
a:link, a:visited { color: white; text-decoration: none; }
|
|
||||||
.left { float: left; width: 1000px;}
|
|
||||||
.right { float: right; }
|
|
||||||
.total { text-align: right; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="left">
|
|
||||||
<h1>购物车列表</h1>
|
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
<div class="right">
|
|
||||||
<a href="/" class="back">返回</a>
|
|
||||||
</div>
|
|
||||||
{% if cart %}
|
|
||||||
<table style="clear: both;">
|
|
||||||
<tr>
|
|
||||||
<th>商品名称</th>
|
|
||||||
<th>商品单价</th>
|
|
||||||
<th>商品数量</th>
|
|
||||||
<th>商品总价</th>
|
|
||||||
<th>操作</th>
|
|
||||||
</tr>
|
|
||||||
{% for item in cart %}
|
|
||||||
<tr>
|
|
||||||
<td class="name">{{ item.goods.name }}</td>
|
|
||||||
<td class="price">¥{{ item.goods.price }}</td>
|
|
||||||
<td>{{ item.amount }}</td>
|
|
||||||
<td class="price">¥{{ item.total }}</td>
|
|
||||||
<td>
|
|
||||||
<a href="" class="del">删除</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
<tr>
|
|
||||||
<td colspan="5" class="total price">¥{{ cart.total }}元</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<a href="" class="back">清空购物车</a>
|
|
||||||
{% else %}
|
|
||||||
<h3 style="clear: both;">购物车中暂时没有商品!</h3>
|
|
||||||
{% endif %}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,46 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
{% load staticfiles %}
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<style>
|
|
||||||
img { display: inline-block; width: 150px; height: 150px; border: 1px solid gray; }
|
|
||||||
th, td { margin: 0; padding: 0; width: 250px; text-align: left; }
|
|
||||||
.name { font-size: 14px; font-weight: bolder; }
|
|
||||||
.price { color: red; font-size: 18px; }
|
|
||||||
a { display: inline-block; width: 120px; height: 30px; line-height: 30px; text-align: center; background-color: red; }
|
|
||||||
a:link, a:visited { color: white; text-decoration: none; }
|
|
||||||
.left { float: left; width: 1000px;}
|
|
||||||
.right { float: right; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="left">
|
|
||||||
<h1>商品列表</h1>
|
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
<div class="right">
|
|
||||||
<a href="/show_cart">查看购物车</a>
|
|
||||||
</div>
|
|
||||||
<table style="clear:both;">
|
|
||||||
<tr>
|
|
||||||
<th>商品名称</th>
|
|
||||||
<th>商品价格</th>
|
|
||||||
<th>商品图片</th>
|
|
||||||
<th>操作</th>
|
|
||||||
</tr>
|
|
||||||
{% for goods in goods_list %}
|
|
||||||
<tr>
|
|
||||||
<td class="name">{{ goods.name }}</td>
|
|
||||||
<td class="price">¥{{ goods.price }}</td>
|
|
||||||
<td>
|
|
||||||
<img src="{% static goods.image %}" alt="{{ goods.name }}">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="/add_to_cart/{{ goods.id }}">加入购物车</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,11 +0,0 @@
|
||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
from cart.models import Goods
|
|
||||||
|
|
||||||
|
|
||||||
class GoodsAdmin(admin.ModelAdmin):
|
|
||||||
|
|
||||||
list_display = ('id', 'name', 'price', 'image')
|
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Goods, GoodsAdmin)
|
|
|
@ -1,5 +0,0 @@
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class CartConfig(AppConfig):
|
|
||||||
name = 'cart'
|
|
|
@ -1,27 +0,0 @@
|
||||||
# Generated by Django 2.0.5 on 2018-05-25 05:11
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Goods',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(db_column='gid', primary_key=True, serialize=False)),
|
|
||||||
('name', models.CharField(db_column='gname', max_length=50)),
|
|
||||||
('price', models.DecimalField(db_column='gprice', decimal_places=2, max_digits=10)),
|
|
||||||
('image', models.CharField(db_column='gimage', max_length=255)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'db_table': 'tb_goods',
|
|
||||||
'ordering': ('id',),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,13 +0,0 @@
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
class Goods(models.Model):
|
|
||||||
|
|
||||||
id = models.AutoField(primary_key=True, db_column='gid')
|
|
||||||
name = models.CharField(max_length=50, db_column='gname')
|
|
||||||
price = models.DecimalField(max_digits=10, decimal_places=2, db_column='gprice')
|
|
||||||
image = models.CharField(max_length=255, db_column='gimage')
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
db_table = 'tb_goods'
|
|
||||||
ordering = ('id',)
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
|
@ -1,16 +0,0 @@
|
||||||
from django.shortcuts import render
|
|
||||||
|
|
||||||
from cart.models import Goods
|
|
||||||
|
|
||||||
|
|
||||||
def index(request):
|
|
||||||
goods_list = list(Goods.objects.all())
|
|
||||||
return render(request, 'goods.html', {'goods_list': goods_list})
|
|
||||||
|
|
||||||
|
|
||||||
def show_cart(request):
|
|
||||||
return render(request, 'cart.html')
|
|
||||||
|
|
||||||
|
|
||||||
def add_to_cart(request, no):
|
|
||||||
pass
|
|
|
@ -1,3 +0,0 @@
|
||||||
import pymysql
|
|
||||||
|
|
||||||
pymysql.install_as_MySQLdb()
|
|
|
@ -1,16 +0,0 @@
|
||||||
"""
|
|
||||||
WSGI config for shop project.
|
|
||||||
|
|
||||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from django.core.wsgi import get_wsgi_application
|
|
||||||
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "shop.settings")
|
|
||||||
|
|
||||||
application = get_wsgi_application()
|
|
|
@ -1,7 +0,0 @@
|
||||||
insert into tb_goods values
|
|
||||||
(default, '乐事(Lay’s)无限薯片', 8.2, 'images/lay.jpg'),
|
|
||||||
(default, '旺旺 仙贝 加量装 540g', 18.5, 'images/wang.jpg'),
|
|
||||||
(default, '多儿比(Dolbee)黄桃水果罐头', 6.8, 'images/dolbee.jpg'),
|
|
||||||
(default, '王致和 精制料酒 500ml', 7.9, 'images/wine.jpg'),
|
|
||||||
(default, '陈克明 面条 鸡蛋龙须挂面', 1.0, 'images/noodle.jpg'),
|
|
||||||
(default, '鲁花 菜籽油 4L', 69.9, 'images/oil.jpg');
|
|
Before Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 10 KiB |
|
@ -1,55 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<style>
|
|
||||||
th, td { margin: 0; padding: 0; width: 180px; text-align: left; }
|
|
||||||
.name { font-size: 14px; font-weight: bolder; width: 280px; }
|
|
||||||
.price { color: red; font-size: 18px; }
|
|
||||||
a { display: inline-block; text-align: center; background-color: red; }
|
|
||||||
.back { width: 120px; height: 30px; line-height: 30px; }
|
|
||||||
.del { width: 60px; height: 20px; line-height: 20px; }
|
|
||||||
a:link, a:visited { color: white; text-decoration: none; }
|
|
||||||
.left { float: left; width: 1000px;}
|
|
||||||
.right { float: right; }
|
|
||||||
.total { text-align: right; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="left">
|
|
||||||
<h1>购物车列表</h1>
|
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
<div class="right">
|
|
||||||
<a href="list_goods" class="back">返回</a>
|
|
||||||
</div>
|
|
||||||
{% if cart_items %}
|
|
||||||
<table style="clear: both;">
|
|
||||||
<tr>
|
|
||||||
<th>商品名称</th>
|
|
||||||
<th>商品单价</th>
|
|
||||||
<th>商品数量</th>
|
|
||||||
<th>商品总价</th>
|
|
||||||
<th>操作</th>
|
|
||||||
</tr>
|
|
||||||
{% for item in cart_items %}
|
|
||||||
<tr>
|
|
||||||
<td class="name">{{ item.name }}</td>
|
|
||||||
<td class="price">¥{{ item.unit_price }}</td>
|
|
||||||
<td>{{ item.amount }}</td>
|
|
||||||
<td class="price">¥{{ item.total_price }}</td>
|
|
||||||
<td>
|
|
||||||
<a href="" class="del">删除</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
<tr>
|
|
||||||
<td colspan="5" class="total price">¥{{ cart.total }}元</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<a href="clear_cart" class="back">清空购物车</a>
|
|
||||||
{% else %}
|
|
||||||
<h3 style="clear: both;">购物车中暂时没有商品!</h3>
|
|
||||||
{% endif %}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,46 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
{% load staticfiles %}
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<style>
|
|
||||||
img { display: inline-block; width: 150px; height: 150px; border: 1px solid gray; }
|
|
||||||
th, td { margin: 0; padding: 0; width: 250px; text-align: left; }
|
|
||||||
.name { font-size: 14px; font-weight: bolder; }
|
|
||||||
.price { color: red; font-size: 18px; }
|
|
||||||
a { display: inline-block; width: 120px; height: 30px; line-height: 30px; text-align: center; background-color: red; }
|
|
||||||
a:link, a:visited { color: white; text-decoration: none; }
|
|
||||||
.left { float: left; width: 1000px;}
|
|
||||||
.right { float: right; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="left">
|
|
||||||
<h1>商品列表</h1>
|
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
<div class="right">
|
|
||||||
<a href="/show_cart">查看购物车</a>
|
|
||||||
</div>
|
|
||||||
<table style="clear:both;">
|
|
||||||
<tr>
|
|
||||||
<th>商品名称</th>
|
|
||||||
<th>商品价格</th>
|
|
||||||
<th>商品图片</th>
|
|
||||||
<th>操作</th>
|
|
||||||
</tr>
|
|
||||||
{% for goods in goods_list %}
|
|
||||||
<tr>
|
|
||||||
<td class="name">{{ goods.name }}</td>
|
|
||||||
<td class="price">¥{{ goods.price }}</td>
|
|
||||||
<td>
|
|
||||||
<img src="{% static goods.image %}" alt="{{ goods.name }}">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="/add_to_cart/{{ goods.id }}">加入购物车</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 85 KiB |
After Width: | Height: | Size: 163 KiB |
After Width: | Height: | Size: 322 KiB |
After Width: | Height: | Size: 294 KiB |
After Width: | Height: | Size: 464 KiB |
After Width: | Height: | Size: 698 KiB |
After Width: | Height: | Size: 344 KiB |
After Width: | Height: | Size: 244 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 125 KiB |
Before Width: | Height: | Size: 253 KiB |
|
@ -27,7 +27,6 @@
|
||||||
大多数网站都会定义robots.txt文件,下面以淘宝的[robots.txt](http://www.taobao.com/robots.txt)文件为例,看看该网站对爬虫有哪些限制。
|
大多数网站都会定义robots.txt文件,下面以淘宝的[robots.txt](http://www.taobao.com/robots.txt)文件为例,看看该网站对爬虫有哪些限制。
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
User-agent: Baiduspider
|
User-agent: Baiduspider
|
||||||
Allow: /article
|
Allow: /article
|
||||||
Allow: /oshtml
|
Allow: /oshtml
|
||||||
|
@ -108,17 +107,17 @@ HTTP响应(响应行+响应头+空行+消息体):
|
||||||
|
|
||||||
![](./res/chrome-developer-tools.png)
|
![](./res/chrome-developer-tools.png)
|
||||||
|
|
||||||
2. POSTMAN:功能强大的网页调试与RESTful请求工具。
|
2. Postman:功能强大的网页调试与RESTful请求工具。
|
||||||
|
|
||||||
![](./res/postman.png)
|
![](./res/postman.png)
|
||||||
|
|
||||||
3. HTTPie:命令行HTTP客户端。
|
3. HTTPie:命令行HTTP客户端。
|
||||||
|
|
||||||
```Shell
|
```Bash
|
||||||
pip3 install httpie
|
pip3 install httpie
|
||||||
```
|
```
|
||||||
|
|
||||||
```Shell
|
```Bash
|
||||||
http --header http://www.scu.edu.cn
|
http --header http://www.scu.edu.cn
|
||||||
HTTP/1.1 200 OK
|
HTTP/1.1 200 OK
|
||||||
Accept-Ranges: bytes
|
Accept-Ranges: bytes
|
||||||
|
@ -138,9 +137,9 @@ HTTP响应(响应行+响应头+空行+消息体):
|
||||||
X-Frame-Options: SAMEORIGIN
|
X-Frame-Options: SAMEORIGIN
|
||||||
```
|
```
|
||||||
|
|
||||||
4. BuiltWith:识别网站所用技术的工具。
|
4. `builtwith`库:识别网站所用技术的工具。
|
||||||
|
|
||||||
```Shell
|
```Bash
|
||||||
pip3 install builtwith
|
pip3 install builtwith
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -155,9 +154,9 @@ HTTP响应(响应行+响应头+空行+消息体):
|
||||||
{'web-servers': ['Tengine'], 'web-frameworks': ['Twitter Bootstrap', 'Ruby on Rails'], 'programming-languages': ['Ruby']}
|
{'web-servers': ['Tengine'], 'web-frameworks': ['Twitter Bootstrap', 'Ruby on Rails'], 'programming-languages': ['Ruby']}
|
||||||
```
|
```
|
||||||
|
|
||||||
5. python-whois:查询网站所有者的工具。
|
5. `python-whois`库:查询网站所有者的工具。
|
||||||
|
|
||||||
```Shell
|
```Bash
|
||||||
pip3 install python-whois
|
pip3 install python-whois
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -167,7 +166,7 @@ HTTP响应(响应行+响应头+空行+消息体):
|
||||||
{'domain_name': ['BAIDU.COM', 'baidu.com'], 'registrar': 'MarkMonitor, Inc.', 'whois_server': 'whois.markmonitor.com', 'referral_url': None, 'updated_date': [datetime.datetime(2017, 7, 28, 2, 36, 28), datetime.datetime(2017, 7, 27, 19, 36, 28)], 'creation_date': [datetime.datetime(1999, 10, 11, 11, 5, 17), datetime.datetime(1999, 10, 11, 4, 5, 17)], 'expiration_date': [datetime.datetime(2026, 10, 11, 11, 5, 17), datetime.datetime(2026, 10, 11, 0, 0)], 'name_servers': ['DNS.BAIDU.COM', 'NS2.BAIDU.COM', 'NS3.BAIDU.COM', 'NS4.BAIDU.COM', 'NS7.BAIDU.COM', 'dns.baidu.com', 'ns4.baidu.com', 'ns3.baidu.com', 'ns7.baidu.com', 'ns2.baidu.com'], 'status': ['clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited', 'clientTransferProhibited https://icann.org/epp#clientTransferProhibited', 'clientUpdateProhibited https://icann.org/epp#clientUpdateProhibited', 'serverDeleteProhibited https://icann.org/epp#serverDeleteProhibited', 'serverTransferProhibited https://icann.org/epp#serverTransferProhibited', 'serverUpdateProhibited https://icann.org/epp#serverUpdateProhibited', 'clientUpdateProhibited (https://www.icann.org/epp#clientUpdateProhibited)', 'clientTransferProhibited (https://www.icann.org/epp#clientTransferProhibited)', 'clientDeleteProhibited (https://www.icann.org/epp#clientDeleteProhibited)', 'serverUpdateProhibited (https://www.icann.org/epp#serverUpdateProhibited)', 'serverTransferProhibited (https://www.icann.org/epp#serverTransferProhibited)', 'serverDeleteProhibited (https://www.icann.org/epp#serverDeleteProhibited)'], 'emails': ['abusecomplaints@markmonitor.com', 'whoisrelay@markmonitor.com'], 'dnssec': 'unsigned', 'name': None, 'org': 'Beijing Baidu Netcom Science Technology Co., Ltd.', 'address': None, 'city': None, 'state': 'Beijing', 'zipcode': None, 'country': 'CN'}
|
{'domain_name': ['BAIDU.COM', 'baidu.com'], 'registrar': 'MarkMonitor, Inc.', 'whois_server': 'whois.markmonitor.com', 'referral_url': None, 'updated_date': [datetime.datetime(2017, 7, 28, 2, 36, 28), datetime.datetime(2017, 7, 27, 19, 36, 28)], 'creation_date': [datetime.datetime(1999, 10, 11, 11, 5, 17), datetime.datetime(1999, 10, 11, 4, 5, 17)], 'expiration_date': [datetime.datetime(2026, 10, 11, 11, 5, 17), datetime.datetime(2026, 10, 11, 0, 0)], 'name_servers': ['DNS.BAIDU.COM', 'NS2.BAIDU.COM', 'NS3.BAIDU.COM', 'NS4.BAIDU.COM', 'NS7.BAIDU.COM', 'dns.baidu.com', 'ns4.baidu.com', 'ns3.baidu.com', 'ns7.baidu.com', 'ns2.baidu.com'], 'status': ['clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited', 'clientTransferProhibited https://icann.org/epp#clientTransferProhibited', 'clientUpdateProhibited https://icann.org/epp#clientUpdateProhibited', 'serverDeleteProhibited https://icann.org/epp#serverDeleteProhibited', 'serverTransferProhibited https://icann.org/epp#serverTransferProhibited', 'serverUpdateProhibited https://icann.org/epp#serverUpdateProhibited', 'clientUpdateProhibited (https://www.icann.org/epp#clientUpdateProhibited)', 'clientTransferProhibited (https://www.icann.org/epp#clientTransferProhibited)', 'clientDeleteProhibited (https://www.icann.org/epp#clientDeleteProhibited)', 'serverUpdateProhibited (https://www.icann.org/epp#serverUpdateProhibited)', 'serverTransferProhibited (https://www.icann.org/epp#serverTransferProhibited)', 'serverDeleteProhibited (https://www.icann.org/epp#serverDeleteProhibited)'], 'emails': ['abusecomplaints@markmonitor.com', 'whoisrelay@markmonitor.com'], 'dnssec': 'unsigned', 'name': None, 'org': 'Beijing Baidu Netcom Science Technology Co., Ltd.', 'address': None, 'city': None, 'state': 'Beijing', 'zipcode': None, 'country': 'CN'}
|
||||||
```
|
```
|
||||||
|
|
||||||
6. robotparser:解析robots.txt的工具。
|
6. `robotparser`模块:解析`robots.txt`的工具。
|
||||||
|
|
||||||
```Python
|
```Python
|
||||||
>>> from urllib import robotparser
|
>>> from urllib import robotparser
|
||||||
|
@ -199,110 +198,106 @@ HTTP响应(响应行+响应头+空行+消息体):
|
||||||
下面的例子给出了一个从“搜狐体育”上获取NBA新闻标题和链接的爬虫。
|
下面的例子给出了一个从“搜狐体育”上获取NBA新闻标题和链接的爬虫。
|
||||||
|
|
||||||
```Python
|
```Python
|
||||||
from urllib.error import URLError
|
|
||||||
from urllib.request import urlopen
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import pymysql
|
from collections import deque
|
||||||
import ssl
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
from pymysql import Error
|
import requests
|
||||||
|
|
||||||
|
LI_A_PATTERN = re.compile(r'<li class="item">.*?</li>')
|
||||||
|
A_TEXT_PATTERN = re.compile(r'<a\s+[^>]*?>(.*?)</a>')
|
||||||
|
A_HREF_PATTERN = re.compile(r'<a\s+[^>]*?href="(.*?)"\s*[^>]*?>')
|
||||||
|
|
||||||
|
|
||||||
def decode_page(page_bytes, charsets=('utf-8',)):
|
def decode_page(page_bytes, charsets):
|
||||||
"""通过指定的字符集对页面进行解码(不是每个网站都将字符集设置为utf-8)"""
|
"""通过指定的字符集对页面进行解码"""
|
||||||
page_html = None
|
|
||||||
for charset in charsets:
|
for charset in charsets:
|
||||||
try:
|
try:
|
||||||
page_html = page_bytes.decode(charset)
|
return page_bytes.decode(charset)
|
||||||
break
|
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
pass
|
pass
|
||||||
# logging.error('Decode:', error)
|
|
||||||
return page_html
|
|
||||||
|
|
||||||
|
|
||||||
def get_page_html(seed_url, *, retry_times=3, charsets=('utf-8',)):
|
def get_matched_parts(content_string, pattern):
|
||||||
"""获取页面的HTML代码(通过递归实现指定次数的重试操作)"""
|
"""从字符串中提取所有跟正则表达式匹配的内容"""
|
||||||
page_html = None
|
return pattern.findall(content_string, re.I) \
|
||||||
try:
|
if content_string else []
|
||||||
page_html = decode_page(urlopen(seed_url).read(), charsets)
|
|
||||||
except URLError:
|
|
||||||
# logging.error('URL:', error)
|
|
||||||
if retry_times > 0:
|
|
||||||
return get_page_html(seed_url, retry_times=retry_times - 1,
|
|
||||||
charsets=charsets)
|
|
||||||
return page_html
|
|
||||||
|
|
||||||
|
|
||||||
def get_matched_parts(page_html, pattern_str, pattern_ignore_case=re.I):
|
def get_matched_part(content_string, pattern, group_no=1):
|
||||||
"""从页面中提取需要的部分(通常是链接也可以通过正则表达式进行指定)"""
|
"""从字符串中提取跟正则表达式匹配的内容"""
|
||||||
pattern_regex = re.compile(pattern_str, pattern_ignore_case)
|
match = pattern.search(content_string)
|
||||||
return pattern_regex.findall(page_html) if page_html else []
|
if match:
|
||||||
|
return match.group(group_no)
|
||||||
|
|
||||||
|
|
||||||
def start_crawl(seed_url, match_pattern, *, max_depth=-1):
|
def get_page_html(seed_url, *, charsets=('utf-8', )):
|
||||||
"""开始执行爬虫程序并对指定的数据进行持久化操作"""
|
"""获取页面的HTML代码"""
|
||||||
conn = pymysql.connect(host='localhost', port=3306,
|
resp = requests.get(seed_url)
|
||||||
database='crawler', user='root',
|
if resp.status_code == 200:
|
||||||
password='123456', charset='utf8')
|
return decode_page(resp.content, charsets)
|
||||||
try:
|
|
||||||
with conn.cursor() as cursor:
|
|
||||||
url_list = [seed_url]
|
def repair_incorrect_href(current_url, href):
|
||||||
# 通过下面的字典避免重复抓取并控制抓取深度
|
"""修正获取的href属性"""
|
||||||
visited_url_list = {seed_url: 0}
|
if href.startswith('//'):
|
||||||
while url_list:
|
href = urljoin('http://', href)
|
||||||
current_url = url_list.pop(0)
|
elif href.startswith('/'):
|
||||||
depth = visited_url_list[current_url]
|
href = urljoin(current_url, href)
|
||||||
|
return href if href.startswith('http') else ''
|
||||||
|
|
||||||
|
|
||||||
|
def start_crawl(seed_url, pattern, *, max_depth=-1):
|
||||||
|
"""开始爬取数据"""
|
||||||
|
new_urls, visited_urls = deque(), set()
|
||||||
|
new_urls.append((seed_url, 0))
|
||||||
|
while new_urls:
|
||||||
|
current_url, depth = new_urls.popleft()
|
||||||
if depth != max_depth:
|
if depth != max_depth:
|
||||||
# 尝试用utf-8/gbk/gb2312三种字符集进行页面解码
|
page_html = get_page_html(current_url, charsets=('utf-8', 'gbk'))
|
||||||
page_html = get_page_html(current_url, charsets=('utf-8', 'gbk', 'gb2312'))
|
contents = get_matched_parts(page_html, pattern)
|
||||||
links_list = get_matched_parts(page_html, match_pattern)
|
for content in contents:
|
||||||
param_list = []
|
text = get_matched_part(content, A_TEXT_PATTERN)
|
||||||
for link in links_list:
|
href = get_matched_part(content, A_HREF_PATTERN)
|
||||||
if link not in visited_url_list:
|
if href:
|
||||||
visited_url_list[link] = depth + 1
|
href = repair_incorrect_href(href)
|
||||||
page_html = get_page_html(link, charsets=('utf-8', 'gbk', 'gb2312'))
|
print(text, href)
|
||||||
headings = get_matched_parts(page_html, r'<h1>(.*)<span')
|
if href and href not in visited_urls:
|
||||||
if headings:
|
new_urls.append((href, depth + 1))
|
||||||
param_list.append((headings[0], link))
|
|
||||||
cursor.executemany('insert into tb_result values (default, %s, %s)',
|
|
||||||
param_list)
|
|
||||||
conn.commit()
|
|
||||||
except Error:
|
|
||||||
pass
|
|
||||||
# logging.error('SQL:', error)
|
|
||||||
finally:
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""主函数"""
|
"""主函数"""
|
||||||
ssl._create_default_https_context = ssl._create_unverified_context
|
start_crawl(
|
||||||
start_crawl('http://sports.sohu.com/nba_a.shtml',
|
seed_url='http://sports.sohu.com/nba_a.shtml',
|
||||||
r'<a[^>]+test=a\s[^>]*href=["\'](.*?)["\']',
|
pattern=LI_A_PATTERN,
|
||||||
max_depth=2)
|
max_depth=2
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
```
|
```
|
||||||
|
|
||||||
由于使用了MySQL实现持久化操作,所以要先启动MySQL服务器并创建名为`crawler`的数据库和名为`tb_result`的二维表才能运行该程序。
|
|
||||||
|
|
||||||
### 爬虫注意事项
|
### 爬虫注意事项
|
||||||
|
|
||||||
通过上面的例子,我们对爬虫已经有了一个感性的认识,在编写爬虫时有以下一些注意事项:
|
通过上面的例子,我们对爬虫已经有了一个感性的认识,在编写爬虫时有以下一些注意事项:
|
||||||
|
|
||||||
1. 处理相对链接。有的时候我们从页面中获取的链接不是一个完整的绝对链接而是一个相对链接,这种情况下需要将其与URL前缀进行拼接(`urllib.parse`中的`urljoin()`函数可以完成此项操作)。
|
1. 上面的代码使用了`requests`三方库来获取网络资源,这是一个非常优质的三方库,关于它的用法可以参考它的[官方文档](https://requests.readthedocs.io/zh_CN/latest/)。
|
||||||
|
|
||||||
2. 设置代理服务。有些网站会限制访问的区域(例如美国的Netflix屏蔽了很多国家的访问),有些爬虫需要隐藏自己的身份,在这种情况下可以设置使用代理服务器,代理服务器有免费的服务器和付费的商业服务器,但后者稳定性和可用性都更好,强烈建议在商业项目中使用付费的代理服务器。可以通过修改`urllib.request`中的`ProxyHandler`来为请求设置代理服务器。
|
2. 上面的代码中使用了双端队列(`deque`)来保存待爬取的URL。双端队列相当于是使用链式存储结构的`list`,在双端队列的头尾添加和删除元素性能都比较好,刚好可以用来构造一个FIFO(先进先出)的队列结构。
|
||||||
|
|
||||||
3. 限制下载速度。如果我们的爬虫获取网页的速度过快,可能就会面临被封禁或者产生“损害动产”的风险(这个可能会导致吃官司且败诉),可以在两次下载之间添加延时从而对爬虫进行限速。
|
3. 处理相对路径。有的时候我们从页面中获取的链接不是一个完整的绝对链接而是一个相对链接,这种情况下需要将其与URL前缀进行拼接(`urllib.parse`中的`urljoin()`函数可以完成此项操作)。
|
||||||
|
|
||||||
4. 避免爬虫陷阱。有些网站会动态生成页面内容,这会导致产生无限多的页面(例如在线万年历通常会有无穷无尽的链接)。可以通过记录到达当前页面经过了多少个链接(链接深度)来解决该问题,当达到事先设定的最大深度时爬虫就不再像队列中添加该网页中的链接了。
|
4. 设置代理服务。有些网站会限制访问的区域(例如美国的Netflix屏蔽了很多国家的访问),有些爬虫需要隐藏自己的身份,在这种情况下可以设置使用代理服务器,代理服务器有免费的服务器和付费的商业服务器,但后者稳定性和可用性都更好,强烈建议在商业项目中使用付费的商业代理服务器。如果使用`requests`三方库,可以在请求方法中添加`proxies`参数来指定代理服务器;如果使用标准库,可以通过修改`urllib.request`中的`ProxyHandler`来为请求设置代理服务器。
|
||||||
|
|
||||||
5. SSL相关问题。在使用`urlopen`打开一个HTTPS链接时会验证一次SSL证书,如果不做出处理会产生错误提示“SSL: CERTIFICATE_VERIFY_FAILED”,可以通过以下两种方式加以解决:
|
5. 限制下载速度。如果我们的爬虫获取网页的速度过快,可能就会面临被封禁或者产生“损害动产”的风险(这个可能会导致吃官司且败诉),可以在两次获取页面数据之间添加延时从而对爬虫进行限速。
|
||||||
|
|
||||||
|
6. 避免爬虫陷阱。有些网站会动态生成页面内容,这会导致产生无限多的页面(例如在线万年历通常会有无穷无尽的链接)。可以通过记录到达当前页面经过了多少个链接(链接深度)来解决该问题,当达到事先设定的最大深度时,爬虫就不再像队列中添加该网页中的链接了。
|
||||||
|
|
||||||
|
7. 避开蜜罐链接。网站上的有些链接是浏览器中不可见的,这种链接通常是故意诱使爬虫去访问的蜜罐,一旦访问了这些链接,服务器就会判定请求是来自于爬虫的,这样可能会导致被服务器封禁IP地址。如何避开这些蜜罐链接我们在后面为大家进行讲解。
|
||||||
|
|
||||||
|
8. SSL相关问题。如果使用标准库的`urlopen`打开一个HTTPS链接时会验证一次SSL证书,如果不做出处理会产生错误提示“SSL: CERTIFICATE_VERIFY_FAILED”,可以通过以下两种方式加以解决:
|
||||||
|
|
||||||
- 使用未经验证的上下文
|
- 使用未经验证的上下文
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
|
|
||||||
通过上一个章节的讲解,我们已经了解到了开发一个爬虫需要做的工作以及一些常见的问题,下面我们给出一个爬虫开发相关技术的清单以及这些技术涉及到的标准库和第三方库,稍后我们会一一介绍这些内容。
|
通过上一个章节的讲解,我们已经了解到了开发一个爬虫需要做的工作以及一些常见的问题,下面我们给出一个爬虫开发相关技术的清单以及这些技术涉及到的标准库和第三方库,稍后我们会一一介绍这些内容。
|
||||||
|
|
||||||
1. 下载数据 - **urllib** / **requests** / **aiohttp**。
|
1. 下载数据 - **urllib** / **requests** / **aiohttp** / **httpx**。
|
||||||
2. 解析数据 - **re** / **lxml** / **beautifulsoup4** / **pyquery**。
|
2. 解析数据 - **re** / **lxml** / **beautifulsoup4** / **pyquery**。
|
||||||
3. 缓存和持久化 - **pymysql** / **sqlalchemy** / **peewee**/ **redis** / **pymongo**。
|
3. 缓存和持久化 - **mysqlclient** / **sqlalchemy** / **peewee**/ **redis** / **pymongo**。
|
||||||
4. 生成数字签名 - **hashlib**。
|
4. 生成数字签名 - **hashlib**。
|
||||||
5. 序列化和压缩 - **pickle** / **json** / **zlib**。
|
5. 序列化和压缩 - **pickle** / **json** / **zlib**。
|
||||||
6. 调度器 - 多进程(**multiprocessing**) / 多线程(**threading**)。
|
6. 调度器 - **multiprocessing** / **threading** / **concurrent.futures**。
|
||||||
|
|
||||||
### HTML页面
|
### HTML页面
|
||||||
|
|
||||||
|
@ -79,47 +79,93 @@
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
// 此处省略JavaScript代码
|
/* 此处省略JavaScript代码 */
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
```
|
```
|
||||||
|
|
||||||
如果你对上面的代码并不感到陌生,那么你一定知道HTML页面通常由三部分构成,分别是用来承载内容的Tag(标签)、负责渲染页面的CSS(层叠样式表)以及控制交互式行为的JavaScript。通常,我们可以在浏览器的右键菜单中通过“查看网页源代码”的方式获取网页的代码并了解页面的结构;当然,我们也可以通过浏览器提供的开发人员工具来了解更多的信息。
|
如上所示的HTML页面通常由三部分构成,分别是用来承载内容的Tag(标签)、负责渲染页面的CSS(层叠样式表)以及控制交互式行为的JavaScript。通常,我们可以在浏览器的右键菜单中通过“查看网页源代码”的方式获取网页的代码并了解页面的结构;当然,我们也可以通过浏览器提供的开发人员工具来了解更多的信息。
|
||||||
|
|
||||||
#### 使用requests获取页面
|
#### 使用requests获取页面
|
||||||
|
|
||||||
|
在上一节课的代码中我们使用了三方库`requests`来获取页面,下面我们对`requests`库的用法做进一步说明。
|
||||||
|
|
||||||
1. GET请求和POST请求。
|
1. GET请求和POST请求。
|
||||||
|
|
||||||
```Python
|
```Python
|
||||||
|
import requests
|
||||||
|
|
||||||
|
resp = requests.get('http://www.baidu.com/index.html')
|
||||||
|
print(resp.status_code)
|
||||||
|
print(resp.headers)
|
||||||
|
print(resp.cookies)
|
||||||
|
print(resp.content.decode('utf-8'))
|
||||||
|
|
||||||
|
resp = requests.post('http://httpbin.org/post', data={'name': 'Hao', 'age': 40})
|
||||||
|
print(resp.text)
|
||||||
|
data = resp.json()
|
||||||
|
print(type(data))
|
||||||
```
|
```
|
||||||
|
|
||||||
2. URL参数和请求头。
|
2. URL参数和请求头。
|
||||||
|
|
||||||
```Python
|
```Python
|
||||||
|
resp = requests.get(
|
||||||
|
url='https://movie.douban.com/top250',
|
||||||
|
headers={
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) '
|
||||||
|
'AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||||
|
'Chrome/83.0.4103.97 Safari/537.36',
|
||||||
|
'Accept': 'text/html,application/xhtml+xml,application/xml;'
|
||||||
|
'q=0.9,image/webp,image/apng,*/*;'
|
||||||
|
'q=0.8,application/signed-exchange;v=b3;q=0.9',
|
||||||
|
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
print(resp.status_code)
|
||||||
```
|
```
|
||||||
|
|
||||||
3. 复杂的POST请求(文件上传)。
|
3. 复杂的POST请求(文件上传)。
|
||||||
|
|
||||||
```Python
|
```Python
|
||||||
|
resp = requests.post(
|
||||||
|
url='http://httpbin.org/post',
|
||||||
|
files={'file': open('data.xlsx', 'rb')}
|
||||||
|
)
|
||||||
|
print(resp.text)
|
||||||
```
|
```
|
||||||
|
|
||||||
4. 操作Cookie。
|
4. 操作Cookie。
|
||||||
|
|
||||||
```Python
|
```Python
|
||||||
|
cookies = {'key1': 'value1', 'key2': 'value2'}
|
||||||
|
resp = requests.get('http://httpbin.org/cookies', cookies=cookies)
|
||||||
|
print(resp.text)
|
||||||
|
|
||||||
|
jar = requests.cookies.RequestsCookieJar()
|
||||||
|
jar.set('tasty_cookie', 'yum', domain='httpbin.org', path='/cookies')
|
||||||
|
jar.set('gross_cookie', 'blech', domain='httpbin.org', path='/elsewhere')
|
||||||
|
resp = requests.get('http://httpbin.org/cookies', cookies=jar)
|
||||||
|
print(resp.text)
|
||||||
```
|
```
|
||||||
|
|
||||||
5. 设置代理服务器。
|
5. 设置代理服务器。
|
||||||
|
|
||||||
```Python
|
```Python
|
||||||
|
requests.get('https://www.taobao.com', proxies={
|
||||||
|
'http': 'http://10.10.1.10:3128',
|
||||||
|
'https': 'http://10.10.1.10:1080',
|
||||||
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
> 说明:关于requests的详细用法可以参考它的[官方文档](http://docs.python-requests.org/zh_CN/latest/user/quickstart.html)。
|
> **说明**:关于`requests`库的相关知识,还是强烈建议大家自行阅读它的[官方文档](https://requests.readthedocs.io/zh_CN/latest/)。
|
||||||
|
|
||||||
|
6. 设置请求超时。
|
||||||
|
|
||||||
|
```Python
|
||||||
|
requests.get('https://github.com', timeout=0.01)
|
||||||
|
```
|
||||||
|
|
||||||
### 页面解析
|
### 页面解析
|
||||||
|
|
||||||
|
@ -131,13 +177,41 @@
|
||||||
| XPath解析 | lxml | 快 | 一般 | 需要安装C语言依赖库<br/>唯一支持XML的解析器 |
|
| XPath解析 | lxml | 快 | 一般 | 需要安装C语言依赖库<br/>唯一支持XML的解析器 |
|
||||||
| CSS选择器解析 | bs4 / pyquery | 不确定 | 简单 | |
|
| CSS选择器解析 | bs4 / pyquery | 不确定 | 简单 | |
|
||||||
|
|
||||||
> 说明:BeautifulSoup可选的解析器包括:Python标准库(html.parser)、lxml的HTML解析器、lxml的XML解析器和html5lib。
|
> **说明**:`BeautifulSoup`可选的解析器包括:Python标准库中的`html.parser`、`lxml`的HTML解析器、`lxml`的XML解析器和`html5lib`。
|
||||||
|
|
||||||
#### 使用正则表达式解析页面
|
#### 使用正则表达式解析页面
|
||||||
|
|
||||||
如果你对正则表达式没有任何的概念,那么推荐先阅读[《正则表达式30分钟入门教程》](),然后再阅读我们之前讲解在Python中如何使用正则表达式一文。
|
如果你对正则表达式没有任何的概念,那么推荐先阅读[《正则表达式30分钟入门教程》](),然后再阅读我们之前讲解在Python中如何使用正则表达式一文。
|
||||||
|
|
||||||
|
下面的例子演示了如何用正则表达式解析“豆瓣电影Top250”中的中文电影名称。
|
||||||
|
|
||||||
|
```Python
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
PATTERN = re.compile(r'<a[^>]*?>\s*<span class="title">(.*?)</span>')
|
||||||
|
|
||||||
|
for page in range(10):
|
||||||
|
resp = requests.get(
|
||||||
|
url=f'https://movie.douban.com/top250?start={page * 25}',
|
||||||
|
headers={
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) '
|
||||||
|
'AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||||
|
'Chrome/83.0.4103.97 Safari/537.36',
|
||||||
|
'Accept': 'text/html,application/xhtml+xml,application/xml;'
|
||||||
|
'q=0.9,image/webp,image/apng,*/*;'
|
||||||
|
'q=0.8,application/signed-exchange;v=b3;q=0.9',
|
||||||
|
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
items = PATTERN.findall(resp.text)
|
||||||
|
for item in items:
|
||||||
|
print(item)
|
||||||
|
time.sleep(random.randint(1, 5))
|
||||||
|
```
|
||||||
|
|
||||||
#### XPath解析和lxml
|
#### XPath解析和lxml
|
||||||
|
|
||||||
|
@ -196,13 +270,37 @@ XPath还支持通配符用法,如下所示。
|
||||||
| //title \| //price | 选取文档中的所有 title 和 price 元素。 |
|
| //title \| //price | 选取文档中的所有 title 和 price 元素。 |
|
||||||
| /bookstore/book/title \| //price | 选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。 |
|
| /bookstore/book/title \| //price | 选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。 |
|
||||||
|
|
||||||
> 说明:上面的例子来自于菜鸟教程网站上[XPath教程](<https://www.runoob.com/xpath/xpath-tutorial.html>),有兴趣的读者可以自行阅读原文。
|
> **说明**:上面的例子来自于菜鸟教程网站上[XPath教程](<https://www.runoob.com/xpath/xpath-tutorial.html>),有兴趣的读者可以自行阅读原文。
|
||||||
|
|
||||||
当然,如果不理解或者不太熟悉XPath语法,可以在Chrome浏览器中按照如下所示的方法查看元素的XPath语法。
|
当然,如果不理解或者不太熟悉XPath语法,可以在Chrome浏览器中按照如下所示的方法查看元素的XPath语法。
|
||||||
|
|
||||||
![](./res/douban-xpath.png)
|
![](./res/douban-xpath.png)
|
||||||
|
|
||||||
|
下面的例子演示了如何用XPath解析“豆瓣电影Top250”中的中文电影名称。
|
||||||
|
|
||||||
|
```Python
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
for page in range(10):
|
||||||
|
resp = requests.get(
|
||||||
|
url=f'https://movie.douban.com/top250?start={page * 25}',
|
||||||
|
headers={
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) '
|
||||||
|
'AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||||
|
'Chrome/83.0.4103.97 Safari/537.36',
|
||||||
|
'Accept': 'text/html,application/xhtml+xml,application/xml;'
|
||||||
|
'q=0.9,image/webp,image/apng,*/*;'
|
||||||
|
'q=0.8,application/signed-exchange;v=b3;q=0.9',
|
||||||
|
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
html = etree.HTML(resp.text)
|
||||||
|
spans = html.xpath('/html/body/div[3]/div[1]/div/div[1]/ol/li/div/div[2]/div[1]/a/span[1]')
|
||||||
|
for span in spans:
|
||||||
|
print(span.text)
|
||||||
|
```
|
||||||
|
|
||||||
### BeautifulSoup的使用
|
### BeautifulSoup的使用
|
||||||
|
|
||||||
|
@ -219,42 +317,63 @@ BeautifulSoup是一个可以从HTML或XML文件中提取数据的Python库。它
|
||||||
- find / find_all
|
- find / find_all
|
||||||
- select_one / select
|
- select_one / select
|
||||||
|
|
||||||
> 说明:更多内容可以参考BeautifulSoup的[官方文档](https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html)。
|
> **说明**:更多内容可以参考BeautifulSoup的[官方文档](https://beautifulsoup.readthedocs.io/zh_CN/v4.4.0/)。
|
||||||
|
|
||||||
### PyQuery的使用
|
下面的例子演示了如何用CSS选择器解析“豆瓣电影Top250”中的中文电影名称。
|
||||||
|
|
||||||
pyquery相当于jQuery的Python实现,可以用于解析HTML网页。
|
|
||||||
|
|
||||||
### 实例 - 获取知乎发现上的问题链接
|
|
||||||
|
|
||||||
```Python
|
```Python
|
||||||
from urllib.parse import urljoin
|
import random
|
||||||
|
import time
|
||||||
|
|
||||||
import re
|
import bs4
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
for page in range(10):
|
||||||
|
resp = requests.get(
|
||||||
|
url=f'https://movie.douban.com/top250?start={page * 25}',
|
||||||
|
headers={
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) '
|
||||||
|
'AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||||
|
'Chrome/83.0.4103.97 Safari/537.36',
|
||||||
|
'Accept': 'text/html,application/xhtml+xml,application/xml;'
|
||||||
|
'q=0.9,image/webp,image/apng,*/*;'
|
||||||
|
'q=0.8,application/signed-exchange;v=b3;q=0.9',
|
||||||
|
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
soup = bs4.BeautifulSoup(resp.text, 'lxml')
|
||||||
|
elements = soup.select('.info>div>a')
|
||||||
|
for element in elements:
|
||||||
|
span = element.select_one('.title')
|
||||||
|
print(span.text)
|
||||||
|
time.sleep(random.random() * 5)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 例子 - 获取知乎发现上的问题链接
|
||||||
|
|
||||||
|
```Python
|
||||||
|
import re
|
||||||
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
|
import bs4
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
headers = {'user-agent': 'Baiduspider'}
|
headers = {'user-agent': 'Baiduspider'}
|
||||||
proxies = {
|
|
||||||
'http': 'http://122.114.31.177:808'
|
|
||||||
}
|
|
||||||
base_url = 'https://www.zhihu.com/'
|
base_url = 'https://www.zhihu.com/'
|
||||||
seed_url = urljoin(base_url, 'explore')
|
resp = requests.get(urljoin(base_url, 'explore'), headers=headers)
|
||||||
resp = requests.get(seed_url,
|
soup = bs4.BeautifulSoup(resp.text, 'lxml')
|
||||||
headers=headers,
|
|
||||||
proxies=proxies)
|
|
||||||
soup = BeautifulSoup(resp.text, 'lxml')
|
|
||||||
href_regex = re.compile(r'^/question')
|
href_regex = re.compile(r'^/question')
|
||||||
link_set = set()
|
links_set = set()
|
||||||
for a_tag in soup.find_all('a', {'href': href_regex}):
|
for a_tag in soup.find_all('a', {'href': href_regex}):
|
||||||
if 'href' in a_tag.attrs:
|
if 'href' in a_tag.attrs:
|
||||||
href = a_tag.attrs['href']
|
href = a_tag.attrs['href']
|
||||||
full_url = urljoin(base_url, href)
|
full_url = urljoin(base_url, href)
|
||||||
link_set.add(full_url)
|
links_set.add(full_url)
|
||||||
print('Total %d question pages found.' % len(link_set))
|
print('Total %d question pages found.' % len(links_set))
|
||||||
|
print(links_set)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -2,60 +2,112 @@
|
||||||
|
|
||||||
### 存储海量数据
|
### 存储海量数据
|
||||||
|
|
||||||
数据持久化的首选方案应该是关系型数据库,关系型数据库的产品很多,包括:Oracle、MySQL、SQLServer、PostgreSQL等。如果要存储海量的低价值数据,文档数据库也是不错的选择,MongoDB是文档数据库中的佼佼者,之前我们已经讲解过MongDB的相关知识,在此不再进行赘述。
|
数据持久化的首选方案应该是关系型数据库,关系型数据库的产品很多,包括:Oracle、MySQL、SQLServer、PostgreSQL等。如果要存储海量的低价值数据,文档数据库也是不错的选择,MongoDB是文档数据库中的佼佼者,有兴趣的读者可以自行研究。
|
||||||
|
|
||||||
### 数据缓存
|
下面的代码演示了如何使用MySQL来保存从知乎发现上爬取到的链接和页面。
|
||||||
|
|
||||||
通过[《网络数据采集和解析》](./02.数据采集和解析.md)一文,我们已经知道了如何从指定的页面中抓取数据,以及如何保存抓取的结果,但是我们没有考虑过这么一种情况,就是我们可能需要从已经抓取过的页面中提取出更多的数据,重新去下载这些页面对于规模不大的网站倒是问题也不大,但是如果能够把这些页面缓存起来,对应用的性能会有明显的改善。可以使用Redis来提供高速缓存服务,关于Redis的知识,我们在[《NoSQL入门》](../Day36-40/NoSQL入门.md)一文中已经做过简要的介绍。
|
```SQL
|
||||||
|
create database zhihu default charset utf8;
|
||||||
|
create user 'hellokitty'@'%' identified by 'Hellokitty.618';
|
||||||
|
grant all privileges on zhihu.* to 'hellokitty'@'%';
|
||||||
|
flush privileges;
|
||||||
|
|
||||||
### 实例 - 缓存知乎发现上的链接和页面代码
|
use zhihu;
|
||||||
|
create table `tb_explore`
|
||||||
|
(
|
||||||
|
`id` integer auto_increment,
|
||||||
|
`url` varchar(1024) not null,
|
||||||
|
`page` longblob not null,
|
||||||
|
`digest` char(48) unique not null,
|
||||||
|
`idate` datetime default now(),
|
||||||
|
primary key (`id`)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
```Python
|
```Python
|
||||||
from hashlib import sha1
|
import hashlib
|
||||||
from urllib.parse import urljoin
|
|
||||||
|
|
||||||
import pickle
|
import pickle
|
||||||
import re
|
import re
|
||||||
import requests
|
|
||||||
import zlib
|
import zlib
|
||||||
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
import MySQLdb
|
||||||
from redis import Redis
|
import bs4
|
||||||
|
import requests
|
||||||
|
|
||||||
|
conn = MySQLdb.connect(host='1.2.3.4', port=3306,
|
||||||
|
user='hellokitty', password='Hellokitty.618',
|
||||||
|
database='zhihu', charset='utf8',
|
||||||
|
autocommit=True)
|
||||||
|
|
||||||
|
|
||||||
|
def write_to_db(url, page, digest):
|
||||||
|
try:
|
||||||
|
with conn.cursor() as cursor:
|
||||||
|
cursor.execute(
|
||||||
|
'insert into tb_explore (url, page, digest) values (%s, %s, %s) ',
|
||||||
|
(url, page, digest)
|
||||||
|
)
|
||||||
|
except MySQLdb.MySQLError as err:
|
||||||
|
print(err)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# 指定种子页面
|
|
||||||
base_url = 'https://www.zhihu.com/'
|
base_url = 'https://www.zhihu.com/'
|
||||||
seed_url = urljoin(base_url, 'explore')
|
seed_url = urljoin(base_url, 'explore')
|
||||||
# 创建Redis客户端
|
|
||||||
client = Redis(host='1.2.3.4', port=6379, password='1qaz2wsx')
|
|
||||||
# 设置用户代理(否则访问会被拒绝)
|
|
||||||
headers = {'user-agent': 'Baiduspider'}
|
headers = {'user-agent': 'Baiduspider'}
|
||||||
# 通过requests模块发送GET请求并指定用户代理
|
try:
|
||||||
resp = requests.get(seed_url, headers=headers)
|
resp = requests.get(seed_url, headers=headers)
|
||||||
# 创建BeautifulSoup对象并指定使用lxml作为解析器
|
soup = bs4.BeautifulSoup(resp.text, 'lxml')
|
||||||
soup = BeautifulSoup(resp.text, 'lxml')
|
|
||||||
href_regex = re.compile(r'^/question')
|
href_regex = re.compile(r'^/question')
|
||||||
# 将URL处理成SHA1摘要(长度固定更简短)
|
|
||||||
hasher_proto = sha1()
|
|
||||||
# 查找所有href属性以/question打头的a标签
|
|
||||||
for a_tag in soup.find_all('a', {'href': href_regex}):
|
for a_tag in soup.find_all('a', {'href': href_regex}):
|
||||||
# 获取a标签的href属性值并组装完整的URL
|
|
||||||
href = a_tag.attrs['href']
|
href = a_tag.attrs['href']
|
||||||
full_url = urljoin(base_url, href)
|
full_url = urljoin(base_url, href)
|
||||||
# 传入URL生成SHA1摘要
|
digest = hashlib.sha1(full_url.encode()).hexdigest()
|
||||||
hasher = hasher_proto.copy()
|
|
||||||
hasher.update(full_url.encode('utf-8'))
|
|
||||||
field_key = hasher.hexdigest()
|
|
||||||
# 如果Redis的键'zhihu'对应的hash数据类型中没有URL的摘要就访问页面并缓存
|
|
||||||
if not client.hexists('zhihu', field_key):
|
|
||||||
html_page = requests.get(full_url, headers=headers).text
|
html_page = requests.get(full_url, headers=headers).text
|
||||||
# 对页面进行序列化和压缩操作
|
|
||||||
zipped_page = zlib.compress(pickle.dumps(html_page))
|
zipped_page = zlib.compress(pickle.dumps(html_page))
|
||||||
# 使用hash数据类型保存URL摘要及其对应的页面代码
|
write_to_db(full_url, zipped_page, digest)
|
||||||
client.hset('zhihu', field_key, zipped_page)
|
finally:
|
||||||
# 显示总共缓存了多少个页面
|
conn.close()
|
||||||
print('Total %d question pages found.' % client.hlen('zhihu'))
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 数据缓存
|
||||||
|
|
||||||
|
通过[《网络数据采集和解析》](./67.数据采集和解析.md)一文,我们已经知道了如何从指定的页面中抓取数据,以及如何保存抓取的结果,但是我们没有考虑过这么一种情况,就是我们可能需要从已经抓取过的页面中提取出更多的数据,重新去下载这些页面对于规模不大的网站倒是问题也不大,但是如果能够把这些页面缓存起来,对应用的性能会有明显的改善。下面的例子演示了如何使用Redis来缓存知乎发现上的页面。
|
||||||
|
|
||||||
|
```Python
|
||||||
|
import hashlib
|
||||||
|
import pickle
|
||||||
|
import re
|
||||||
|
import zlib
|
||||||
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
|
import bs4
|
||||||
|
import redis
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
base_url = 'https://www.zhihu.com/'
|
||||||
|
seed_url = urljoin(base_url, 'explore')
|
||||||
|
client = redis.Redis(host='1.2.3.4', port=6379, password='1qaz2wsx')
|
||||||
|
headers = {'user-agent': 'Baiduspider'}
|
||||||
|
resp = requests.get(seed_url, headers=headers)
|
||||||
|
soup = bs4.BeautifulSoup(resp.text, 'lxml')
|
||||||
|
href_regex = re.compile(r'^/question')
|
||||||
|
for a_tag in soup.find_all('a', {'href': href_regex}):
|
||||||
|
href = a_tag.attrs['href']
|
||||||
|
full_url = urljoin(base_url, href)
|
||||||
|
field_key = hashlib.sha1(full_url.encode()).hexdigest()
|
||||||
|
if not client.hexists('spider:zhihu:explore', field_key):
|
||||||
|
html_page = requests.get(full_url, headers=headers).text
|
||||||
|
zipped_page = zlib.compress(pickle.dumps(html_page))
|
||||||
|
client.hset('spider:zhihu:explore', field_key, zipped_page)
|
||||||
|
print('Total %d question pages found.' % client.hlen('spider:zhihu:explore'))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
## 并发下载
|
## 并发下载
|
||||||
|
|
||||||
### 多线程和多进程回顾
|
### 多线程和多进程补充知识点
|
||||||
|
|
||||||
在前面的[《进程和线程》](../Day01-15/Day13/进程和线程.md)一文中,我们已经对在Python中使用多进程和多线程实现并发编程进行了简明的讲解,在此我们补充几个知识点。
|
|
||||||
|
|
||||||
#### threading.local类
|
#### threading.local类
|
||||||
|
|
||||||
使用线程时最不愿意遇到的情况就是多个线程竞争资源,在这种情况下为了保证资源状态的正确性,我们可能需要对资源进行加锁保护的处理,这一方面会导致程序失去并发性,另外如果多个线程竞争多个资源时,还有可能因为加锁方式的不当导致[死锁](https://zh.wikipedia.org/wiki/%E6%AD%BB%E9%94%81)。要解决多个线程竞争资源的问题,其中一个方案就是让每个线程都持有资源的副本(拷贝),这样每个线程可以操作自己所持有的资源,从而规避对资源的竞争。
|
使用线程时最不愿意遇到的情况就是多个线程竞争资源,在这种情况下为了保证资源状态的正确性,我们可能需要对资源进行加锁保护的处理,这一方面会导致程序失去并发性,另外如果多个线程竞争多个资源时,还有可能因为加锁方式的不当导致[死锁](https://zh.wikipedia.org/wiki/%E6%AD%BB%E9%94%81)。要解决多个线程竞争资源的问题,其中一个方案就是让每个线程都持有资源的副本(拷贝),这样每个线程可以操作自己所持有的资源,从而规避对资源的竞争。
|
||||||
|
|
||||||
要实现将资源和持有资源的线程进行绑定的操作,最简单的做法就是使用threading模块的local类,在网络爬虫开发中,就可以使用local类为每个线程绑定一个MySQL数据库连接或Redis客户端对象,这样通过线程可以直接获得这些资源,既解决了资源竞争的问题,又避免了在函数和方法调用时传递这些资源。具体的请参考本章多线程爬取“手机搜狐网”(Redis版)的实例代码。
|
要实现将资源和持有资源的线程进行绑定的操作,最简单的做法就是使用`threading`模块的`local`类,在网络爬虫开发中,就可以使用`local`类为每个线程绑定一个MySQL数据库连接或Redis客户端对象,这样通过线程可以直接获得这些资源,既解决了资源竞争的问题,又避免了在函数和方法调用时传递这些资源。具体的请参考本章多线程爬取“手机搜狐网”(Redis版)的实例代码。
|
||||||
|
|
||||||
#### concurrent.futures模块
|
#### concurrent.futures模块
|
||||||
|
|
||||||
|
@ -29,7 +27,7 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池
|
||||||
1. 执行效率极高,因为子程序(函数)切换不是线程切换,由程序自身控制,没有切换线程的开销。
|
1. 执行效率极高,因为子程序(函数)切换不是线程切换,由程序自身控制,没有切换线程的开销。
|
||||||
2. 不需要多线程的锁机制,因为只有一个线程,也不存在竞争资源的问题,当然也就不需要对资源加锁保护,因此执行效率高很多。
|
2. 不需要多线程的锁机制,因为只有一个线程,也不存在竞争资源的问题,当然也就不需要对资源加锁保护,因此执行效率高很多。
|
||||||
|
|
||||||
> 说明:协程适合处理的是I/O密集型任务,处理CPU密集型任务并不是它的长处,如果要提升CPU的利用率可以考虑“多进程+协程”的模式。
|
> **说明**:协程适合处理的是I/O密集型任务,处理CPU密集型任务并不是它擅长的,如果要提升CPU的利用率可以考虑“多进程+多线程”或者“多进程+协程”的工作模式。
|
||||||
|
|
||||||
#### 历史回顾
|
#### 历史回顾
|
||||||
|
|
||||||
|
@ -39,142 +37,7 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池
|
||||||
4. Python 3.4:引入`asyncio.coroutine`装饰器用来标记作为协程的函数,协程函数和`asyncio`及其事件循环一起使用,来实现异步I/O操作。
|
4. Python 3.4:引入`asyncio.coroutine`装饰器用来标记作为协程的函数,协程函数和`asyncio`及其事件循环一起使用,来实现异步I/O操作。
|
||||||
5. Python 3.5:引入了`async`和`await`,可以使用`async def`来定义一个协程函数,这个函数中不能包含任何形式的`yield`语句,但是可以使用`return`或`await`从协程中返回值。
|
5. Python 3.5:引入了`async`和`await`,可以使用`async def`来定义一个协程函数,这个函数中不能包含任何形式的`yield`语句,但是可以使用`return`或`await`从协程中返回值。
|
||||||
|
|
||||||
#### 示例代码
|
协程实现了协作式并发,通过提高CPU的利用率来达到改善性能的目的。著名的三方库[`aiohttp`](https://github.com/aio-libs/aiohttp)就是通过协程的方式实现了HTTP客户端和HTTP服务器的功能,较之`requests`有更好的获取数据的性能,有兴趣可以阅读它的[官方文档](https://aiohttp.readthedocs.io/en/stable/)。
|
||||||
|
|
||||||
1. 生成器 - 数据的生产者。
|
|
||||||
|
|
||||||
```Python
|
|
||||||
from time import sleep
|
|
||||||
|
|
||||||
|
|
||||||
# 倒计数生成器
|
|
||||||
def countdown(n):
|
|
||||||
while n > 0:
|
|
||||||
yield n
|
|
||||||
n -= 1
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
for num in countdown(5):
|
|
||||||
print(f'Countdown: {num}')
|
|
||||||
sleep(1)
|
|
||||||
print('Countdown Over!')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
生成器还可以叠加来组成生成器管道,代码如下所示。
|
|
||||||
|
|
||||||
```Python
|
|
||||||
# Fibonacci数生成器
|
|
||||||
def fib():
|
|
||||||
a, b = 0, 1
|
|
||||||
while True:
|
|
||||||
a, b = b, a + b
|
|
||||||
yield a
|
|
||||||
|
|
||||||
|
|
||||||
# 偶数生成器
|
|
||||||
def even(gen):
|
|
||||||
for val in gen:
|
|
||||||
if val % 2 == 0:
|
|
||||||
yield val
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
gen = even(fib())
|
|
||||||
for _ in range(10):
|
|
||||||
print(next(gen))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
```
|
|
||||||
|
|
||||||
2. 协程 - 数据的消费者。
|
|
||||||
|
|
||||||
```Python
|
|
||||||
from time import sleep
|
|
||||||
|
|
||||||
|
|
||||||
# 生成器 - 数据生产者
|
|
||||||
def countdown_gen(n, consumer):
|
|
||||||
consumer.send(None)
|
|
||||||
while n > 0:
|
|
||||||
consumer.send(n)
|
|
||||||
n -= 1
|
|
||||||
consumer.send(None)
|
|
||||||
|
|
||||||
|
|
||||||
# 协程 - 数据消费者
|
|
||||||
def countdown_con():
|
|
||||||
while True:
|
|
||||||
n = yield
|
|
||||||
if n:
|
|
||||||
print(f'Countdown {n}')
|
|
||||||
sleep(1)
|
|
||||||
else:
|
|
||||||
print('Countdown Over!')
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
countdown_gen(5, countdown_con())
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
```
|
|
||||||
|
|
||||||
> 说明:上面代码中countdown_gen函数中的第1行consumer.send(None)是为了激活生成器,通俗的说就是让生成器执行到有yield关键字的地方挂起,当然也可以通过next(consumer)来达到同样的效果。如果不愿意每次都用这样的代码来“预激”生成器,可以写一个包装器来完成该操作,代码如下所示。
|
|
||||||
|
|
||||||
```Python
|
|
||||||
from functools import wraps
|
|
||||||
|
|
||||||
|
|
||||||
def coroutine(fn):
|
|
||||||
|
|
||||||
@wraps(fn)
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
gen = fn(*args, **kwargs)
|
|
||||||
next(gen)
|
|
||||||
return gen
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
```
|
|
||||||
|
|
||||||
这样就可以使用`@coroutine`装饰器对协程进行预激操作,不需要再写重复代码来激活协程。
|
|
||||||
|
|
||||||
3. 异步I/O - 非阻塞式I/O操作。
|
|
||||||
|
|
||||||
```Python
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
|
||||||
def countdown(name, n):
|
|
||||||
while n > 0:
|
|
||||||
print(f'Countdown[{name}]: {n}')
|
|
||||||
yield from asyncio.sleep(1)
|
|
||||||
n -= 1
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
tasks = [
|
|
||||||
countdown("A", 10), countdown("B", 5),
|
|
||||||
]
|
|
||||||
loop.run_until_complete(asyncio.wait(tasks))
|
|
||||||
loop.close()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
```
|
|
||||||
|
|
||||||
4. `async`和`await`。
|
|
||||||
|
|
||||||
```Python
|
```Python
|
||||||
import asyncio
|
import asyncio
|
||||||
|
@ -184,9 +47,9 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池
|
||||||
async def download(url):
|
async def download(url):
|
||||||
print('Fetch:', url)
|
print('Fetch:', url)
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(url) as resp:
|
async with session.get(url, ssl=False) as resp:
|
||||||
print(url, '--->', resp.status)
|
print(url, '--->', resp.status)
|
||||||
print(url, '--->', resp.cookies)
|
print(url, '--->', resp.headers)
|
||||||
print('\n\n', await resp.text())
|
print('\n\n', await resp.text())
|
||||||
|
|
||||||
|
|
||||||
|
@ -197,7 +60,7 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池
|
||||||
'http://www.sohu.com/',
|
'http://www.sohu.com/',
|
||||||
'http://www.sina.com.cn/',
|
'http://www.sina.com.cn/',
|
||||||
'https://www.taobao.com/',
|
'https://www.taobao.com/',
|
||||||
'https://www.jd.com/'
|
'http://jd.com/'
|
||||||
]
|
]
|
||||||
tasks = [download(url) for url in urls]
|
tasks = [download(url) for url in urls]
|
||||||
loop.run_until_complete(asyncio.wait(tasks))
|
loop.run_until_complete(asyncio.wait(tasks))
|
||||||
|
@ -208,8 +71,6 @@ Python3.2带来了`concurrent.futures` 模块,这个模块包含了线程池
|
||||||
main()
|
main()
|
||||||
```
|
```
|
||||||
|
|
||||||
上面的代码使用了[AIOHTTP](https://github.com/aio-libs/aiohttp)这个非常著名的第三方库,它实现了HTTP客户端和HTTP服务器的功能,对异步操作提供了非常好的支持,有兴趣可以阅读它的[官方文档](https://aiohttp.readthedocs.io/en/stable/)。
|
|
||||||
|
|
||||||
### 实例 - 多线程爬取“手机搜狐网”所有页面
|
### 实例 - 多线程爬取“手机搜狐网”所有页面
|
||||||
|
|
||||||
下面我们把之间讲的所有知识结合起来,用面向对象的方式实现一个爬取“手机搜狐网”的多线程爬虫。
|
下面我们把之间讲的所有知识结合起来,用面向对象的方式实现一个爬取“手机搜狐网”的多线程爬虫。
|
||||||
|
|
|
@ -79,4 +79,4 @@ selenium.common.exceptions.WebDriverException: Message: 'chromedriver' executabl
|
||||||
export PATH=$PATH:/Users/Hao/Downloads/Tools/chromedriver/
|
export PATH=$PATH:/Users/Hao/Downloads/Tools/chromedriver/
|
||||||
```
|
```
|
||||||
|
|
||||||
其中`/Users/Hao/Downloads/Tools/chromedriver/ `就是chromedriver所在的路径。
|
其中`/Users/Hao/Downloads/Tools/chromedriver/ `就是chromedriver所在的路径。当然,更为简单的办法是把chromedriver直接放在虚拟环境中,跟Python解释器位于同一个路径下就可以了。
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
## 爬虫项目实战
|
|