0801 RESTAPI设计,DRF 序列化
1.内容回顾
1.restframework serializer(序列化)的简单使用
QuereySet([obj,obj,obj]) --> JSON格式数据
安装和导入:
pip install djangorestframework
from rest_framework import serializer
1.创建一个类,类一定要继承serializer.Serializers
2.chocie字段和FK字段都可以通过source来获取对应的值
3.多对多字段可以通过使用serializer.SerializerMethod
声明序列化器:
def get_tag(self, obj):
tag_list = []
for i in obj.tag.all():
tag_list.append(i.name)
return tag_list
(比较麻烦)
2.使用 ModelSerializer(简单)
和ORM绑定的序列化
通过配置class Meta:
model = 表名
fields = ['字段', ...]
depth = 1
2. 今日内容
1. RESTful风格API介绍
一、什么是RESTful
REST -- REpresentational State Transfer,英语的直译就是“表现层状态转移”。如果看这个概念,估计没几个人能明白是什么意思。
那下面就让我来用一句人话解释一下什么是RESTful:URL定位资源,用HTTP动词(GET,POST,PUT,DELETE)描述操作。
Resource:资源,即数据。
Representational:某种表现形式,比如用JSON,XML,JPEG等;
State Transfer:状态变化。通过HTTP动词实现。
REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”。
REST从资源得角度类审视整个网络,它将分布在网络中某个节点的资源通过URL进行标识,客户端应用通过URL来获取资源的表征,获得这些表征致使这些应用转变状态。
所有的数据,不过是通过网络获取的还是操作(增删改查)的数据,都是资源,将一切数据视为资源是REST区别与其他架构风格的最本质属性。
二、RESTful API设计
API与用户的通信协议
总是使用HTTPs协议。
域名
https://api.example.com 尽量将API部署在专用域名
https://example.org/api/ API很简单
版本
1. 将版本信息放在URL中,如:https://api.example.com/v1/
2. 将版本信息放在请求头中。
路径
视网络上任何东西都是资源,均使用名词表示(可复数)
https://api.example.com/v1/zoos
https://api.example.com/v1/animals
https://api.example.com/v1/employees
method
GET :从服务器取出资源(一项或多项)
POST :在服务器新建一个资源
PUT :在服务器更新资源(客户端提供改变后的完整资源)
PATCH :在服务器更新资源(客户端提供改变的属性)
DELETE :从服务器删除资源
过滤
通过在url上传参的形式传递搜索条件
https://api.example.com/v1/zoos?limit=10:指定返回记录的数量
https://api.example.com/v1/zoos?offset=10:指定返回记录的开始位置
https://api.example.com/v1/zoos?page=2&per_page=100:指定第几页,以及每页的记录数
https://api.example.com/v1/zoos?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序
https://api.example.com/v1/zoos?animal_type_id=1:指定筛选条件
状态码
200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT - [DELETE]:用户删除数据成功。
400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
更多看这里:http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
错误处理
状态码是4xx时,应返回错误信息,error当做key。
{
error: "Invalid API key"
}
返回结果
针对不同操作,服务器向用户返回的结果应该符合以下规范
GET /collection:返回资源对象的列表(数组)
GET /collection/resource:返回单个资源对象
POST /collection:返回新生成的资源对象
PUT /collection/resource:返回完整的资源对象
PATCH /collection/resource:返回完整的资源对象
DELETE /collection/resource:返回一个空文档
Hypermedia API
RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。
{"link": {
"rel": "collection https://www.example.com/zoos",
"href": "https://api.example.com/zoos",
"title": "List of zoos",
"type": "application/vnd.yourformat+json"
}}
更多内容,请参考:阮一峰的Blog
二、基于Django实现RESTful API
1.RESTful API之url设计
路由分发:
在原先的URL中添加:url(r'api/', include(app01_urls)), include表示导入py文件
在 app01下新建路径文件:app01_urls.py
from django.conf.urls import urlfrom app01 import views
urlpatterns = [
url(r'article/', views.Article.as_view()),
url(r'article/(?P<pk>\d+)', views.ArticleDetail.as_view(), name='article-detail'),
url(r'school/(?P<id>\d+)', views.SchoolDetail.as_view(), name='school-detail111'),
# 评论
# url(r'comment_list/', ),
# url(r'add_comment/', ),
# url(r'delete_comment/', ),
# url(r'edit_comment/', ),
url(r'comment/', views.Comment.as_view()),
]
2.RESTful API之urls视图
针对4种请求方式,使用FBV
def comment(request):
if request.method == "GET":
return HttpResponse("获取评论")
elif request.method == "POST":
return HttpResponse("创建新评论")
elif request.method == "PUT":
return HttpResponse("修改评论")
elif request.method == "DELETE":
return HttpResponse("删除评论")
这样写的话,一个4层if判断,就会有很冗长的代码。不利与维护,推荐使用CBV方式
CBV方式
from django import viewsclass Comment(views.View):
def get(self, request):
return HttpResponse("获取评论")
def post(self, request):
return HttpResponse("创建新评论")
def put(self, request):
return HttpResponse("修改评论")
def delete(self, request):
return HttpResponse("删除评论")
三、 APIView
from rest_framework import APIView
class Comment(views.View):
pass
-------- 之前的写法 ↑ ----------
class APIView(views.View):
扩展的功能
self.request = Resquest(
self._request = request(Django的request)
)
self.request.data 封装好的数据属性,POST\PUT请求携带的数据都可以从这里取
pass
class Comment(APIView):
pass
1. APIView与View的区别
- APIView是View的子类
- 传递给请求处理程序的request实例是REST框架的请求实例,而不是Django的HttpRequest实例
- 处理程序返回的基于REST框架的Response,而不是Django的HttpResponse,视图函数将会管理内容协商,然后设置正确的渲染方式
- 任何APIException将会被捕捉,然后转换成合适的response对象
- 接收到的请求首先被认证,然后赋予相应的权限,然后通过节流器分发给相应的请求处理函数,类似.get()和.post()
APIView是专门写API的视图函数,结合serializers,非常方便做序列化!
关于APIView的源码解析,请参考文章:
https://blog.csdn.net/u013210620/article/details/79857654
取数据
get:
request._request ( 原request )
request.GET == request._request.GET
post:
request.data ( 得到 json 数据,也可得到 urlencode 数据)
都是 rest_framework APIView 的功劳,
如果使用 View , request.post( 只能得到 urlencode 的数据,不能得到 json 数据,只能从 request.body 中取)
3. serializer
1. ModelSerializer
1. 基于APIView实现的GET和POST
2. POST过来的数据进行校验
3. 保存数据
在app01下面创建一个文件,专门放序列化的
from app01.models import Comment, Article, School
from rest_framework import serializers
from rest_framework.validators import ValidationError
# 序列化评论的类
class CommentSerializer(serializers.ModelSerializer):
# content = serializers.CharField(error_messages={"required": '内容不能为空'})
# article_detail = serializers.SerializerMethodField(read_only=True)
# 用于做校验的钩子函数,类似于form组件的clean_字段名
def validate_content(self, value):
if '草' in value:
raise ValidationError('不符合社会主义核心价值观!')
else:
return value
# 全局的钩子
# def validate(self, attrs):
# # self.validated_data # 经过校验的数据 类似于form组件中的cleaned_data
# # 全局钩子
# pass
class Meta:
model = Comment
fields = "__all__"
# depth = 1
# 定义额外的参数
extra_kwargs = {
"content": {
"error_messages": {
"required": '内容不能为空',
}
},
"article": {
"error_messages": {
"required": '文章不能为空'
}
}
}
# 文章的序列化类
class ArticleModelSerializer(serializers.ModelSerializer):
# type_str = serializers.CharField(source='get_type_display', read_only=True)
class Meta:
model = Article # 绑定的ORM类是哪一个
fields = "__all__" # ["id", "title", "type"]
# depth = 1 # 官方推荐不超过10层
# 文章超链接序列化
class ArticleHyperLinkedSerializer(serializers.HyperlinkedModelSerializer):
school = serializers.HyperlinkedIdentityField(view_name='school-detail111', lookup_url_kwarg='id')
class Meta:
model = Article # 绑定的ORM类是哪一个
fields = ["id", "title", "type", "school"]
# 学校的序列化
class SchoolSerializer(serializers.ModelSerializer):
class Meta:
model = School
fields = "__all__"
参数解释:
source 表示来源
lookup_field 表示查找字段
lookup_url_kwarg 表示路由查找的参数,pk表示主键
view_name 它是指urls定义的name值,一定要一一对应。
models.py
from django.db import models
# 文章表
class Article(models.Model):
title = models.CharField(max_length=32, unique=True, error_messages={"unique": "文章标题不能重复"})
# 文章发布时间
# auto_now每次更新的时候会把当前时间保存
create_time = models.DateField(auto_now_add=True)
# auto_now_add 第一次创建的时候把当前时间保存
update_time = models.DateField(auto_now=True)
# 文章的类型
type = models.SmallIntegerField(
choices=((1, "原创"), (2, "转载")),
default=1
)
# 来源
school = models.ForeignKey(to='School', on_delete=models.CASCADE)
# 标签
tag = models.ManyToManyField(to='Tag')
# 文章来源表
class School(models.Model):
name = models.CharField(max_length=16)
# 文章标签表
class Tag(models.Model):
name = models.CharField(max_length=16)
# 评论表
class Comment(models.Model):
content = models.CharField(max_length=128)
article = models.ForeignKey(to='Article', on_delete=models.CASCADE)
GET:
class Comment(APIView):
def get(self, request):
res = {"code": 0}
# print(self.request) # rest framework封装好的request
# print(self.request.data)
# print(self.request._request.GET.get("age")) # Django原来的request对象
# 数据
all_comment = models.Comment.objects.all()
# 序列化
ser_obj = app01_serializers.CommentSerializer(all_comment, many=True)
res["data"] = ser_obj.data
return Response(res)
POST:
def post(self, request):
res = {"code": 0}
# 去提交的数据
comment_data = self.request.data
# 对用户提交的数据做校验
ser_obj = app01_serializers.CommentSerializer(data=comment_data)
if ser_obj.is_valid():
# 表示数据没问题,可以创建
ser_obj.save() #创建数据,写入数据库
else:
# 表示数据有问题
res["code"] = 1
res["error"] = ser_obj.errors
return Response(res)
return HttpResponse("创建新评论")
#文章CBV
class Article(APIView):
def get(self,request):
res={"code":0}
all_article = models.Article.object.all() #显示超链接的URL需要的参数,表示上下文
ser_obj = app01_serializers.ArticleHyperLinkedSerializer(all_article, many=True, context={'request': request})
res["data"] = ser_obj.data
return Response(res)
def post(self, request):
res = {"code": 0}
ser_obj = app01_serializers.ArticleModelSerializer(data=self.request.data)
if ser_obj.is_valid():
ser_obj.save()
else:
res["code"] = 1
res["error"] = ser_obj.errors
return Response(res)
# 文章详情CBV
class ArticleDetail(APIView):
def get(self, request, pk):
res = {"code": 0}
article_obj = models.Article.objects.filter(pk=pk).first()
# 序列化
ser_obj = app01_serializers.ArticleHyperLinkedSerializer(article_obj, context={'request': request})
res["data"] = ser_obj.data
return Response(res)
# 学校详情CBV
class SchoolDetail(APIView):
def get(self, request, id):
res = {"code": 0}
school_obj = models.School.objects.filter(pk=id).first()
ser_obj = app01_serializers.SchoolSerializer(school_obj, context={'request': request})
res["data"] = ser_obj.data
return Response(res)
Response:
配置之后:浏览器才可访问; rest_framework 是django的一个app!
可发 get / post 请求 !!
rest framework 引入了Response对象,它是一个TemplateResponse类型,并根据客户端需求正确返回需要的类型。
使用前,需要导入模块
from rest_framework.response import Response
语法:
return Response(data) # 根据客户端的需求返回不同的类型。