目录
接口规范
Web API接口
- 什么是Web API接口
# 为了在团队内部形成共识、防止个人习惯差异引起的混乱,我们需要找到一种大家都觉得很好的接口实现规范,而且这种规范能够让后端写的接口,用途一目了然,减少双方之间的合作成本。
# 通过网络,规定了前后端信息交互规则的url链接,也就是前后端信息交互的媒介,它称之为Web API接口
-前端:向后端发送请求,获取数据 127.0.0.1:8080/index --->返回数据
-后端:请求某个地址,返回固定的数据
# Web API接口简单概括有下面四大特点
1.url:长得像返回数据的url链接
127.0.0.1:8080/index
2.有不同的请求方式:get、post、put、patch、delete
3.请求参数:json或xml格式的key-value类型数据
127.0.0.1:8080/books?name=红楼梦 放在请求体中
4.响应结果: 一般是json格式,也可能是xml
- 接口文档编写
接口文档资料参照:https://www.cnblogs.com/liuqingzheng/articles/17413678.html
# 作为后端,接口写好了
# 作为前端,需要使用我们写的接口(移动端,web,桌面端)
# 后端需要写接口文档
# 接口文档编写规范
-以用户注册接口为例:
1 接口描述
2 请求地址
3 请求方式
4 编码格式:json,urlencoded,form-data
5 请求参数:
-请求头
-请求地址参数
-请求体参数
6 返回格式示例
返回参数说明
7 其他(可有可无)
错误码
# 接口文档的展现形式:
1 word ,md ,写好传到公司的某个平台,与前端共享
2 自动生成接口文档---》后端通过配置--》把所写的接口都自动生成---》地址--》访问这个地址就能看到所有接口文档
# 自动生成接口文档
-coreapi,swagger:drf-yasg
# coreapi自动生成
第一步:pip install coreapi
第二步:设置接口文档访问路径
from rest_framework.documentation import include_docs_urls
urlpatterns = [
path('docs/', include_docs_urls(title='站点页面标题'))
]
第三步:在视图中,加注释
第四步:配置文件配置
REST_FRAMEWORK = {'DEFAULT_SCHEMA_CLASS':
'rest_framework.schemas.coreapi.AutoSchema'},
3 公司内部搭建接口文档平台
- 开源:Yapi,YApi是去哪网大前端技术中心的一个开源可视化接口管理平台,YApi项目可以搭建在任何本地或云服务器上,完成后台项目开发时的接口编写。为开发、测试等人员提供可视化的接口预览
-https://zhuanlan.zhihu.com/p/366025001
- 自己开发(自研)
4 使用第三方平台(花钱)-->showdoc ....
接口测试工具
# 写好的接口,要测试,可以使用浏览器来做,但是浏览器只能发送get请求,接口有其他请求方式
# 专门的接口测试工具(使用习惯,大致一样)
-postman,大部分公司用的,原来免费, 后来收费了,但是咱们只用免费部分就够了
-postwomen
-apifox
# 安装:下载安装包,双击运行,就安装完成
# 可以注册,登录后使用
#也可以跳过等, 直接使用
#不自动匹配/
# post请求,有多种编码格式
-urlencoded 格式 ---》默认格式 b'xx=xx&yy=yy'
-form-data 格式 ---》传文件格式,也可以带数据
----------------------------251971281264627087498320-- 带数据,带文件
-json格式(用的最多)-->只能传数据 b'{"name":"lqz","age":18}'
# 如果是json形式提交数据----》django中想取,从request.POST取出到的
- 测试postman
# 1 用django写个demo---》postman测试
-1 传用户名密码到后端,查询数据库,都对了---》返回json格式{code:100,msg:登录成功}---》urlencoded
-2 打印 request.POST
-3 打印 request.body
def index(request):
back_dic={'code':200,'msg':'登录成功'}
name=request.POST.get('name') #jack
password=request.POST.get('password') #123
#查询数据库用户信息与之比较
res=models.User.objects.filter(name=name,password=password)
if res:
print(name)
print(password)
return JsonResponse(back_dic)
else:
back_dic={'code':201,'msg':'用户名或密码错误'}
return JsonResponse(back_dic)
# 2 写一个接口,可以上传文件{code:100,msg:上传成功}
-request.POST---->有没有取决于前端传没传数据
-request.body--->小文件没事,大文件报错
@csrf_exempt
def files(request):
print('body>', request.body)
print('POST>', request.POST) # 针对文件数据不在封装到qs中
file_obj = request.FILES.get('myfile')
# print(file_obj.name) #课堂随记.txt
# print(file_obj,type(file_obj))#<class 'django.core.files.uploadedfile.InMemoryUploadedFile'>
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
dir_path = os.path.join(base_dir, 'file_dir')
if not os.path.exists(dir_path):
os.mkdir(dir_path)
files_dir=os.path.join(dir_path,file_obj.name)
with open(files_dir, 'wb') as f:
# with open(f'{dir_path}/{file_obj.name}', 'wb') as f:
for line in file_obj:
f.write(line)
return JsonResponse({'code': 200, 'msg': '上传成功'})
# 3 -测:json格式---》request.POST 取不到,-request.data 默认没有---》(装饰器)--->无论前端什么格式:josn,urlencoded---》都取出来,是字典
#方式一
def authenticated(func):
def inner(request, *args, **kwargs):
# if request.method == 'POST':
if request.POST: #其他格式form-data
request.data=request.POST
print(request.POST) #<QueryDict: {'name': ['jack']}>
else: #json格式
js_str = request.body.decode('utf8')
request.data = json.loads(js_str)
print(request.data) #{'name': 'jack'}
return func(request, *args, **kwargs)
return inner
from django.views.decorators.csrf import csrf_exempt #取消验证csrf中间件
@csrf_exempt
@authenticated
def js(request):
return JsonResponse(request.data)
#方式二
def wraper(func):
def inner(request,*args,**kwargs):
try:
request.data=json.loads(request.body)
except:
request.data=request.POST
return func(request, *args, **kwargs)
return inner
@csrf_exempt
@wraper
def test(request):
name=request.data.get('name')
password=request.data.get('password')
user=models.User.objects.filter(name=name,password=password).first()
if user:
return JsonResponse({'code':'200','msg':'登录成功'})
else:
return JsonResponse({'code':'201','msg':'用户名或密码错误'})
Restful 接口规范
- 简介
RESTful是一种定义API接口的设计风格,API接口的编写规范,尤其适用于前后端分离的应用模式中
这种风格的理念认为后端开发任务就是提供数据的,对外提供的是数据资源的访问接口,所以在定义接口时,客户端访问的URL路径就表示这种要操作的数据资源
我们可以使用任何一个框架都可以实现符合restful规范的API接口
- 10条规范
1 数据的安全保障,通常使用https协议进行传输,它比http更安全
2 url地址中带接口标识:一般这样
-https://api.baidu.com
-https://www.baidu.com/api
3 多版本共存,url地址中带版本信息
https://api.baidu.com/v1/login/
https://api.baidu.com/v2/login/
4 数据即是资源,均使用名词:
url地址尽量使用名词,不建议使用动词
# 接口一般都是完成前后台数据的交互,交互的数据我们称之为资源
https://api.baidu.com/users
https://api.baidu.com/books
https://api.baidu.com/book
注:一般提倡用资源的复数形式,在url链接中不要出现操作资源的动词,错误示范:https://api.baidu.com/delete-user
# 特殊的接口可以出现动词,因为这些接口一般没有一个明确的资源,或是动词就是接口的核心含义
https://api.baidu.com/place/search
https://api.baidu.com/login
5 资源操作由请求方式决定(method)
#操作资源一般都会涉及到增删改查,我们提供请求方式来标识增删改查动作(get请求数据 post新增数据 put修改数据 delete删除数据)
https://api.baidu.com/books - get请求:获取所有书
https://api.baidu.com/books/1 - get请求:获取主键为1的书
https://api.baidu.com/books - post请求:新增一本书书
https://api.baidu.com/books/1 - put请求:整体修改主键为1的书
https://api.baidu.com/books/1 - delete请求:删除主键为1的书
6 url地址中带过滤条件
# 只针对于搜索所有接口?后带过滤条件
https://api.baidu.com/books -get请求表示查询所有图书,要查名字中有红的图书
https://api.baidu.com/books?name_contains=红
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:指定筛选条件
7 响应状态码(http响应中带状态码)
-http的响应状态码:https://blog.csdn.net/meng2lin/article/details/128955775
-1xx:请求正在处理
-2xx:请求成功 200 201
-3xx:重定向
-4xx:客户端错误
-5xx:服务端出错
-http的响应的数据中带状态码(公司自己规定的)
{
"code": 101,
"msg": "用户名或密码错误"
}
8 响应的数据中带错误信息
{
"code": 101,
"msg": "用户名或密码错误"
}
9 返回结果,针对不同操作,服务器向用户返回的结果应该符合以下规范
GET /books:返回资源对象的列表(数组)
-[{name:金金金,price:88},{name:西游记,price:88}]
-{code:100,msg:成功,data:[{name:金金金,price:88},{name:西游记,price:88}]}
GET /books/1:返回单个资源对象
-{name:金金金,price:88} ---{code:100,msg:成功,data:{name:金金金,price:88}}
POST /books:返回新生成的资源对象
-{id:4,name:金金金,price:88} ---{code:100,msg:成功}
PUT /books/4:返回完整的资源对象
-{id:4,name:金金金,price:188} ---{code:100,msg:修改成功}
DELETE /books/4: 返回一个空文档 ---{code:100,msg:删除成功}
10 响应的结果中带url链接
Django Rest_Framework
drf(djangorestframework:drf)为django中的第三方模块,只能用在django中帮助我们,快速实现符合resful规范的接口
# book表为例,写5个接口(后面写的所有接口,都是这5个,及其变形)
-查询所有图书
-新增一本图书
-修改一本图书
-查询一本图书
-删除一本图书
- 下载安装
1.安装模块
pip3.8 install djangorestframework==稍微降版本
# 如果是django2,直接这样装,装最新drf,他们不匹配---》pip会自动把旧django卸载,安装最新版本的django,安装最新drf
# django3 ,这样没有任何问题
pip3.8 install djangorestframework
- 快速使用
# 注册
在app中注册
INSTALLED_APPS = [
'rest_framework', # 一定不要忘了加 ,
]
# 路由
from app01.views import BookView
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('books', BookView, 'books')
urlpatterns = [
]
urlpatterns += router.urls
# 视图
from .serializer import BookSerializer
from rest_framework.viewsets import ModelViewSet
class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 序列化类
from rest_framework import serializers
from .models import Book
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
- 例
# 1 book的剩余3个接口
views.py
import json
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.views import View
from app01 import models
from django.http import JsonResponse
class Bookview(View):
print('123')
@method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def get(self, request): # 查询所有数据
print('get')
books = models.Books.objects.all()
# print(books)
lis = []
for items in books:
lis.append({'title': items.title, 'price': items.price})
res = {'code': 200, 'msg': '查询成功', 'data': lis}
return JsonResponse(res)
def post(self, request): # 新增数据
print('post')
title = request.POST.get('title')
price = request.POST.get('price')
models.Books.objects.create(title=title, price=price)
books_object = models.Books.objects.all()
lis = []
for items in books_object:
lis.append({'title': items.title, 'price': items.price})
res = {'code': 200, 'msg': '查询成功', 'data': lis}
return JsonResponse(res)
class Bookdetailview(View):
@method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def get(self, request, pk): # 获取相应主键的数据
print('get')
book = models.Books.objects.filter(pk=pk).first()
# print(book)
if book:
return JsonResponse({'code': 200, 'msg': '查询成功', 'data': {book.title: book.price}})
else:
return JsonResponse({'code': 200, 'msg': '数据不存在'})
def put(self, request, pk): # 修改相应主键的数据
print('put')
try:
# b'title=ooo&price=88'
b = request.body.decode('utf8').split('&') # ['title=ooo', 'price=88']
res = {i.split("=")[0]: i.split("=")[1] for i in b} # {'title': 'ooo', 'price': '88'}
title = res.get('title')
price = res.get('price')
models.Books.objects.filter(pk=pk).update(title=title, price=price)
return JsonResponse({'code': 200, 'msg': '修改成功', 'data': res})
except:
print(request.body) #b'{"title":"hhaha","price":"99"}'
res = request.body.decode('utf8') #{"title":"hhaha","price":"99"}
title = json.loads(res).get('title')
price = json.loads(res).get('price')
models.Books.objects.filter(pk=pk).update(title=title, price=price)
return JsonResponse({'code': 200, 'msg': '修改成功', 'data': {'title': title, 'price': price}})
def delete(self, request, pk): # 删除相应主键数据
print('delete')
res = models.Books.objects.filter(pk=pk).delete() # res结果为影响的行数
if res[0] == 1: # res结果为1条则有数据
return JsonResponse({'code': 200, 'msg': '删除成功'})
else: # res结果为0则没有数据
return JsonResponse({'code': 200, 'msg': '数据不存在'})
urls.py
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('books/<int:pk>/', views.Bookdetailview.as_view()),
#转换器:str,int ,path http://127.0.0.1/books/<转化名字:变量>
path('books/', views.Bookview.as_view()),
]
序列化组件
序列化和反序列化
# api接口开发,最核心最常见的一个过程就是序列化
# 序列化: 把我们识别的数据转换成指定的格式提供给别人。
例如:我们在django中获取到的数据默认是模型对象(queryset),但是模型对象数据无法直接提供给前端或别的平台使用,所以我们需要把数据进行序列化,变成字符串或者json数据,提供给别人。
# 反序列化:把别人提供的数据转换/还原成我们需要的格式。
例如:前端js提供过来的json数据,对于python而言就是字符串,我们需要进行反序列化换成模型类对象,这样我们才能把数据保存到数据库中
# 序列化:drf称为 read 序列化
# 反序列化:drf称为 write 反序列化
序列化组件介绍
基于原生djagno写接口序列化,自己用for循环做比较麻烦,借助于drf提供的序列化组件来完成快速序列化
## 使用步骤
1 先在配置文件中注册 :
INSTALLED_APPS = [
'rest_framework',
]
2 写一个序列化类--》新建一个py文件---》serializer.py
-继承drf提供的serializers.Serializer
-在类中写要序列化的字段:字段类---》跟之前学过的models.py中的字段类完全对应,但是比models多
from rest_framework import serializers
class UserSerializer(serializers.Serializer):
name = serializers.CharField()
hobby = serializers.CharField()
password=serializers.CharField()
age=serializers.IntegerField()
3 在视图类中,使用序列化类
多条:serializer=UserSerializer(instance=users,many=True)
单条:serializer=UserSerializer(instance=user)
4 拿到序列化后的数据
serializer.data 可能是列表,可能是字典
5 使用drf提供的Resposne 返回
from rest_framework.response import Response
...
return Response(...)
快速使用
- 路由
urlpatterns = [
path('users/', UserView.as_view()),
path('users/<int:pk>', UserDetailView.as_view()),
]
- 视图类
from .models import User
from .serializer import UserSerializer
from rest_framework.response import Response
class UserView(APIView):
def get(self,request):
users=User.objects.all()
# 之前用for循环,现在用序列化类
# 传了两个参数:instance 要序列化的对象(qs,单个对象) many=True表示序列化多条,如果不写就是序列化一条
ser=UserSerializer(instance=users,many=True)
# 拿到序列化后的数据 ser.data--->多条就是列表 单条字典
"""
return JsonResponse(ser.data,safe=False)
之前我们通过使用return JsonResponse,ser.data返回的数据可能是字典、列表,而列表形式我们又需要添加safe=False较为麻烦,在drf中提供响应对象Response,需要导入from rest_framework.response import Response,使用Response后ser.data返回的任何数据则都不用再添加safe=False
"""
return Response(ser.data)
class UserDetailView(APIView):
def get(self,request,pk):
user=User.objects.all().filter(pk=pk).first()
# 传了两个参数:instance 要序列化的对象 many=True表示序列化多条
ser=UserSerializer(instance=user)
return Response(ser.data)
- 序列化类
from rest_framework import serializers
class UserSerializer(serializers.Serializer):
# 写要序列化的字段
name = serializers.CharField()
# hobby = serializers.CharField()
# password=serializers.CharField()
age=serializers.IntegerField()
常用字段类和参数
- 常用字段类
# 写序列化类的时候,写了CharField,IntegerField 跟django中models中的类似
# 序列化类中的和models中的一一对应,但是序列化类中多一些
# 多的--->暂时有个印象,后面会详细讲
ListField
DictField
# ListField\DictField使用场景
{name:金鹏没,price:99,publish:{name:xx出版社,addr:南京},authors:[{},{}]}
NullBooleanField | NullBooleanField() |
---|---|
CharField | CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True) |
EmailField | EmailField(max_length=None, min_length=None, allow_blank=False) |
BooleanField | BooleanField() |
RegexField | RegexField(regex, max_length=None, min_length=None, allow_blank=False) |
SlugField | SlugField(maxlength=50, min_length=None, allow_blank=False) 正则字段,验证正则模式 [a-zA-Z0-9-]+ |
URLField | URLField(max_length=200, min_length=None, allow_blank=False) |
UUIDField | UUIDField(format=’hex_verbose’) format: 1) 'hex_verbose' 如"5ce0e9a5-5ffa-654b-cee0-1238041fb31a" 2) 'hex' 如 "5ce0e9a55ffa654bcee01238041fb31a" 3)'int' - 如: "123456789012312313134124512351145145114" 4)'urn' 如: "urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a" |
IPAddressField | IPAddressField(protocol=’both’, unpack_ipv4=False, **options) |
IntegerField | IntegerField(max_value=None, min_value=None) |
FloatField | FloatField(max_value=None, min_value=None) |
DecimalField | DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None) max_digits: 最多位数 decimal_palces: 小数点位置 |
DateTimeField | DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None) |
DateField | DateField(format=api_settings.DATE_FORMAT, input_formats=None) |
TimeField | TimeField(format=api_settings.TIME_FORMAT, input_formats=None) |
DurationField | DurationField() |
ChoiceField | ChoiceField(choices) choices与Django的用法相同 |
MultipleChoiceField | MultipleChoiceField(choices) |
FileField | FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL) |
ImageField | ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL) |
ListField | ListField(child=, min_length=None, max_length=None) |
DictField | DictField(child=) |
- 常用字段参数
# 字段类上,可以传参数,是做反序列化校验用的
CharField:max_length,min_lenght,allow_blank: 可以不传
IntegerField:max_value,min_value
# 所有字段都可以用通用的
-非常重要:read_only,write_only
-default,required,allow_null
max_length | 最大长度 |
---|---|
min_lenght | 最小长度 |
allow_blank | 是否允许为空 |
trim_whitespace | 是否截断空白字符 |
max_value | 最小值 |
min_value | 最大值 |
参数名称 | 说明 |
---|---|
read_only | 表明该字段仅用于序列化输出,默认False |
write_only | 表明该字段仅用于反序列化输入,默认False |
required | 表明该字段在反序列化时必须输入,默认True |
default | 反序列化时使用的默认值 |
allow_null | 表明该字段是否允许传入None,默认False |
validators | 该字段使用的验证器 |
error_messages | 包含错误编号与错误信息的字典 |
label | 用于HTML展示API页面时,显示的字段名称 |
help_text | 用于HTML展示API页面时,显示的字段帮助提示信息 |
序列化组件之校验
# 序列化组件
- 序列化
- 反序列化
- 反序列化校验
# 反序列化之校验: ser.is_valid()
1 字段自己的校验规则(字段类的属性上)
2 局部钩子(给某个字段加校验规则)
3 全局钩子
# 反序列化保存
ser.save()---》必须序列化类中重写 create--》自己定保存到哪个表
def create(self, validated_data): # validated_data:前端传入,校验过后的数据
user = User.objects.create(**validated_data)
return user
视图类
from .models import User
from .serializer import UserSerializer
from rest_framework.response import Response
class UserView(APIView):
def get(self, request):
users = User.objects.all()
# 之前用for循环,现在用序列化类
# 传了两个参数:instance 要序列化的对象(qs,单个对象) many=True表示序列化多条,如果不写就是序列化一条
ser = UserSerializer(instance=users, many=True)
# 拿到序列化后的数据 ser.data--->多条就是列表 单条字典
return Response(ser.data)
def post(self, request):
# 前端提交过来的数据 request.data
ser = UserSerializer(data=request.data)
# 校验数据--》3层: 1 字段自己的校验规则(字段类的属性上) 2 局部钩子(给某个字段加校验规则) 3 全局钩子
if ser.is_valid():
# 保存
ser.save() # 会报错,序列化类中重写create方法
return Response({'code': 100, 'msg': '保存成功'})
else: # ser.errors 校验失败错误的数据
return Response({'code': 101, 'msg': ser.errors})
class UserDetailView(APIView):
def get(self, request, pk):
user = User.objects.all().filter(pk=pk).first()
# 传了两个参数:instance 要序列化的对象 many=True表示序列化多条
ser = UserSerializer(instance=user)
return Response(ser.data)
序列化类
# 写序列化类
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from .models import User
class UserSerializer(serializers.Serializer):
# 写要序列化的字段
# 字段自己
name = serializers.CharField(max_length=8, min_length=3, required=True)
hobby = serializers.CharField()
password = serializers.CharField()
age = serializers.IntegerField()
# 局部钩子写一个方法 def validate_字段名,传入要校验的数据--》前端传入的
"""针对局部钩子一个字段只能写一个方法"""
def validate_name(self, value):
if value.startswith('sb'):
raise ValidationError('不能以sb开头') # 如果校验失败,抛ValidationError
return value # 如果校验通过,返回 value,后续继续用
# 全局钩子 写一个方法名为 def validate,用于多个字段的同时校验
"""全局钩子不能写多个,当写多个def validate方法执行始终以最后的全局钩子方法为准,要想校验多种必须写在一个def validate方法中"""
def validate(self, attrs): # 前端传入的所有数据,校验过后attrs 字典
name = attrs.get('name')
hobby = attrs.get('hobby')
if name == hobby:
raise ValidationError('名字和爱好不能一样') # 如果校验失败,抛ValidationError
else:
return attrs # 如果校验通过,返回 attrs
# 重写create方法
def create(self, validated_data): # validated_data:前端传入,校验过后的数据
user = User.objects.create(**validated_data)
return user
路由
urlpatterns = [
path('users/', UserView.as_view()),
path('users/<int:pk>', UserDetailView.as_view()),
]
基于APIVIew+Response+序列化类的5个接口
- views.py
from django.shortcuts import render
# Create your views here.
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import Book
from .serializer import BookSerializer
class BookView(APIView):
# 查询所有接口
def get(self, request, *args, **kwargs):
book_list = Book.objects.all()
ser = BookSerializer(book_list, many=True)
return Response({'code': 100, 'msg': '查询成功', 'result': ser.data})
# 新增一条
def post(self, request, *args, **kwargs):
ser = BookSerializer(data=request.data)
if ser.is_valid():
ser.save() # 调用了序列化类的save:内部会触发序列化类中 create方法的执行 不是book.save()
return Response({'code': 100, 'msg': '新增成功'})
else:
return Response({'code': 100, 'msg': ser.errors})
class BookDetailView(APIView):
# 查询单条
def get(self, request, pk, *args, **kwargs):
book = Book.objects.all().filter(pk=pk).first()
ser = BookSerializer(book,many=False)
return Response({'code': 100, 'msg': '查询单条成功', 'result': ser.data})
# 修改一条
def put(self, request, pk, *args, **kwargs):
# 要用查出来的对象,使用传入的数据,做修改
book=Book.objects.filter(pk=pk).first()
ser = BookSerializer(instance=book,data=request.data) # 使用前端传入的数据,修改book
if ser.is_valid():
ser.save() # 调用了序列化类的save:内部会触发序列化类中 update方法的执行 不是book.save()
return Response({'code': 100, 'msg': '修改成功'})
else:
return Response({'code': 100, 'msg': ser.errors})
def delete(self, request, pk, *args, **kwargs):
Book.objects.filter(pk=pk).delete()
return Response({'code': 100, 'msg': '删除成功'})
- serializer.py
class BookSerializer(serializers.Serializer):
# 这两个即做序列化又做反序列化
name = serializers.CharField()
price = serializers.IntegerField()
# 这俩只做序列化
publish_dict = serializers.DictField(read_only=True) # 只做序列化
author_list = serializers.ListField(read_only=True) # 只做序列化
# 这俩只做反序列化
publish = serializers.IntegerField(write_only=True) # 反序列化
authors = serializers.ListField(write_only=True) # 反序列化
def create(self, validated_data): # {"name":"111金金金111","price":999,"publish":1,"authors":[1,2]}
book = Book.objects.create(name=validated_data.get('name'), price=validated_data.get('price'),
publish_id=validated_data.get('publish_id'))
# 增加中间表的记录:图书和作者的关系
book.authors.add(*validated_data.get('authors')) # 向中间表中存入:这个图书关联的做作者
return book
def update(self, instance, validated_data):
# Book.objects.filter(pk=instance).update(**validated_data)
# instance 待修改的对象 咱们在 view中的那个book
# validated_data 校验过后的数据 本质还是 request.data 经过了数据校验
# 方式一:
# instance.name=validated_data.get('name')
# instance.price=validated_data.get('price')
# instance.publish=validated_data.get('publish')
# instance.save()
# 方式二: 反射是通过字符串动态的获取或设置属性或方法
# get=getattr(self,'get')
# get()
# setattr(instance,'name','西游记') ---》 instance.name='西游记'
for k in validated_data: # {"name":"西游记","price":99,"publish":南京出版社}
setattr(instance, k, validated_data.get(k))
# instance.publish=validated_data.get('publish')
instance.save()
return instance
- models.py
from django.db import models
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.IntegerField()
publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)
# 多对多关系---》
# authors 写了这个字段,在数据的book表中,不会有这个字段,而它的形式是 一个中间表
authors = models.ManyToManyField(to='Author')
@property
def publish_dict(self):
return {'name': self.publish.name}
@property
def get_name(self):
return self.name + '-NB'
def author_list(self):
l = []
for author in self.authors.all():
l.append({'name': author.name, 'sex': author.sex, 'age': author.age})
return l
class Publish(models.Model):
name = models.CharField(max_length=32)
addr = models.CharField(max_length=32)
class Author(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
sex = models.CharField(max_length=32)
- urls.py
from django.contrib import admin
from django.urls import path
from app01.views import BookView, BookDetailView
urlpatterns = [
path('admin/', admin.site.urls),
path('books/', BookView.as_view()),
path('books/<int:pk>', BookDetailView.as_view()),
]
- 总结
#1.序列化类
多条一定要写many=True
#2.序列化校验
ser.is_valid()走校验
#3.反序列化保存
-新增:
ser = BookSerializer(data=request.data)
ser.save()会触发序列化类中create方法,在源码内部根据是否有instance来判断是执行create还是update方法,没有执行create方法,有则执行update方法
create中,自己写保存到哪个表中:有数据--》保存到某个表中
-修改:
ser = BookSerializer(instance=待修改对象,data=request.data)
ser.save()--->触发 序列化类中的 update---》为什么?内部做了判断:根据是否有instance
update中,有待修改对象,有数据---》修改完保存即可--》两种方式
反序列化之更新
- view.py
class BookDetailView(APIView):
# 查询单条
def get(self, request, pk, *args, **kwargs):
book = Book.objects.all().filter(pk=pk).first()
ser = BookSerializer(book,many=False)
return Response({'code': 100, 'msg': '查询单条成功', 'result': ser.data})
def put(self, request, pk, *args, **kwargs):
# 要用查出来的对象,使用传入的数据,做修改
book=Book.objects.filter(pk=pk).first()
ser = BookSerializer(instance=book,data=request.data) # 此段代码为使用前端传入的数据(data=request.data)来修改book(instance=book)
"""触发序列化类create和update的区别在于源码中判断是否有instance,有instance触发update反之触发create"""
if ser.is_valid():
ser.save() # 调用了序列化类的save:内部会触发序列化类中 update方法的执行 不是book.save()
return Response({'code': 100, 'msg': '修改成功'})
else:
return Response({'code': 100, 'msg': ser.errors})
def delete(self, request, pk, *args, **kwargs):
Book.objects.filter(pk=pk).delete()
return Response({'code': 100, 'msg': '删除成功'})
- serializer.py
class BookSerializer(serializers.Serializer):
name = serializers.CharField()
price = serializers.IntegerField()
publish = serializers.CharField()
# 重写create 前端传入,校验过后的数据validated_data
def create(self, validated_data):
book = Book.objects.create(**validated_data) # 前端传入的key键必须要与数据库中的字段名一致,否则字段打散后key键对应不上表字段名
return book
# 如果是修改,需要重写update方法
def update(self, instance, validated_data):
# instance 为待修改的对象就是在视图类中的那个book(instance=book); validated_data为校验过后的数据,本质还是request.data 经过了数据校验
# instance 就是pk 2
# Book.objects.filter(pk=instance).update(**validated_data)
# 方式一:
# instance.name=validated_data.get('name')
# instance.price=validated_data.get('price')
# instance.publish=validated_data.get('publish')
# instance.save()
# 方式二: 反射是通过字符串动态的获取或设置属性或方法
for k in validated_data: # {"name":"西游记","price":99,"publish":南京出版社}
setattr(instance, k, validated_data.get(k))
# setattr等同于:
# instance.name=validated_data.get('name')
# instance.price=validated_data.get('price')
# instance.publish=validated_data.get('publish')
instance.save()
return instance
source用法
serializers.py
class BookSerializer(serializers.Serializer):
# 用法一:最简单,拿表中的字段
xxx = serializers.CharField(source='name')
# 用法二 :跨表查询
publish = serializers.CharField(source='publish.name') # source='publish.name'自动对应成出版社的名字 可以通过 . 跨表查询
#用法三:表模型中写方法,拿到方法的返回值
yyy = serializers.CharField(source='get_name')
### models.py中
class User(models.Model):
name=models.CharField(max_length=64)
@property
def get_name(self):
return self.name+'sb'
# 没使用source参数前端看到:
{
"name": "西游记",
"price": 199,
"publish": "南京出版社"
},
# 使用用法一source参数后前端看到:
{
"xxx": "西游记",
"price": 199,
"publish": "南京出版社"
},
# 使用用法三source参数后前端看到:
{
"name": "西游记",
"price": 199,
"publish": "南京出版社"
"yyy":"西游记NB"
},
SerializerMethodField定制字段
# 定制返回的字段格式
{
"name": "信息",
"price": 12,
"publish": {name:xx,addr:xx}
}
# 方案一:使用SerializerMethodField 定制
-步骤1在序列化类中使用SerializerMethodField
publish_detail = serializers.SerializerMethodField()
-步骤2 配合步骤1在序列化类中写个方法 def get_步骤1的字段名
def get_publish_detail(self, obj): # 返回什么,序列化后publish就是什么,obj 就是序列化到的book对象
return {'name':obj.publish.name,'addr':obj.publish.addr}
# 方案二: 在表模型中定制
-步骤1 表模型中写方法,方法名与序列化类同名,包装成数据属性
@property
def publish_dict(self):#self为视图类中instance的对象
return {'name': self.publish.name}
-步骤2 序列化类中
publish_dict=serializers.DictField()
- serializers.py
class BookSerializer(serializers.Serializer):
name = serializers.CharField()
price = serializers.IntegerField()
# publish = serializers.CharField(source='publish.name')
# author = serializers.CharField(source='author.name')
# 用法一
publish_detail=serializers.SerializerMethodField()
def get_publish_detail(self,obj):
return {'name':obj.publish.name,'addr':obj.publish.addr}
# 用法二
publish_dict=serializers.DictField()
- models.py
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.IntegerField()
publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)
author=models.ManyToManyField(to='Author')
def publish_dict(self): #配合用法二使用
return {'name':self.publish.name,'addr':self.publish.addr}
- 前端看到的定制的格式
"data": [
{
"name": "王国",
"price": 28,
"publish_dict": {
"name": "南京出版社",
"addr": "南京"
}
},
多表关联序列化和反序列化
# 一个序列化类,想即做序列化,又做反序列化,会出现问题:字段不匹配,尤其是多表关联的字段
# 字段 即做序列化,又做反序列化
name = serializers.CharField()
price = serializers.IntegerField()
# 字段(定制字段):只做序列化(read_only=True表示 只做序列化)
publish_dict = serializers.DictField(read_only=True) # 只做序列化
author_list = serializers.ListField(read_only=True) # 只做序列化
# 字段只做反序列化(write_only=True);字段类型取决于前端传入的格式
publish_id = serializers.IntegerField(write_only=True) # 反序列化
authors = serializers.ListField(write_only=True) # 反序列化
"""反序列化字段也可以做局部钩子
def validate_publish_id(self,value):
pass
return value
"""
# 保存方法需要自己重写
def create(self, validated_data): # {"name":"111金金金111","price":999,"publish":1,"authors":[1,2]}
authors=validated_data.pop('authors')
book = Book.objects.create(**validated_data)
# 增加中间表的记录:图书和作者的关系
book.authors.add(*authors) # 向中间表中存入:这个图书关联的做作者
"""多对多外键增删改查 增add 删clear\remove 改set必须放容器类型列表、元组"""
return book
# 修改方法
def update(self, instance, validated_data):
author = validated_data.pop('author')
for k in validated_data:
setattr(instance, k, validated_data[k])
instance.publish_id = validated_data.get('publish_id')
instance.save()
instance.author.set(author)
return instance
反序列化校验
# 反序列化,有三层校验
-1 字段自己的(写的字段参数:required max_length 。。。)
-2 局部钩子:写在序列化类中的方法,方法名必须是 validate_字段名
def validate_name(self, name):
if 'sb' in name:
# 不合法,抛异常
raise ValidationError('书名中不能包含sb')
else:
return name
-3 全局钩子:写在序列化类中的方法 方法名必须是 validate
def validate(self, attrs):
price = attrs.get('price')
name = attrs.get('name')
if name == price:
raise ValidationError('价格不能等于书名')
else:
return attrs
# 只有三层都通过,在视图类中:
ser.is_valid(): 才是True,才能保存
视图组件
APIView
#1 使用drf,以后都写cbv---》继承一个视图类---》以后都继承drf提供 的APIView
#2 APIView 继承了djagno的View
#3 补充:这三个都是一个
from django.views import View
from django.views.generic import View
from django.views.generic.base import View
# 4 继承APIView写cbv---》执行起来跟之前效果一样---》内部发生了非常大的变化
1 写视图类
from rest_framework.views import APIView
class PublishView(APIView):
def get(self, request):
return JsonResponse({'code': 999})
2 配置路由
path('publish/', PublishView.as_view()),
3.配置文件注册
INSTALLED_APPS = [
'rest_framework',]
基于APIView的5个接口
- 视图类
from rest_framework.views import APIView # APIView继承了djagno原来的View
from .serializer import BookSerializer
from rest_framework.response import Response
class BookView(APIView):
# 查询所有
def get(self, request):
book_list = Book.objects.all()
# drf提供了序列化类(先别关注)
ser = BookSerializer(instance=book_list, many=True) # 序列化
return Response({'code': 100, 'msg': '成功', 'result': ser.data})
def post(self, request):
ser = BookSerializer(data=request.data) # 反序列化
if ser.is_valid(): # 数据校验---》有些不合法的禁止
ser.save() # 保存到数据库中
return Response({'code': 100, 'msg': '成功'})
class BookDetailView(APIView):
# 查询单条
def get(self, request, pk):
book = Book.objects.filter(pk=pk).first()
ser = BookSerializer(instance=book, many=False) # 序列化
return Response({'code': 100, 'msg': '成功', 'result': ser.data})
# 修改一条
def put(self, request, pk):
book = Book.objects.filter(pk=pk).first()
ser = BookSerializer(instance=book, data=request.data) # 反序列化
if ser.is_valid(): # 数据校验---》有些不合法的禁止
ser.save() # 保存到数据库中
return Response({'code': 100, 'msg': '成功'})
def delete(self, request, pk):
Book.objects.filter(pk=pk).delete()
return Response({'code': 100, 'msg': '删除成功'})
- 序列化类
from rest_framework import serializers
from .models import Book
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
- 路由
urlpatterns = [
path('books/', BookView.as_view()),
path('books/<int:pk>/', BookDetailView.as_view()),
]
CBV源码分析
# cbv写法:
1 视图中写视图类,继承View,写跟请求方式同名的方法
class BookView(View):
def get(self,request):
return 四件套
2 在路径用写
path('books/', BookView.as_view())
# 如上写法,为什么能够执行
# 前置条件:前端请求,一旦路径匹配成功,就会执行 BookView.as_view()(request传入,)
# 入口在 BookView.as_view()--->执行结果---》View中有个as_view类的绑定方法
@classmethod
def as_view(cls, **initkwargs):
def view(request, *args, **kwargs):
self = cls(**initkwargs)
res=self.dispatch(request, *args, **kwargs)
return res
return view
# 执行结果是view 的内存地址: 请求来了,执行view(request)
path('books/', view)
# 执行 View类中的as_view方法中的内层的view函数,路由匹配成功,本质是在执行
self.dispatch(request, *args, **kwargs)
# self是谁的对象?BookView的对象
# 去BookView中dispatch,找不到,去父类,View中找到了
# View这个类的dispatch
def dispatch(self, request, *args, **kwargs):
# request.method.lower() 如果是get请求, ‘get’ 在这个列表里面
if request.method.lower() in self.http_method_names:
# handler=getattr(BookView的对象,'get')
# handler就是BookView类中的get方法
handler = getattr(self, request.method.lower())
else:
handler = self.http_method_not_allowed
# 执行 BookView类中的get方法 (request)
return handler(request, *args, **kwargs)
# 最终本质跟写fbv的执行流程一样
# 最终结论:什么请求方式,就会执行视图类中的什么方法
APIView源码分析
# 有了drf,后期都写CBV,都是继承APIView及其子类
# 执行流程:
-入口:path('books/', BookView.as_view())---》请求来了,执行BookView.as_view()(request)
-as_view 是谁的? APIView的as_view
@classmethod
def as_view(cls, **initkwargs):
# super()代指的是:父类对象 View类的对象
# View的as_view(**initkwargs)----》执行结果是view,是View类的as_view方法中的view
view = super().as_view(**initkwargs)
view=csrf_exempt(view) # 局部禁用csrf,
return view
-path('books/', View类的as_view中的view,只是去掉了csrf的认证)
-请求来了,执行 【View类的as_view中的view,只是去掉了csrf的认证(request)】
-执行:self.dispatch(request, *args, **kwargs), self要从根上找
-self.dispatch 是APIView的dispatch,源码如下
def dispatch(self, request, *args, **kwargs):
# request 是新的request, request是老的request
request = self.initialize_request(request, *args, **kwargs)
self.request = request
try:
# 执行了认证,权限和频率
self.initial(request, *args, **kwargs)
# 在执行视图类方法之前,去掉了csrf认证,包装了新的request,执行了认证频率和权限
#### 执行请求方式字符串对应的方法
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
response = handler(request, *args, **kwargs)
except Exception as exc:
response = self.handle_exception(exc)
# 无论是在三大认证,还是视图类的方法中,出现错误,都会被异常捕获,统一处理
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
# 总结:
1 以后只要继承APIView的所有视图类的方法,都没有csrf的校验了
2 以后只要继承APIView的所有视图类的方法 中的request是新的request了
3 在执行视图类的方法之前,执行了三大认证(认证,权限,频率)
4 期间除了各种错误,都会被异常捕获,统一处理