restful 规范与 drf 的安装使用 & drf 中 APIView 源码分析
内容概要
- web 开发模式
- API 接口
- postman 测试软件的使用
- restful 规范
- drf 的安装与使用
- cbv 的 View 源码分析
- APIView 源码分析
- drf 的 Request 类
- drf 的 APIView 类执行过程
内容详细
web 开发模式
1、前后端不分离
在开发一个网站的过程中,前端页面需要使用后端框架的模板语法(DTL) 来渲染,比如 Django 自带的模板语法或者 jinjia2,这种前端页面在后端渲染完成之后才会把页面文档传送给前端
2、前后端分离
目前主流的开发模式,这种模式中前端先在后端的静态文件服务器(nfs)中获取静态文件(static 中的HTML、CSS、JS代码),需要数据的时候向后端发送请求只获取json格式的数据,再在前端运用js的BOM与DOM操作对页面进行渲染。
这样便实现了前后端分离,前端只需要从后端获取一定格式的数据,那么前端就可以是一个网页、app或者小程序,提高开发水平。
API 接口
简介: api 接口是前后端交互数据的媒介
api 接口包含:
- 1、url 地址: 向后端请求数据的地址
- https://api.example.com
- 2、请求方式 : get、post、put、patch、delete
- 3、请求参数:在 url 地址后面紧跟着的 ?name=”elijah”,可以看作是过滤条件
- https://api.example.com/v1/zoos?limit=10
- 4、响应结果:包括响应首行(响应状态码)、响应头、响应体(json格式数据)
postman 测试软件的使用
过去我们开发网站开放出来的接口一般使用浏览器发送get请求来进行测试,但基于api接口的restful设计规范,我们还需要使用 post请求、put请求等,而浏览器只能发送 get请求
postman 是一种测试接口共组,在 postman 中可以满足我们的测试需求
市面上也有许多测试软件(postwoman),我们使用主流的postman
下载地址: https://www.getpostman.com/downloads/
使用:
1、发送请求
前后端数据交互的编码格式:
- urlencoded:正常的post请求提交数据:name=elijah
- formdata:post请求上传文件:带文件二进制形式
- json:在body体中的数据格式为:{“name”:”elijah”,”age”:18} (用的多)
发起请求时,可以在 body 中书写请求体:
2、建立集合批量发起测试
同时,这个批量测试也支持导出文件,然后发送给别人,再从文件中导出
restful 规范
REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”或“表现层状态转化”。它首次出现在2000年Roy Fielding的博士论文中。
在前后端分离的开发模式中,为了方便数据交互,我们在设计前后端进行数据交互的 api 接口时,需要符合 restful 规范,它是一种写前后端分离的标准
对于REST这种面向资源的架构风格,有人提出一种全新的结构理念,即:面向资源架构(ROA:Resource Oriented Architecture)
10条 restful 规范:
1、通信协议一般使用 https 协议,为保障数据安全
2、设计的接口地址是可以让人一眼看出来是 api 接口(在接口中带api字眼)
- https://api.baidu.com/books
- https://127.0.0.1:8080/api/books
3、多数据版本共存
接口更新之后,老版本的接口依旧一起使用,等过了期限才弃用老版本(维护老版本接口),以便可以留存更多用户。
- https://api.baidu.com/v1/login
- https://api.baidu.com/v2/login
4、交互数据即是资源,使用名词(可以是复数)
- https://api.baidu.com/v1/users
- https://api.example.com/v1/animals
不建议使用动词,如 get_resource ,除非是 login 或者 register 这种接口
- https://api.baidu.com/v2/login
5、用不同的请求方式表示不同的资源操作
- get: 获取资源
- post: 新增资源
- put: 修改资源(全局修改)
- patch: 修改资源(局部修改)
- delete: 删除资源
6、过滤,通过在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:指定筛选条件
7、响应状态码(http的响应状态码,响应体的json数据中带状态码)
http 协议的状态码:
更多状态码请参考: https://www.cnblogs.com/elijah-li/p/16069638.html
1xx:请求正在处理
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 - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
更多状态码请参考: https://www.cnblogs.com/elijah-li/p/16069638.html
服务端自定义的状态码
响应体中返回json格式数据,数据中有 服务端自定义的状态码(code,status)
- 1001 :用户名错误
- 1002 :没有权限
8、返回错误信息(错误处理)
返回数据是json格式,带状态码和错误信息
{
code: 1002
error: "Invalid API key"
}
9、不同的请求方式(不同操作)应该返回不同的资源
- get:/collection/resources 获取多个资源 –》 返回资源对象的列表 [{name:elijah,age:18},{name:pyy,age:33}]
- get /collection/resource 返回单个资源对象 {name:elijah,age:18}
- post: 新增资源 –》 返回新增的资源对象 {name:elijah,age:18}
- put: 修改资源(全局修改) –》 返回修改的资源对象 {name:elijah,age:18}
- patch: 修改资源(局部修改) –》 返回修改的资源对象 {name:elijah,age:18}
- delete: 删除资源 –》 返回空文档(或者错误文档) {code:100,msg:删除成功}
10、返回资源中要跳转的链接地址
{"link": {
"rel": "collection https://www.example.com/zoos",
"href": "https://api.example.com/zoos", """连接地址"""
"title": "List of zoos",
"type": "application/vnd.yourformat+json"
}}
drf 的安装与使用
1、安装 djangorestframework
pip install djangorestframework==3.10.3
2、在 settings.py 文件中 注册 rest_framework
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"app01.apps.App01Config",
"rest_framework",
]
3、在 models.py 中写表模型
class Book(models.Model):
name = models.CharField(verbose_name="书名", max_length=32)
price = models.DecimalField(verbose_name="价格", max_digits=5, decimal_places=2)
author = models.CharField(verbose_name="作者", max_length=32)
class Meta:
verbose_name_plural = "书籍表"
4、新建一个序列化类
在应用文件夹下创建一个 ser.py 作为序列化模块
from rest_framework.serializers import ModelSerializer
from app01.models import Book
class BookModelSerializer(ModelSerializer): # 继承的是 ModelSerializer
class Meta:
model = Book
fields = "__all__" # 注意别漏写 fields
5、书写视图函数(CBV)
from rest_framework.viewsets import ModelViewSet
from .ser import BookModelSerializer, Book
# Create your views here.
class BookModelViewSet(ModelViewSet): # 继承的是 ModelViewSet
queryset = Book.objects.all()
serializer_class = BookModelSerializer
6、书写 url
from app01 import views
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register("books", views.BookModelViewSet)
urlpatterns = [
path("admin/", admin.site.urls),
]
urlpatterns += router.urls
9、迁移数据库
python manage.py makemigrations
python manage.py migrate
10、在浏览器中测试接口
11、在 postman 中测试接口
注意: 在发送 put 请求(修改资源)、patch 请求(修改资源)、delete 请求(删除资源)时,得在名词后面添加具体的资源 id 值,并且要加上 “/” ,因为 postman 不会自动加斜杆
获取资源:
新增资源:
修改资源:
删除资源:
cbv 的 View 源码分析
从路由文件 urls.py 的调用语句中看起
urlpatterns = [
path("CBV/", views.MyClass.as_view()),
]
# as_view() 函数加括号优先调用 as_view函数
按住 ctrl 键,鼠标点击查看 as_view 源码
@classonlymethod
def as_view(cls, **initkwargs):
def view(request, *args, **kwargs):
self = cls(**initkwargs) # self 是我们自定义类产生的对象
if hasattr(self, "get") and not hasattr(self, "head"):
self.head = self.get
self.setup(request, *args, **kwargs)
if not hasattr(self, "request"): # 自定义类必须接收 request 对象,否则主动报错
raise AttributeError(
"%s instance has no "request" attribute. Did you override "
"setup() and forget to call super()?" % cls.__name__
)
return self.dispatch(request, *args, **kwargs) # view 函数调用 View 类中的dispath函数
# ... 中间省略部分源码
return view # 返回view函数对象
从源码中可以看出,as_view 调用之后得到的是 view 函数对象,这是个闭包函数,当视图函数被触发,调用的是 view函数,view函数return一个 dispatch 函数调用结果
我们再查看 dispatch 的源码:
http_method_names = ["get", "post", "put", "patch", "delete", "head", "options", "trace"]
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn"t exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn"t on the approved list.
if request.method.lower() in self.http_method_names: # 如果是request中的属性,则用反射调用自身的响应的函数
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
APIView 源码分析
APIView 是 rest_framework.views 中的一个类,是 drf 提供给我们更多功能的视图函数类,也继承了 Django 的 View
基于 CBV 先定义一个视图函数
views.py :
from rest_framework.views import APIView
class BookAPIView(APIView):
def get(self, request):
pass
def post(self, request):
pass
urls.py :
urlpatterns = [
path("admin/", admin.site.urls),
path("book/",views.BookAPIView.as_view()) # 调用 as_view() 返回的是 view 函数的内存地址
]
调用的是 APIView 的 as_view 方法:
APIView 类继承的是 Django 的 View,所以 super().as_view(**initkwargs) 调用了父类(View)中的 as_view,返回的是 View 中 as_view 返回的 view,在 view 函数里调用的 self.dispatch(request, *args, kwargs) 根据类的查找顺序,调用的就是 APIView 中的 dispatch
class APIView(View):
@classmethod
def as_view(cls, **initkwargs):
view = super().as_view(**initkwargs)
view.cls = cls
view.initkwargs = initkwargs
return csrf_exempt(view) # 所有继承了 APIView 的视图函数都忽略了 csrf 认证
- 调用了 APIView 的 dispatch方法
- 忽略了视图函数的 csrf 认证
csrf_exempt(index) 相当于
@csrf_exempt
def index(request)
pass
# index=csrf_exempt(index)
APIView 中的 dispatch:
def dispatch(self, request, *args, **kwargs):
# 重写了 Django 传过来的 request 请求数据对象,给request增添了新属性
request = self.initialize_request(request, *args, **kwargs)
self.request = request
try:
# 进行三大认证
self.initial(request, *args, **kwargs)
# 反射机制调用视图函数类中的方法,与原生 dispatch 一致
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)
# 处理放回数据,具有 drf 特色的页面
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
APIView 中的 initialize_request:
对原生的 request 进行处理,新增了一些功能和属性
def initialize_request(self, request, *args, **kwargs):
return Request(
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
initialize_request 中的 Request():
把原生的 request 赋给了 _request,原 request 请求对象的方法在 _request 中获取就可以
class Request:
def __init__(self, request, parsers=None, authenticators=None,
negotiator=None, parser_context=None):
self._request = request
self._data = Empty
request 的变化
新的:<class "rest_framework.request.Request">
老的:<class "django.core.handlers.wsgi.WSGIRequest">
三大认证:
# 三大认证人如何走的
self.initial(request, *args, **kwargs)---》APIView的
核心代码:
self.perform_authentication(request) # 认证
self.check_permissions(request) #权限
self.check_throttles(request) # 频率
drf 的 Request 类
重写了 request 请求数据对象之后,原生的 request 方法一样能用,因为 Request 类中的 getattr 方法(对象的 “.” 点拦截)中用放射机制获取了原生 request 的所有属性
Request --> __getattr__
def __getattr__(self, attr):
try:
return getattr(self._request, attr)
except AttributeError:
return self.__getattribute__(attr)
前端post请求传入的数据,在原来的request.POST中只能处理urlencoded和formdata编码格式,json格式不能处理。
而 Request 类实例化之后,提供了一个 data 方法,无论前端用什么编码post提交的数据,都从 request.data 中获取。
@property
def data(self):
if not _hasattr(self, "_full_data"):
self._load_data_and_files()
return self._full_data