drf入门规范
一 Web应用模式
在开发Web应用中,有两种应用模式:
1.1 前后端混合
后端人员,既要写后端,django,又要写前端。只能在网页中使用,不能在小程序、APP中使用,扩展性差。
重要的问题:
模板的渲染是在哪里完成的:前端的模板语法是经过后端渲染成功后,组成一个个字符串(html、css样式)后交给前端来渲染页面。
1.2 前后端分离
-后端人员,只写后端,写一个个的API接口
-前端人员,只写前端页面
-最后项目写完,前后端联调
前后端分离项目是:
前端人员只写页面,返回一个个没有数据的空页面。前端用ajax请求后端的数据,用js把一个个数据插入到对应的位置。
后端人员是只写后端,并且写一个个API接口(eg:BBS中的点赞点踩函数)
二 API接口
为了在团队内部形成共识、防止个人习惯差异引起的混乱,我们需要找到一种大家都觉得很好的接口实现规范,而且这种规范能够让后端写的接口,用途一目了然,减少双方之间的合作成本。
通过网络,规定了前后台信息交互规则的url链接,也就是前后台信息交互的媒介,它称之为API接口
-前端:向后端发送请求,获取数据 127.0.0.1:8080/index --->返回数据
-后端:请求某个地址,返回固定的数据
四大特点
Web API接口简单概括有下面四大特点:
-
url:长得像返回数据的url链接
- 127.0.0.1:8080/index
- https://api.map.baidu.com/place/v2/search
-
请求方式:get、post、put、patch、delete
-
请求参数:
-
get请求参数放在问号后面:127.0.0.1:8080/books?name=红楼梦
-
post请求参数放在请求体中:json或xml格式的key-value类型数据
- ak:6E823f587c95f0148c19993539b99295
- region:上海
- query:肯德基
- output:json
- 响应结果:一般是json格式,也可能是xml
总结:向某个地址发送GET请求方式带一些参数,有返回结果,就是API接口。
三 接口测试工具:Postman
写好的接口,要测试,可以使用浏览器来做,但是浏览器只能发送get请求,接口有其他请求方式
专门的接口测试工具(使用习惯,大致一样)
- postman,大部分公司用的,原来免费, 后来收费了,但是咱们只用免费部分就够了
- postwomen
- apifox
Postman可以直接从官网:https://www.getpostman.com/downloads/下载获得,然后进行安装。
Postman介绍
postman工具的使用:https://blog.csdn.net/zong596568821xp/article/details/123395046
- 界面导航说明
- 接口响应
响应数据是发送请求后经过服务器处理后返回的结果,响应由三部分组成,分别是状态行、响应头、响应体。我们来看下postman的响应数据展示。
- 注意:在postman中写地址一定要加上/
post请求的不同编码格式
post请求编码格式有多种,主流是3种。
-urlencoded 格式 ---> 默认格式 b'xx=xx&yy=yy'
-form-data 格式 ---> 传文件格式,也可以带数据
----------------------------251971281264627087498320- - 带数据,带文件
-json格式(用的最多)-->只能传数据 b'{"name":"lqz","age":18}'
写不同的接口,尝试用不同的编码格式。
路由:使用django1版本
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
# get请求
url(r'^index/', views.index),
# post请求路由
url(r'^post_func/', views.post_func),
url(r'^post_form/', views.post_form),
url(r'^post_json/', views.post_json),
]
get请求的视图函数
from django.shortcuts import render
# Create your views here.
from django.http import JsonResponse
def index(request):
# get请求
username = request.GET.get('username')
age = request.GET.get('age')
print(username)
print(age)
return JsonResponse({'book': '红楼梦', 'price': 999})
post请求,默认编码格式是urlencode
def post_func(request):
# post请求,默认编码格式是urlencode
name = request.POST.get('postname')
age = request.POST.get('age')
print(name)
print(age)
return JsonResponse({'request_method': 'post', 'code': 'urlencoded'})
"""
结果是:
xxx
20
[15/May/2023 16:17:59] "POST /post_func/ HTTP/1.1" 200 48
"""
post请求,编码格式是form-data,可以上传文件
def post_form(request):
# post请求,编码格式是form-data,可以上传文件
name = request.POST.get('postdata')
age = request.POST.get('age')
file_obj = request.FILES.get('myfile')
print(name)
print(age)
print(file_obj, type(file_obj)) # token运行原理图片.png <class 'django.core.files.uploadedfile.InMemoryUploadedFile'>
with open(file_obj.name, 'wb') as f:
for line in file_obj:
f.write(line)
return JsonResponse({'request_method': 'post', 'code': 'form-data'})
"""
结果是:
data
22
token运行原理图片.png
[15/May/2023 16:24:23] "POST /post_form/ HTTP/1.1" 200 47
"""
post请求,编码格式是json,数据在request.body中
def post_json(request):
# post请求,编码格式是json,数据在request.body中
body = request.body # b'{"json_name":"json111", "age": 20}'
body_de = body.decode('utf-8')
import json
body_dic = json.loads(body_de)
print(body_dic.get('json_name'))
print(body_dic.get('age'))
return JsonResponse({'request_method': 'post', 'code': 'json'})
"""
结果是:
[15/May/2023 16:46:13] "POST /post_json/ HTTP/1.1" 200 42
json111
20
"""
四 RESTful API规范
RESTful是一种定义API接口的设计风格,AIP接口的编写规范,,尤其适用于前后端分离的应用模式中
这种风格的理念认为后端开发任务就是提供数据的,对外提供的是数据资源的访问接口,所以在定义接口时,客户端访问的URL路径就表示这种要操作的数据资源
我们可以使用任何一个框架都可以实现符合restful规范的API接口
4.1 数据的安全保障
-
url链接一般都采用https协议进行传输
注:采用https协议,可以提高数据交互过程中的安全性
4.2 url地址中带接口标识:一般这样
用api关键字标识接口url:
-
注:看到api字眼,就代表该请求url链接是完成前后台数据交互的
4.3 多数据版本共存
-
url地址中带版本信息
-
https://api.baidu.com/v2/login/
注:url链接中的v1、v2就是不同数据版本的体现(只有在一种数据资源有多版本情况下)
4.4 数据即是资源,均使用名词(可复数)
-
url地址尽量使用名词。接口一般都是完成前后台数据的交互,交互的数据我们称之为资源
-
注:一般提倡用资源的复数形式,在url链接中奖励不要出现操作资源的动词,错误示范:https://api.baidu.com/delete-user
-
特殊的接口可以出现动词,因为这些接口一般没有一个明确的资源,或是动词就是接口的核心含义
4.5 资源操作由请求方式决定(method)
- 操作资源一般都会涉及到增删改查,我们提供请求方式来标识增删改查动作
- 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的书
4.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 :指定筛选条件
4.7 响应状态码
4.7.1 http的响应状态码
-
请求正在处理:1xx
-
正常响应:2xx
- 200:常规请求
- 201:创建成功
-
重定向响应:3xx
- 301:永久重定向
- 302:暂时重定向
-
客户端错误:4xx
- 403:请求无权限
- 404:请求路径不存在
- 405:请求方法不存在
-
服务器异常:5xx
- 500:服务器异常
4.7.2 http的响应的数据中带状态码(公司自己规定的)
{code:100}
MySQLmysql自己规定了自己的状态码,以后发生错误,就可以直接使用状态码来搜索解决方式,这样比较准确。
4.8 返回的数据中带错误信息
{code:101,massige:用户名或密码错误}
{code:100,msg:成功}
4.9 返回结果,针对不同操作,服务器向用户返回的结果应该符合以下规范
- 返回的数据是个字符串,只是有不同的规范。
* GET /collection:返回资源对象的列表(数组)
- [{name:红楼梦,price:88},{name:西游记,price:88}]
* GET /collection/resource:返回单个资源对象
- \{name:红楼梦,price:88\}
* POST /collection:返回新生成的资源对象
- {id:4,name:红楼梦,price:88}
* PUT /collection/resource:返回完整的资源对象
- {id:4,name:红楼梦,price:188}
* DELETE /collection/resource:返回一个空文档
我们自己的规范是:
* GET /books:
- {code:100,msg:成功,data:[{name:红楼梦,price:88},{name:西游记,price:88}]}
* GET /books/1:
- {code:100,msg:成功,data:{name:红楼梦,price:88}}
* POST /books:
- {code:100,msg:成功}
* PUT /books/4:
- {code:100,msg:修改成功}
* DELETE /books/4:
- {code:100,msg:删除成功}
4.10 需要url请求的资源需要访问资源的请求链接
# Hypermedia API,RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么
{
"status": 0,
"msg": "ok",
"results":[
{
"name":"肯德基(罗餐厅)",
"img": "https://image.baidu.com/kfc/001.png"
}
...
]
}
比较好的接口返回
# 响应数据要有状态码、状态信息以及数据本身
{
"status": 0,
"msg": "ok",
"results":[
{
"name":"肯德基(罗餐厅)",
"location":{
"lat":31.415354,
"lng":121.357339
},
"address":"月罗路2380号",
"province":"上海市",
"city":"上海市",
"area":"宝山区",
"street_id":"339ed41ae1d6dc320a5cb37c",
"telephone":"(021)56761006",
"detail":1,
"uid":"339ed41ae1d6dc320a5cb37c"
}
...
]
}
四 序列化和反序列化
api接口开发,最核心最常见的一个过程就是序列化
- 后端
# 序列化: 把我们识别的数据转换成指定的格式提供给别人。
- 例如:我们在django中获取到的数据默认是模型对象(queryset),但是模型对象数据无法直接提供给前端或别的平台使用,所以我们需要把数据进行序列化,变成字符串或者json数据,提供给别人。
# 反序列化:把别人提供的数据转换/还原成我们需要的格式。
- 例如:前端js提供过来的json数据,对于python而言就是字符串,我们需要进行反序列化换成模型类对象,这样我们才能把数据保存到数据库中
- 前端
序列化:drf称为 read 序列化,拿数据的过程,后端从数据库中查询,查询之后给我前端,后端把数据序列化之后给到我前端,我来读取(read)数据,称之为序列化
反序列化:drf称为 write 反序列化,我从前端提交了一些数据,要保存到,写到(write)数据库中,称之为反序列化。
五 Django Rest_Framework
5.1 使用原生的django写图书的增删改查
# book表为例,写5个接口(后面你写的所有接口,都是这5个,及其变形)
-查询所有图书
-新增一本图书
-修改一本图书
-查询一本图书
-删除一本图书
5.1.1 创建模型操作类,models.py
from django.db import models
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.IntegerField()
数据库迁移命令:
python38 manage.py makemigrations
python38 manage.py migrate
5.1.2 路由:
from django.urls import path
from app01 import views
urlpatterns = [
path('books/', views.BookView.as_view()),
path('books/<int:pk>/', views.BookDetailView.as_view()),
]
5.1.3 视图views.py
from django.http import JsonResponse
from django.views import View
from . import models # 一个点代表当前目录(即当前app01目录)
import json
# 视图类
class BookView(View):
back_dic = {'code': 200, 'msg': ''}
def get(self, request):
# 查询所有
book_obj = models.Book.objects.all()
# 操作数据表
# 定义一个空列表,循环每个queryset对象,加到新的空列表中
book_list = []
for obj in book_obj:
book_list.append({'name': obj.name, 'price': obj.price})
# 修改返回信息
self.back_dic['msg'] = '查询所有成功'
self.back_dic['data'] = book_list
return JsonResponse(self.back_dic)
# 路径一致,就可以写在同一个视图类中
def post(self, request):
# 新增一条记录
name = request.POST.get('name')
price = request.POST.get('price')
# print(name, price)
# 操作数据表
models.Book.objects.create(name=name, price=price)
# 修改返回信息
self.back_dic['msg'] = '新增一条成功'
self.back_dic['data'] = {'name': name, 'price': price}
return JsonResponse(self.back_dic)
class BookDetailView(View):
back_dic = {'code': 200, 'msg': ''}
def get(self, request, pk):
# 查询单条数据
book = models.Book.objects.filter(pk=pk).first()
print(book)
# 修改返回信息
self.back_dic['msg'] = '查询一条成功'
self.back_dic['data'] = {'name': book.name, 'price': book.price}
return JsonResponse(self.back_dic)
def put(self, request, pk):
# 修改一条数据,使用put请求方式,使用json格式提交数据
# put 请求urlencode提交的数据,在request.POST取不到数据
print(request.POST) # <QueryDict: {}>
# 反序列化数据
dic = json.loads(request.body.decode('utf-8')) # {'name': '红楼梦新版', 'price': 999}
# 操作数据表
book_obj = models.Book.objects.filter(pk=pk).update(name=dic.get('name'), price=dic.get('price'))
print(book_obj) # 1 # ????????????????????????????
# 修改返回信息
self.back_dic['msg'] = '修改一条成功'
self.back_dic['data'] = dic
return JsonResponse(self.back_dic)
def delete(self, request, pk):
# 操作数据表
models.Book.objects.filter(pk=pk).delete()
# 修改返回信息
self.back_dic['msg'] = '删除记录成功'
return JsonResponse(self.back_dic)
5.2 drf介绍
django 中自带的app,djangorestframework:drf,帮助我们,快速实现符合resful规范的接口(只能在django框架中使用)
下载
pip3.8 install djangorestframework==稍微降版本(使用django2)
# 如果你是django2, 直接这样装,装最新drf,他们不匹配---> pip会自动把django卸载,安装最新django,安装最新drf
# django3 ,这样没有任何问题,就可以安装最新版本
pip3.8 install djangorestframework --upgrade # 强制更新
# 注意:
-python和pip是成对的
-如果装了多版本python一定要注意这个pip是跟谁一对
# 补充一点点东西:
-如果写了一个包,或app,想给别人用---> 把你写的包,放到pypi上别人pip install 安装--> 使用
5.3 drf快速使用(不需要会)
这个是终极写法,现在只需了解,最后学完drf会写即可。
5.3.1 定义路由
from app01.views import BookView
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('books', BookView, 'books')
urlpatterns = [
]
urlpatterns += router.urls
5.3.2 视图函数
from .serializer import BookSerializer
from rest_framework.viewsets import ModelViewSet
class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 5个接口只写一个函数类就行了
5.3.3 序列化类
在app01应用目录中新建serializers.py用于保存该应用的序列化器。
创建一个BookSerializer用于序列化与反序列化。
# 创建序列化器类,回头会在试图中被调用
from rest_framework import serializers
from .models import Book
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
5.4 基于APIView的5个接口(drf提供的)
序列化类serializer.py
from rest_framework import serializers
from .models import Book
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
路由
from django.urls import path
from app01 import views
urlpatterns = [
path('books/', views.BookView.as_view()),
path('books/<int:pk>/', views.BookDetailView.as_view()),
]
视图类:
from .models import Book # 一个点代表当前目录(即当前app01目录)
from rest_framework.views import APIView
from .serializer import BookSerializer # 序列化类
from rest_framework.response import Response
# drf中的views.py文件中有个APIView类
# APIView+序列化类+Response写5个接口
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': '修改数据成功', 'data': ser.data})
def delete(self, request, pk):
# 删除单条
Book.objects.filter(pk=pk).delete()
return Response({'code': 100, 'msg': '删除成功'})
六 源码分析
导入View的不同方式
from django.views import View
from django.views.generic import View
from django.views.generic.base import View
# View类的具体位置就在:django包下的views包下的generic包下的base.py文件中
# 其他方式为什么能够导入?
1. views的__init__.py文件中直接导入了View类
from django.views.generic.base import View
__all__ = ['View']
2. generic的__init__.py文件也导入了View类
from django.views.generic.base import View
__all__ = ['View']
6.1 CBV源码分析
# cbv写法:
1 视图中写视图类,继承View,写跟请求方式同名的方法
class BookView(View):
def get(self,request):
return 四件套(render,reidrect,HttpResponse, JsonResponse)
2 在路由中写
path('books/', BookView.as_view()) # 一定要加括号
# 如上写法,为什么能够执行
# 前置条件:前端请求,一旦路径匹配成功,就会执行 BookView.as_view()(request传入,)
# 1.入口在 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
# 2.执行结果是view 的内存地址: 请求来了,执行view(request)
path('books/', view)
# 3.执行 View类中的as_view方法中的内层的view函数,路由匹配成功,本质是在执行
self.dispatch(request, *args, **kwargs)
# self是谁的对象?BookView的对象
# 去BookView中dispatch,找不到,去父类,View中找到了
# 4.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的执行流程一样
# 最终结论:什么请求方式,就会执行视图类中的什么方法
# 补充:
1.path()是个函数,一旦路径匹配成功,就会调用第二个参数加括号执行,并把第二个参数传入进去。
2.反射,getattr(对象, '字符串'),根据字符串动态的去对象中找属性或者方法,如果能找到直接返回属性值或函数内存地址。不存在,有第三个参数返回第三个参数,没有就报错。
6.2 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类的对象
# super()在哪里就从那个类开始找
# View的as_view(**initkwargs)----> 执行结果是view,是View类的as_view方法中的view
view = super().as_view(**initkwargs)
view=csrf_exempt(view) # 局部禁用csrf,跟加上csrf_exempt装饰器的作用是一样的
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,(django.core.handlers.WSGIRequest)
# 等号左边的request 是新的request,(rest_framework.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
# 真正的执行自己类中的get()方法
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.fbv,局部禁用csrf,如何写?
@csrf_exempt
def index(request):
pass
本质原理是(装饰器本质):index=csrf_exempt(index)
2.闭包函数的定义(闭包函数跟语言无关)
- 定义在函数内部
- 对外部作用域有引用
总结:
# 总结:
1 以后只要继承APIView的所有视图类的方法,都没有csrf的校验了
2 以后只要继承APIView的所有视图类的方法 中的request是新的request了
3 在执行视图类的方法之前,执行了三大认证(认证,权限,频率)
4 期间除了各种错误,都会被异常捕获,统一处理
源码跳转比较快,找不见原来的位置,就需要显示工具栏