REST framework
一、引入Django REST framework
1.Web应用模式
在开发web应用中,有两种应用模式:前后端不分离和前后端分离。
-
前后端不分离
- 前端页面看到的效果都是由后端控制,由后端渲染页面或重定向。
- 前端与后端的耦合度很高。
- 比较适合纯网页应用,当后端对接App时,返回的网页接口不适用于前端App应用
-
前后端分离
- 由前端服务器返回静态文件,后端仅返回前端所需要的的数据,不再渲染HTML页面
- 前端与后端的耦合度相对较低
- 后端仅需要开发一套逻辑对外提供数据 ,可适用于网页和App的前端
- 在前后端分离的应用模式中,将后端开发的每个视图都称为一个借口,或者API,前端通过访问接口来对数据进行增删改查。
前后端分离开发示例
获取图书数据 Web API
- 请求方式:get
- 请求路径:book/1
- 请求参数:图书id 1
- 请求结果:json {‘books’:[{‘name’:‘西游记’,‘bread’:20},{‘name’:‘三国’,‘bread’:30}]}
前端
<ul>
<li v-for="book in data">
{
{
book.name}}
{
{
book.bread}}
</li>
</ul>
axios.get('http:\\127.0.0.1:8000/book/1')
# 200系列状态码,执行then
.then(response => {
this.data = response.data.books
})
# 返回400/500状态码,执行catch
.catch(response = >{
response.data.error
})
后端
url(r'book/(?P<pk>\d+)',views.BookView.as_view())
class BookView(View):
def get(self,request,pk):
# 1.获取数据
# 2.查询数据
try:
book = Book.object.get(id=pk)
except:
return HttpResponse()
data = {
"name":book.name, "bread":book.bread}
return JsonResponse(data)
2.RESTful
在前后端分离的应用模式里,API接口普遍采用RESTful设计风格
-
1.域名:应该尽量将API部署在专有域名之下
https://api.example.com
如京东:https://shouji.jd.com/
如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下
https:example.org/api/ -
2.版本:应该将API的版本号放入url
http://www.example.com/app/1.0/foo
http://www.example.com/app/1.1/foo
http://www.example.com/app/2.0/foo
另一种做法是将版本号放在HTTP头信息中,因为不同的版本可以理解成同一种资源的不同表现形式,所以应该采用同一种url,版本号可以放在HTTP请求头信息的Accept字段中进行区分。
Accept: vnd.example-com.foo+json; version=1.0
Accept: vnd.example-com.foo+json; version=1.1
Accept: vnd.example-com.foo+json; version=2.0 -
3.路径
路径又称“终点”,表示API的具体网址,每个网址代表一种资源。- (1) 资源作为网址,只能有名词,不能有动词,所用名词往往与数据库的表名对应;此外,利用HTTP方法可以分离网址中的资源名称的操作。
如:
GET /products :将返回所有产品清单
POST /products :将产品新建到集合
GET /products/4 :将获取产品 4
PATCH(或)PUT /products/4 :将更新产品 4 - (2) API中的名词 应该使用复数,无论子资源还是所有资源
如:
获取单个产品:http://127.0.0.1:8080/AppName/rest/products/1
获取所有产品: http://127.0.0.1:8080/AppName/rest/products
- (1) 资源作为网址,只能有名词,不能有动词,所用名词往往与数据库的表名对应;此外,利用HTTP方法可以分离网址中的资源名称的操作。
-
4.HTTP动词
对于资源的具体操作类型,由HTTP动词表示,常用的HTTP动词有下面四个。
GET:从服务器取出资源(一项或多项)。
POST:在服务器新建一个资源。
PUT:在服务器更新资源(客户端提供改变后的完整资源)。
DELETE:从服务器删除资源。
如:
GET /zoos:列出所有动物园
POST /zoos:新建一个动物园(上传文件)
GET /zoos/ID:获取某个指定动物园的信息
PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
DELETE /zoos/ID:删除某个动物园
GET /zoos/ID/animals:列出某个指定动物园的所有动物
DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物 -
5.过滤信息以查询字符串进行
如果记录数量很多,API应该提供参数,过滤返回结果。
下面是一些常见的参数:?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
?animal_type_id=1:指定 -
6.状态码
后端执行成功或失败以状态码的形式告诉前端,处理成功返回200系列状态码,执行前端then里面的代码;处理失败返回400/500系列状态码,执行catch里面的代码。
- 200 OK :服务器成功返回用户请求的数据
- 201 CREATED :用户新建或修改数据成功。
- 202 Accepted :表示一个请求已经进入后台排队(异步任务)
- 204 NO CONTENT :用户删除数据成功。
- 400 INVALID REQUEST :用户发出的请求有错误,服务器没有进行新建或修改数据的操作
- 401 Unauthorized :表示用户没有权限(令牌、用户名、密码错误)。
- 403 Forbidden :表示用户得到授权(与401错误相对),但是访问是被禁止的。
- 404 NOT FOUND :用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
- 406 Not Acceptable :用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
- 410 Gone:用户请求的资源被永久删除,且不会再得到的。
- 422 Unprocesable entity : 当创建一个对象时,发生一个验证错误。
- 500 INTERNAL SERVER ERROR :服务器发生错误,用户将无法判断发出的请求是否成功
- 7.错误处理
如果状态码是4xx,服务器就应该向用户返回出错信息,一般来说,返回的信息中将error作为键名,出错信息作为键值。
{
error:"出错信息"
}
- 8.返回结果
针对不同操作,服务器向用户返回的结果应该符合以下规范:- GET /collection:返回资源对象的列表(数组)
- GET /collection/resource:返回单个资源对象
- POST /collection:返回新生成的资源对象
- PUT /collection/resource:返回完整的资源对象
- PATCH /collection/resource:返回完整的资源对象
- DELETE /collection/resource:返回一个空文档
- 9.其他
服务器返回的数据格式,应该尽量使用JSON,避免使用XML
3.使用Django开发REST接口示例
使用图书英雄案例来写一套支持图书数据增删改查的REST API接口
① 接口设计
- 增加图书
请求方式 | 请求路径 | 请求参数 | 返回结果 |
---|---|---|---|
POST | books/ | title、bpub_date (请求体json) | 保存后的图书数据books=[{btitle:“西游记”,bpub_date:2017},{…}] |
- 删除图书
请求方式 | 请求路径 | 请求参数 | 返回结果 |
---|---|---|---|
DELETE | books/1 | id (路径) |
- 修改图书
请求方式 | 请求路径 | 请求参数 | 返回结果 |
---|---|---|---|
PUT | books/1 | 修改字段内容(json)、id (路径) | 更新后的图书数据 book={ btitle:“xxx”,bpub_date:“xxx”} |
- 查询图书
查询单一图书
请求方式 | 请求路径 | 请求参数 | 返回结果 |
---|---|---|---|
GET | books/1 | id (路径) | book={ btitle:“xxx”,bpub_date:“xxx”} |
查询所有图书
请求方式 | 请求路径 | 请求参数 | 返回结果 |
---|---|---|---|
GET | books/1 | books=[{btitle:“西游记”,bpub_date:2017},{…}] |
② 后端代码实现
urlpatterns = [
url(r'^books/$', views.BooksView.as_view()),
url(r'^books/(?P<pk>\d+)/', views.BookView.as_view()),
]
from django.views import View
from django.http import JsonResponse
from book.models import BookInfo
import json
class BooksView(View):
def get(self,request):
"""获取所有图书""""
books = BookInfo.objects.all()
data = []
for book in books:
data.append({
"id": book.id,
"btitle": book.btitle,
"bpub_date": book.bpub_date
})
# safe=False,是将列表[]转成json中的数组"[]"
return JsonResponse(data, safe=False)
def post(self,request):
"""保存图书"""
# 1、获取前端数据
data = request.body.decode()
data_dict = json.loads(data)
btitle = data_dict.get("btitle")
bpub_date = data_dict.get("bpub_date")
# 2、验证数据
if btitle == "python":
return JsonResponse({
"error": "错误的书名"},status=400)
# 3、保存数据
book = BookInfo.objects.create(btitle=btitle, bpub_date=bpub_date)
# 4、返回结果
return JsonResponse({
"id": book.id,
"bpub_date": book.bpub_date,
"btitle": book.btitle
})
class BookView(View):
def get(self,request,pk):
"""查询一本图书"""
try:
book = BookInfo.objects.get(id=pk)
except:
return JsonResponse({
"id": book.id,
"btitle": book.btitle,
"bpub_date": book.bpub_date
})
def put(self,request,pk):
"""修改一本图书"""
# 1、获取数据
data = request.body.decode()
data_dict = json.loads(data)
btitle = data_dict.get("btitle")
bpub_date = data_dict.get("bpub_date")
# 2、校验数据
# 3、更新数据
# 这种方法得到的并不是对象,而是得到更新数据的个数
# book = BookInfo.objects.filter(id=pk).update(btitle=btitle,bpub_date=bpub_date)
try:
book = BookInfo.objects.get(id=pk)
except:
return JsonResponse({
"error": "错误的id"},status=400)
book.btitle = btitle
book.bpub_date = bpub_date
book.save()
# 4、返回结果
return JsonResponse({
"id": book.id,
"btitle": book.btitle,
"bpub_date": book.bpub_date
})
def delete(self,request,pk):
"""删除一本图书"""
try:
book = BookInfo.objects.get(id=pk)
except:
return JsonResponse({
"error": "错误的id"}, status=400)
book.delete()
4.序列化与反序列化
- 序列化:将程序的一个数据结构类型转换成其他格式(字典、JSON、XML等),如,获取数据库中的数据对象所对应的字段数据转化为json的过程(从后端服务到前端服务)
- 反序列化:将其他格式(字典、JSON、XML等)转换为程序中的数据,如,获取前端数据,验证数据到保存或更新数据获取新的数据对象的过程(从前端服务到后端服务)
5.Django REST framework简介
- Django REST framework 框架是一个用于构建Web API的强大而又灵活的工具,通常简称为DRF框架或REST framework。
- 特点:
- 提供了定义序列化器Serializer的方法,可以快速根据Django ORM或者其他库自动序列化/反序列化。
- 提供了各种丰富的类视图、Mixin扩展类,简化视图的编写
- 丰富的定制层级:函数视图、类视图、视图集合到自动生成API,满足各种需要
- 多种身份认证和权限认证方式的支持
- 内置了限流系统
- 直观的API web界面
- 可扩展性,插件丰富
- 官方文档
二、DRF工程搭建
环境安装与配置
- 1.安装DRF
pip install djangorestframework
- 2.添加rest_framework应用
在settings.py的INSTALLED_APPS中添加’rest_framework’
INSTALLED_APPS = [
...
'rest_framework',
]
三、Serializer序列化器
序列化器的作用:
- 进行数据的校验
- 对数据对象进行转换
1.定义Serializer
在应用的目录下创建serializers.py文件,Serializer使用类来定义,须继承自rest_framework.serializers.Serializer.
from rest_framework import serializers
class BookSerializer(serializers.Serializers):
# 根据模型类字段进行定义字段
btitle=serializers.CharField()
bpub_date=serializers.DateField()
注:serializer不是只能为数据库模型类定义,也可以为非数据库模型类的数据定义,serializer是独立于数据库之外的存在。
2.序列化使用
- 1 基本使用
from book.serializers import BookSerializer
class BookView(View):
def get(self,request,pk):
"""查询一本图书"""
try:
# 查询出一个图书对象
book = BookInfo.objects.get(id=pk)
except:
# return JsonResponse({"id": book.id,"btitle": book.btitle,"bpub_date": book.bpub_date})
# 1.构造序列化器对象
serializer = BookSerializer(book)
# 2.通过data方法获取序列化数据
data = serializer.data
return JsonResponse(data)
- 2 返回多条数据,通过添加many=True参数
from book.serializers import BookSerializer
class BooksView(View):
def get(self,request):
"""获取所有图书""""
books = BookInfo.objects.all()
# data = []
# for book in books:
# data.append({"id": book.id,"btitle": book.btitle,"bpub_date": book.bpub_date})
serializer = BookSerializer(books,many=True)
return JsonResponse(serializer.data, safe=False)
- 3 关联对象嵌套序列化
如果需要序列化的数据包含有其他关联对象,则对关联对象数据的序列化需要指明。
对于关联字段可以采用以下几种方式:- PrimaryKeyRelatedField
此字段将被序列化为关联对象的主键,返回关联对象的id值
heroinfo_set = serializers.PrimaryKeyRelatedField(read_only=True,many=True)
注:read_only=True,该字段只能用于序列化操作,不能用于反序列化;write_only=True,只能用于反序列化操作,不能用于序列化操作;many=True,关联的对象数据可能包含多条数据。
返回结果示例:
- PrimaryKeyRelatedField
{
"btitle":"天龙八部", "bpub_date": "1986-07-24", "heroinfo_set":[6,7,8,9]}
- StringRelatedField
此字段将被序列化为关联对象的字符串表示方式(即__str__方法的返回值),返回模型类中__str__方法中返回的数据。
<