django文档8
### 8.4.2、api接口
应用程序编程接口(Application Programming Interface,API接口),就是应用程序对外提供了一个操作数据的入口,这个入口可以是一个函数或类方法,也可以是一个url地址或者一个网络地址。当客户端调用这个入口,应用程序则会执行对应代码操作,给客户端完成相对应的功能。
当然,api接口在工作中是比较常见的开发内容,有时候,我们会调用其他人编写的api接口,有时候,我们也需要提供api接口给其他人操作。由此就会带来一个问题,api接口往往都是一个函数、类方法、或者url或其他网络地址,不断是哪一种,当api接口编写过程中,我们都要考虑一个问题就是这个接口应该怎么编写?接口怎么写的更加容易维护和清晰,这就需要大家在调用或者编写api接口的时候要有一个明确的编写规范!!!
为了在团队内部形成共识、防止个人习惯差异引起的混乱,我们都需要找到一种大家都觉得很好的接口实现规范,而且这种规范能够让后端写的接口,用途一目了然,减少客户端和服务端双方之间的合作成本。
目前市面上大部分公司开发人员使用的接口实现规范主要有:restful、RPC。
RPC( Remote Procedure Call ): 翻译成中文:远程过程调用[远程服务调用]. 从字面上理解就是访问/调用远程服务端提供的api接口。这种接口一般以服务或者过程式代码提供。
- 服务端提供一个**唯一的访问入口地址**:http://api.xxx.com/ 或 http://www.xx.com/api 或者基于其他协议的地址
- 客户端请求服务端的时候,所有的操作都理解为动作(action),一般web开发时,对应的就是HTTP请求的post请求
- 通过**请求体**参数,指定要调用的接口名称和接口所需的参数
action=get_all_student&class=301&sex=1
m=get_all_student&sex=1&age=22
command=100&sex=1&age=22
rpc接口多了,对应函数名和参数就多了,前端在请求api接口时难找.对于年代久远的rpc服务端的代码也容易出现重复的接口
restful: 翻译成中文: 资源状态转换.(表征性状态转移)
- 把服务端提供的所有的数据/文件都看成资源, 那么通过api接口请求数据的操作,本质上来说就是对资源的操作了.
因此,restful中要求,我们把当前接口对外提供哪种资源进行操作,就把**资源的名称写在url地址**。
- web开发中操作资源,最常见的最通用的无非就是增删查改,所以restful要求在地址栏中声明要操作的资源是什么。然后通过**http请求动词**来说明对该资源进行哪一种操作.
> POST http://www.xxx.com/api/students/ 添加学生数据
>
> GET http://www.xxx.com/api/students/ 获取所有学生
>
> GET http://www.xxx.com/api/students/1/ 获取id=pk的学生
>
> DELETE http://www.xxx.com/api/students/1/ 删除id=pk的一个学生
>
> PUT http://www.xxx.com/api/students/1/ 修改一个学生的全部信息 [id,name,sex,age,]
>
> PATCH http://www.xxx.com/api/students/1/ 修改一个学生的部分信息[age]
也就是说,我们仅需要通过url地址上的资源名称结合HTTP请求动作,就可以说明当前api接口的功能是什么了。restful是以资源为主的api接口规范,体现在地址上就是资源就是以名词表达。rpc则以动作为主的api接口规范,体现在接口名称上往往附带操作数据的动作。
### 8.4.3、RESTful API规范

REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征)性状态转移。 它首次出现在2000年Roy Fielding的博士论文中。
RESTful是一种专门为Web 开发而定义API接口的设计风格,尤其适用于前后端分离的应用模式中。
这种风格的理念认为后端开发任务就是提供数据的,对外提供的是数据资源的访问接口,所以在定义接口时,客户端访问的URL路径就表示这种要操作的数据资源。
而对于数据资源分别使用POST、DELETE、GET、UPDATE等请求动作来表达对数据的增删查改。
| GET | /students | 获取所有学生 |
| :------- | :--------- | :----------------- |
| 请求方法 | 请求地址 | 后端操作 |
| POST | /students | 增加学生 |
| GET | /students/ | 获取编号为pk的学生 |
| PUT | /students/ | 修改编号为pk的学生 |
| DELETE | /students/ | 删除编号为pk的学生 |
restful规范是一种通用的规范,不限制语言和开发框架的使用。事实上,我们可以使用任何一门语言,任何一个框架都可以实现符合restful规范的API接口。
参考文档:http://www.runoob.com/w3cnote/restful-architecture.html
接口实现过程中,会存在**幂等性**。所谓幂等性是指代**客户端发起多次同样请求时,是否对于服务端里面的资源产生不同结果**。如果**多次请求**,服务端**结果**还是**一样**,则属于**幂等接口**,如果多次请求,服务端产生结果是不一样的,则属于**非幂等接口**。
| 请求方式 | 是否幂等 | 是否安全 |
| :-------- | :------- | :------- |
| GET | 幂等 | 安全 |
| POST | 不幂等 | 不安全 |
| PUT/PATCH | 幂等 | 不安全 |
| DELETE | 幂等 | 不安全 |
### 8.4.4、CBV使用
之前我们用的视图函数叫FBV(也就是函数型视图函数),这里我们来试试CBV(类视图函数)的写法。类视图函数可以让代码看起来更简洁,用起来更方便。
```python
# FBV
# def index(request):
# if request.method == "GET":
#
# return HttpResponse("GET")
# elif request.method == "POST":
#
# return HttpResponse("POST")
#
# elif request.method == "DELETE":
# return HttpResponse("DELETE")
# CBV模式: 基于restful开发
class IndexView(View):
def get(self, request):
return HttpResponse("CBV GET")
def post(self, request):
return HttpResponse("CBV POST")
class BookView(View):
def get(self, request):
# 获取数据
book_list = Book.objects.all()
# 序列化:json
data_json = serializers.serialize("json", book_list)
return HttpResponse(data_json, content_type="json")
```
```python
# FBV模式
# path('index/', views.index),
# CBV模式
path("index/",views.IndexView.as_view()),
path("books/",views.BookView.as_view())
```
## 8.5、csrftoken(跨站请求伪造)
CSRF(Cross-Site Request Forgery,跨站点伪造请求)是一种网络攻击方式,该攻击可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击站点,从而在未授权的情况下执行在权限保护之下的操作,具有很大的危害性。具体来讲,可以这样理解CSRF攻击:攻击者盗用了你的身份,以你的名义发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作,比如以你的名义发送邮件、发消息,盗取你的账号,添加系统管理员,甚至于购买商品、虚拟货币转账等。
`````
-- 企业邮箱
-- pornhub.com
`````
[参考文章](https://www.pianshen.com/article/62531249043/)
token其实就是一个令牌,用于用户验证的,token的诞生离不开CSRF。正是由于上面的Cookie/Session的状态保持方式会出现CSRF,所以才有了token。
- 解除中间件注释

- 无csrf_token数据的post请求

### 8.5.1、基本使用
#### 一、form表单提交
在html页面form表单中直接添加{% csrf_token%}

#### 二、ajax提交
方式1:放在请求数据中。
```js
$.ajax({
url: '/csrf_test/',
method: 'post',
data: {'name': $('[name="name"]').val(),
'password': $('[name="password"]').val(),
'csrfmiddlewaretoken':$('[name="csrfmiddlewaretoken"]').val()
},
success: function (data) {
console.log('成功了')
console.log(data) },
})
```
方式2:放在请求头
```js
$.ajax({
url: '/csrf_test/',
method: 'post',
headers:{'X-CSRFToken':'token值'}, // 注意放到引号里面
data:{}
}
```
### 8.5.2、全局使用,局部禁csrf
#### (1) 在视图函数上加装饰器
```python
from django.views.decorators.csrf import csrf_exempt,csrf_protect
@csrf_exempt
def 函数名(request): # 加上装饰器后,这个视图函数,就没有csrf校验了
```
#### (2) 视图类
```python
from django.views.decorators.csrf import csrf_exempt,csrf_protect
from django.utils.decorators import method_decorator
@method_decorator(csrf_exempt,name='dispatch')
class index(View):
def get(self,request):
return HttpResponse("GET")
def post(self,request):
return HttpResponse("POST")
```
## 8.6、ORM进阶
### 8.6.1、queryset特性
#### (1)可切片
使用Python 的切片语法来限制`查询集`记录的数目 。它等同于SQL 的`LIMIT` 和`OFFSET` 子句。
```python
>>> Article.objects.all()[:5] # (LIMIT 5)
>>> Article.objects.all()[5:10] # (OFFSET 5 LIMIT 5)
```
不支持负的索引(例如`Article.objects.all()[-1]`)。通常,`查询集` 的切片返回一个新的`查询集` —— 它不会执行查询。
#### (2)可迭代
```python
articleList=models.Article.objects.all()
for article in articleList:
print(article.title)
```
#### (3)惰性查询
`查询集` 是惰性执行的 —— 创建`查询集`不会带来任何数据库的访问。你可以将过滤器保持一整天,直到`查询集` 需要求值时,Django 才会真正运行这个查询。
```python
queryResult=models.Article.objects.all() # not hits database
print(queryResult) # hits database
for article in queryResult:
print(article.title) # hits database
```
一般来说,只有在“请求”`查询集` 的结果时才会到数据库中去获取它们。当你确实需要结果时,`查询集` 通过访问数据库来*求值*。 关于求值发生的准确时间。
#### (4)缓存机制
每个`查询集`都包含一个缓存来最小化对数据库的访问。理解它是如何工作的将让你编写最高效的代码。
在一个新创建的`查询集`中,缓存为空。首次对`查询集`进行求值 —— 同时发生数据库查询 ——Django 将保存查询的结果到`查询集`的缓存中并返回明确请求的结果(例如,如果正在迭代`查询集`,则返回下一个结果)。接下来对该`查询集` 的求值将重用缓存的结果。
请牢记这个缓存行为,因为对`查询集`使用不当的话,它会坑你的。例如,下面的语句创建两个`查询集`,对它们求值,然后扔掉它们:
```python
queryset = Book.objects.all()
print(queryset) # hit database
print(queryset) # hit database
```
> 注:简单地打印查询集不会填充缓存。
这意味着相同的数据库查询将执行两次,显然倍增了你的数据库负载。同时,还有可能两个结果列表并不包含相同的数据库记录,因为在两次请求期间有可能有Article被添加进来或删除掉。为了避免这个问题,只需保存`查询集`并重新使用它:
```python
queryset = Book.objects.all()
ret = [i for i in queryset] # hit database
print(queryset) # 使用缓存
print(queryset) # 使用缓存
```
何时查询集会被缓存?
> 1. 遍历queryset时
> 2. if语句(为了避免这个,可以用exists()方法来检查是否有数据)
所以单独queryset的索引或者切片都不会缓存。
```python
queryset = Book.objects.all()
one = queryset[0] # hit database
two = queryset[1] # hit database
print(one)
print(two)
```
#### (5)exists()与iterator()方法
##### exists
简单的使用if语句进行判断也会完全执行整个queryset并且把数据放入cache,虽然你并不需要这些 数据!为了避免这个,可以用exists()方法来检查是否有数据:
```python
if queryResult.exists():
#SELECT (1) AS "a" FROM "blog_article" LIMIT 1; args=()
print("exists...")
```
##### iterator
当queryset非常巨大时,cache会成为问题。
处理成千上万的记录时,将它们一次装入内存是很浪费的。更糟糕的是,巨大的queryset可能会锁住系统 进程,让你的程序濒临崩溃。要避免在遍历数据的同时产生queryset cache,可以使用iterator()方法 来获取数据,处理完数据就将其丢弃。
```python
objs = Book.objects.all().iterator()
# iterator()可以一次只从数据库获取少量数据,这样可以节省内存
for obj in objs:
print(obj.title)
#BUT,再次遍历没有打印,因为迭代器已经在上一次遍历(next)到最后一次了,没得遍历了
for obj in objs:
print(obj.title)
```
当然,使用iterator()方法来防止生成cache,意味着遍历同一个queryset时会重复执行查询。所以使 #用iterator()的时候要当心,确保你的代码在操作一个大的queryset时没有重复执行查询。
queryset的cache是用于减少程序对数据库的查询,在通常的使用下会保证只有在需要的时候才会查询数据库。 使用exists()和iterator()方法可以优化程序对内存的使用。不过,由于它们并不会生成queryset cache,可能 会造成额外的数据库查询。
总结:在使用缓存机制还是生成器机制的选择上如果是,数据量大情况主要使用生成器;数据少使用次数多的情况使用缓存机制。
### 8.6.2、中介模型
处理类似搭配 pizza 和 topping 这样简单的多对多关系时,使用标准的`ManyToManyField` 就可以了。但是,有时你可能需要关联数据到两个模型之间的关系上。
例如,有这样一个应用,它记录音乐家所属的音乐小组。我们可以用一个`ManyToManyField` 表示小组和成员之间的多对多关系。但是,有时你可能想知道更多成员关系的细节,比如成员是何时加入小组的。
对于这些情况,Django 允许你指定一个中介模型来定义多对多关系。 你可以将其他字段放在中介模型里面。源模型的`ManyToManyField` 字段将使用`through` 参数指向中介模型。对于上面的音乐小组的例子,代码如下:
```python
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self): # __unicode__ on Python 2
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self): # __unicode__ on Python 2
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
```
既然你已经设置好`ManyToManyField` 来使用中介模型(在这个例子中就是`Membership`),接下来你要开始创建多对多关系。你要做的就是创建中介模型的实例:
```python
>>> ringo = Person.objects.create(name="Ringo Starr")
>>> paul = Person.objects.create(name="Paul McCartney")
>>> beatles = Group.objects.create(name="The Beatles")
>>> m1 = Membership(person=ringo, group=beatles,
... date_joined=date(1962, 8, 16),
... invite_reason="Needed a new drummer.")
>>> m1.save()
>>> beatles.members.all()
[<Person: Ringo Starr>]
>>> ringo.group_set.all()
[<Group: The Beatles>]
>>> m2 = Membership.objects.create(person=paul, group=beatles,
... date_joined=date(1960, 8, 1),
... invite_reason="Wanted to form a band.")
>>> beatles.members.all()
[<Person: Ringo Starr>, <Person: Paul McCartney>]
```
与普通的多对多字段不同,你不能使用`add`、 `create`和赋值语句(比如,`beatles.members = [...]`)来创建关系:
```python
# THIS WILL NOT WORK
>>> beatles.members.add(john)
# NEITHER WILL THIS
>>> beatles.members.create(name="George Harrison")
# AND NEITHER WILL THIS
>>> beatles.members = [john, paul, ringo, george]
```
为什么不能这样做? 这是因为你不能只创建 `Person`和 `Group`之间的关联关系,你还要指定 `Membership`模型中所需要的所有信息;而简单的`add`、`create` 和赋值语句是做不到这一点的。所以它们不能在使用中介模型的多对多关系中使用。此时,唯一的办法就是创建中介模型的实例。
`remove()`方法被禁用也是出于同样的原因。但是`clear()` 方法却是可用的。它可以清空某个实例所有的多对多关系:
```python
>>> # Beatles have broken up
>>> beatles.members.clear()
>>> # Note that this deletes the intermediate model instances
>>> Membership.objects.all()
[]
```
### 8.6.3、数据库表反向生成模型类
众所周知,Django较为适合原生开发,即通过该框架搭建一个全新的项目,通过在修改models.py来创建新的数据库表。但是往往有时候,我们需要利用到之前的已经设计好的数据库,数据库中提供了设计好的多种表单。那么这时如果我们再通过models.py再来设计就会浪费很多的时间。所幸Django为我们提供了inspecdb的方法。他的作用即使根据已经存在对的mysql数据库表来反向映射结构到models.py中.
我们在展示django ORM反向生成之前,我们先说一下怎么样正向生成代码。
> 正向生成,指的是先创建model.py文件,然后通过django内置的编译器,在数据库如mysql中创建出符合model.py的表。
>
> 反向生成,指的是先在数据库中create table,然后通过django内置的编译器,生成model代码。
```python
python manage.py inspectdb > models文件名
```
### 8.6.4、查询优化
#### (1)select_related()
对于一对一字段(OneToOneField)和外键字段(ForeignKey),可以使用select_related 来对QuerySet进行优化。
select_related 返回一个`QuerySet`,当执行它的查询时它沿着外键关系查询关联的对象的数据。它会生成一个复杂的查询并引起性能的损耗,但是在以后使用外键关系时将不需要数据库查询。
简单说,在对QuerySet使用select_related()函数后,Django会获取相应外键对应的对象,从而在之后需要的时候不必再查询数据库了。
下面的例子解释了普通查询和`select_related()` 查询的区别。
查询id=2的的书籍的出版社名称,下面是一个标准的查询:
```python
# Hits the database.
book= models.Book.objects.get(nid=2)
# Hits the database again to get the related Blog object.
print(book.publish.name)
```
如果我们使用select_related()函数:
```python
books=models.Book.objects.select_related("publish").all()
for book in books:
# Doesn't hit the database, because book.publish
# has been prepopulated in the previous query.
print(book.publish.name)
```
##### 多外键查询
这是针对publish的外键查询,如果是另外一个外键呢?让我们一起看下:
```python
book=models.Book.objects.select_related("publish").get(nid=1)
print(book.authors.all())
```
观察logging结果,发现依然需要查询两次,所以需要改为:
```python
book=models.Book.objects.select_related("publish","").get(nid=1)
print(book.publish)
```
或者:
```python
book=models.Article.objects
.select_related("publish")
.select_related("")
.get(nid=1) # django 1.7 支持链式操作
print(book.publish)
```
#### (2)prefetch_related()
对于多对多字段(ManyToManyField)和一对多字段,可以使用prefetch_related()来进行优化。
prefetch_related()和select_related()的设计目的很相似,都是为了减少SQL查询的数量,但是实现的方式不一样。后者是通过JOIN语句,在SQL查询内解决问题。但是对于多对多关系,使用SQL语句解决就显得有些不太明智,因为JOIN得到的表将会很长,会导致SQL语句运行时间的增加和内存占用的增加。若有n个对象,每个对象的多对多字段对应Mi条,就会生成Σ(n)Mi 行的结果表。
prefetch_related()的解决方法是,分别查询每个表,然后用Python处理他们之间的关系。
```python
# 查询所有文章关联的所有标签
books=models.Book.objects.all()
for book in books:
print(book.authors.all()) #4篇文章: hits database 5
```
改为prefetch_related:
```python
# 查询所有文章关联的所有标签
books=models.Book.objects.prefetch_related("authors").all()
for book in books:
print(book.authors.all()) #4篇文章: hits database 2
```
#### (3)extra
```python
extra(select=None, where=None, params=None,
tables=None, order_by=None, select_params=None)
```
有些情况下,Django的查询语法难以简单的表达复杂的 `WHERE` 子句,对于这种情况, Django 提供了 `extra()` `QuerySet`修改机制 — 它能在 `QuerySet`生成的SQL从句中注入新子句
extra可以指定一个或多个 `参数`,例如 `select`, `where` or `tables`. 这些参数都不是必须的,但是你至少要使用一个!要注意这些额外的方式对不同的数据库引擎可能存在移植性问题.(因为你在显式的书写SQL语句),除非万不得已,尽量避免这样。
##### 参数之select
The `select` 参数可以让你在 `SELECT` 从句中添加其他字段信息,它应该是一个字典,存放着属性名到 SQL 从句的映射。
```
queryResult=models.Article
.objects.extra(select={'is_recent': "create_time > '2017-09-05'"})
```
结果集中每个 Entry 对象都有一个额外的属性is_recent, 它是一个布尔值,表示 Article对象的create_time 是否晚于2017-09-05.
##### 参数之`where` / `tables`
您可以使用`where`定义显式SQL `WHERE`子句 - 也许执行非显式连接。您可以使用`tables`手动将表添加到SQL `FROM`子句。
`where`和`tables`都接受字符串列表。所有`where`参数均为“与”任何其他搜索条件。
举例来讲:
```python
queryResult=models.Article
.objects.extra(where=['nid in (3,4) OR title like "py%" ','nid>2'])
```
## 8.7、上传文件
### 8.7.1、form表单上传文件
```html
<h3>form表单上传文件</h3>
<form action="/upload_file/" method="post" enctype="multipart/form-data">
<p><input type="file" name="upload_file_form"></p>
<input type="submit">
</form>
```
```python
def index(request):
return render(request,"index.html")
def upload_file(request):
print("FILES:",request.FILES)
print("POST:",request.POST)
return HttpResponse("上传成功!")
```
### 8.7.2、Ajax(基于FormData)
FormData是什么呢?
XMLHttpRequest Level 2添加了一个新的接口`FormData`.利用`FormData对象`,我们可以通过JavaScript用一些键值对来模拟一系列表单控件,我们还可以使用XMLHttpRequest的`send()`方法来异步的提交这个"表单".比起普通的ajax,使用`FormData`的最大优点就是我们可以异步上传一个二进制文件.
所有主流浏览器的较新版本都已经支持这个对象了,比如Chrome 7+、Firefox 4+、IE 10+、Opera 12+、Safari 5+。
```html
<h3>Ajax上传文件</h3>
<p><input type="text" name="username" id="username" placeholder="username"></p>
<p><input type="file" name="upload_file_ajax" id="upload_file_ajax"></p>
<button id="upload_button">提交</button>
{#注意button标签不要用在form表单中使用#}
<script>
$("#upload_button").click(function(){
var username=$("#username").val();
var upload_file=$("#upload_file_ajax")[0].files[0];
var formData=new FormData();
formData.append("username",username);
formData.append("upload_file_ajax",upload_file);
$.ajax({
url:"/upload_file/",
type:"POST",
data:formData,
contentType:false,
processData:false,
success:function(){
alert("上传成功!")
}
});
})
</script>
```
```python
def index(request):
return render(request,"index.html")
def upload_file(request):
print("FILES:",request.FILES)
print("POST:",request.POST)
return HttpResponse("上传成功!")
```
### 8.7.3、ImageField 和 FileField
ImageField 和 FileField 可以分别对图片和文件进行上传到指定的文件夹中。
1. 在下面的 models.py 中 :
picture = models.ImageField(upload_to='avatars/', default="avatars/default.png",blank=True, null=True) 注:定义 ImageField 字段时必须制定参数 upload_to这个字段要写相对路径,
这个参数会加在 settings.py 中的 MEDIA_ROOT后面, 形成一个路径, 这个路径就是上 传图片的存放位置,默认在Django项目根路径下,也就是MEDIA_ROOT默认是Django根目录
所以要先设置好 mysite/settings.py中的 settings.py 中的 MEDIA_ROOT
```python
class Userinfo(models.Model):
name = models.CharField(max_length=32)
avatar_img = models.FileField("avatars/")
```
```python
username = request.POST.get("username")
#获取文件对象
file = request.FILES.get("file")
#插入数据,将图片对象直接赋值给字段
user = Userinfo.objects.create(name=username,avatar_img=file)
```
Django会在项目的根目录创建avatars文件夹,将上传文件下载到该文件夹中,avatar字段保存的是文件的相对路径。
2. 在 mysite/settings.py中 :
```python
MEDIA_ROOT = os.path.join(BASE_DIR,"media")
MEDIA_URL='/media/'
```
> MEDIA_ROOT:存放 media 的路径, 这个值加上 upload_to的值就是真实存放上传图片文件位置
>
> MEDIA_URL:给这个属性设值之后,静态文件的链接前面会加上这个值,如果设置这个值,则UserInfo.avatar.url自动替换成:/media/avatars/default.png,可以在模板中直接调用:<img src="{{ user.avatar.url }}" alt="">。
3.url.py:
```python
from django.views.static import serve
# 添加media 配置
re_path(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
```
浏览器可以直接访问http://127.0.0.1:8000/media/yuan/avatars/%E7%86%8A%E7%8C%AB.webp,即我们的用户上传文件。
最后再给大家补充一个用户文件夹路径
```python
def user_directory_path(instance, filename):
return os.path.join(instance.name,"avatars", filename)
class Userinfo(models.Model):
name = models.CharField(max_length=32)
avatar_img = models.FileField(upload_to=user_directory_path)
```
4.FileField 和 ImageFiled 相同。
### 8.7.4、导入表格批量创建数据
```python
# Create your tests here.
def multi_create(request):
# 将接受到的excel文件转到mysql
# (1)下载文件到服务器
emp_excel = request.FILES.get("emp_excel")
print(emp_excel) # 期末题目(陕西联通).xlsx
print(emp_excel.name) # "期末题目(陕西联通).xlsx"
# 下载文件
with open("files/"+emp_excel.name,"wb") as f:
for line in emp_excel:
f.write(line)
# 读取excel,批量导入到mysql中
import os
from openpyxl import load_workbook
file_path = os.path.join("files",emp_excel.name)
# 加载某一个excel文件
wb = load_workbook(file_path)
# 获取sheet对象
print("wb.sheetnames",wb.sheetnames)
worksheet = wb.worksheets[0]
for row in worksheet.iter_rows(min_row=3):
print(row)
if row[0].value == None:
break
Employee.objects.create(name=row[0].value,
age=row[1].value,
ruzhi_date=row[2].value,
dep=row[3].value,
salary=row[4].value,
)
return redirect("/index/")
```
# 九、Django3的ASGI
## 9.1、Web应用程序和web服务器
Web应用程序(Web)是一种能完成web业务逻辑,能让用户基于web浏览器访问的应用程序,它可以是一个实现http请求和响应功能的函数或者类,也可以是Django、Flask、sanic等这样的web框架,当然也可以是其他语言的web程序或web框架。
Web服务器(Web Server)是一种运行于网站后台(物理服务器)的软件。Web服务器主要用于提供网页浏览或文件下载服务,它可以向浏览器等Web客户端提供html网页文档,也可以提供其他类型的可展示文档,让客户端用户浏览;还可以提供数据文件下载等。目前世界上最主流的Web服务器有 Nginx 、Apache、IIS、tomcat。
```python
问:Web服务器和Web应用程序的区别?
答:Web应用程序主要是完成web应用的业务逻辑的处理,Web服务器则主要是应对外部请求的接收、响应和转发。
需要使用web服务器启动运行,web应用程序才能被用户访问到。
而django框架中,我们之所以只有一个web应用程序就跑起来了,是因为我们在终端执行了一个命令,python manage.py runserver。这个命令启动了django框架中内置提供的测试web服务器。
```
## 9.2、网关接口
网关接口(Gateway Interface,GI)就是一种为了实现加载动态脚本而运行在Web服务器和Web应用程序中的通信接口,也可以理解为一份协议/规范。只有Web服务器和Web应用程序都实现了网关接口规范以后,双方的通信才能顺利完成。常见的网关接口协议:CGI,FastCGI,WSGI,ASGI。

#### (1)CGI
CGI(Common Gateway Inteface): 字面意思就是通用网关接口,
> 它是外部应用程序与Web服务器之间的接口标准
意思就是它用来规定一个程序该如何与web服务器程序之间通信从而可以让这个程序跑在web服务器上。当然,CGI 只是一个很基本的协议,在现代常见的服务器结构中基本已经没有了它的身影,更多的则是它的扩展和更新。
FastCGI: CGI的一个扩展, 提升了性能,废除了 CGI fork-and-execute (来一个请求 fork 一个新进程处理,处理完再把进程 kill 掉)的工作方式,转而使用一种长生存期的方法,减少了进程消耗,提升了性能。
这里 FastCGI 就应用于前端 server(nginx)与后端 server(uWSGI)的通信中,制定规范等等,让前后端服务器可以顺利理解双方都在说什么(当然 uWSGI 本身并不用 FastCGI, 它有另外的协议)
#### (2)WSGI
WSGI(Python Web Server GateWay Interface):它是用在 python web 框架编写的应用程序与后端服务器之间的规范(本例就是 Django 和 uWSGI 之间),让你写的应用程序可以与后端服务器顺利通信。在 WSGI 出现之前你不得不专门为某个后端服务器而写特定的 API,并且无法更换后端服务器,而 WSGI 就是一种统一规范, 所有使用 WSGI 的服务器都可以运行使用 WSGI 规范的 web 框架,反之亦然。
WSGI和ASGI,都是基于Python设计的网关接口(Gateway Interface,GI)。

Web服务器网关接口(Python Web Server Gateway Interface,WSGI),是Python为了解决**Web服务器端与客户端之间的通信**基于CGI标准而设计的。实现了WSGI协议的web服务器有:uWSGI、uvicorn、gunicorn。像django框架一般开发中就不会使用runserver来运行,而是采用上面实现了WSGI协议的web服务器来运行。
django中运行runserver命令时,其实内部就启动了wsgiref模块作为web服务器运行的。wsgiref是python内置的一个简单地遵循了wsgi接口规范的web服务器程序。
```python
from wsgiref.simple_server import make_server
# application 由wsgi服务器调用、函数对http请求与响应的封装、使得Python专注与HTML
# environ http 请求 (dist)
# start_response 响应 (function)
def application(environ, start_response):
# 请求
if environ['REQUEST_METHOD'] == 'GET' and environ['PATH_INFO'] == '/':
# 响应
start_response('200 OK', [('Content-Type', 'text/html')])
return [b'<h1>hi, python!</h1>']
if __name__ == '__main__':
# 启动服务器 | 这个服务器负责与 wsgi 接口的 application 函数对接数据
httpd = make_server('127.0.0.1', 8000, application)
# 监听请求
httpd.serve_forever()
# 1. 监听8000端口,
# 2. 把http请求根据WSGI协议将其转换到applcation中的environ参数, 然后调用application函数.
# 3. wsgiref会把application函数提供的响应头设置转换为http协议的响应头,
# 4. 把application的返回(return)作为响应体, 根据http协议,生成响应, 返回给浏览器.
```
开发中,我们一般使用uWSGI或者Gunicorn作为web服务器运行django。
#### (3)uWSGI
[uWSGI](https://uwsgi-docs.readthedocs.io/) 是一个快速的,自我驱动的,对开发者和系统管理员友好的应用容器服务器,用于接收前端服务器转发的动态请求并处理后发给 web 应用程序。完全由 C 编写,实现了WSGI协议,uwsgi,http等协议。注意:uwsgi 协议是一个 uWSGI服务器自有的协议,用于定义传输信息的类型,常用于uWSGI服务器与其他网络服务器的数据通信中。
uwsgi: 是uWSGI服务器实现的独有的协议, 网上没有明确的说明这个协议是用在哪里的,我个人认为它是用于前端服务器与 uwsgi 的通信规范,相当于 FastCGI的作用。


