drf入门规范
一、Web一能用模式
- 前后端混合开发(前后端不分离:全栈):返回来的是html的内容,需要自己写模板
- 前后端分离:只专注于写后端接口,返回的是json、xml(过时)格式的数据
- 前后端分离需要中间媒介:api接口
二、API接口
- 通过网络,规定前后台信息交互规则的url链接
- 就是前后端交互的媒介,双方协议好传输数据的格式
三、Restful规范(重点)
# REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征性状态转移)。 它首次出现在2000年Roy Fielding的博士论文中。
# uri:资源唯一标识(抽象概念)
# url:资源唯一定位(具体实现)
RESTful是Web API接口的设计规范风格,这种风格的理念认为后端开发任务就是提供数据的,对外提供的是数据资源的访问接口,尤其适用于前后端分离的应用模式中。
所以在定义接口时,客户端访问的URL路径就表示这种要操作的数据资源。
其实,任何一个框架都可以实现符合restful规范的API接口。
十条规范
1.数据的安全保障:
url链接一般都是采用哪个https协议进行传输
注:采用https协议,可以提高数据交互过程中的安全性
2.接口特征表现,一看看就知道是以api接口
用api关键字标识接口url
https://www.baidu.com/api
[https://api.baidu.com](https://api.baidu.com/)
注:看到api字眼,就代表该请求url链接是完成前后台数据交互的
3.多数据版本共存
在url链接中标识数据版本
https://api.baidu.com/v1
https://api.baidu.com/v2
注:url链接中的v1、v2就是不同数据版本的体现(只有在一种数据资源有多版本情况下)
4 数据即是资源,均使用名词(可复数)#####################重点####################
接口一般都是完成前后台数据的交互,交互的数据我们称之为资源
https://api.baidu.com/users
注:一般提倡用资源的复数形式,在url链接中奖励不要出现操作资源的动词
特殊的接口可以出现动词,因为这些接口一般没有一个明确的资源,或是动词就是接口的核心含义
https://api.baidu.com/place/search
https://api.baidu.com/login
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 - patch请求:局部修改主键为1的书
https://api.baidu.com/books/1 - delete请求:删除主键为1的书
6 过滤,通过在url上传参的形式传递搜索条件,或者说查询参数(query_params)
- 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 响应状态码
7.1 正常响应
- 响应状态码2xx
- 200:常规请求
- 201:创建成功
7.2 重定向响应
- 响应状态码3xx
- 301:永久重定向
- 302:暂时重定向
7.3 客户端异常
- 响应状态码4xx
- 403:请求无权限
- 404:请求路径不存在
- 405:请求方法不存在
7.4 服务器异常
- 响应状态码5xx
- 500:服务器异常
8 错误处理,应返回错误信息,error当做key
{
error: "无权限操作"
}
9 返回结果,针对不同操作,服务器向用户返回的结果应该符合以下规范
GET /collection:返回资源对象的列表(数组)
GET /collection/resource:返回单个资源对象
POST /collection:返回新生成的资源对象
PUT /collection/resource:返回完整的资源对象
PATCH /collection/resource:返回完整的资源对象
DELETE /collection/resource:返回一个空文档
10 需要url请求的资源需要访问资源的请求链接
- Hypermedia API,RESTful API最好做到Hypermedia,
- 即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么
{
"status": 0,
"msg": "ok",
"results":[
{
"name":"肯德基(罗餐厅)",
"img": "https://image.baidu.com/kfc/001.png"
}
...
]
}
四、drf的安装和简单使用
-
安装:pip install djangorestframework==3.10.3
-
使用
-
在settings.py的app中注册
INSTALLED_APPS = [ 'rest_framework' ]
-
在models.py中写表模型
class Book(models.Model): nid=models.AutoField(primary_key=True) name=models.CharField(max_length=32) price=models.DecimalField(max_digits=5,decimal_places=2) author=models.CharField(max_length=32)
-
新建一个序列化类(听不懂)
from rest_framework.serializers import ModelSerializer from app01.models import Book class BookModelSerializer(ModelSerializer): class Meta: model = Book fields = "__all__"
-
在视图中写视图类
from rest_framework.viewsets import ModelViewSet from .models import Book from .ser import BookModelSerializer class BooksViewSet(ModelViewSet): queryset = Book.objects.all() serializer_class = BookModelSerializer
-
写路由关系
from app01 import views from rest_framework.routers import DefaultRouter router = DefaultRouter() # 可以处理视图的路由器 router.register('book', views.BooksViewSet) # 向路由器中注册视图集 # 将路由器中的所以路由信息追到到django的路由列表中 urlpatterns = [ path('admin/', admin.site.urls), ] #这是什么意思?两个列表相加 # router.urls 列表 urlpatterns += router.urls
-
启动,在postman中测试即可
-
五、CBV源码阅读
-
代码分析
-
切入点: urls.py中的.as_view()方法
url(r'^books1/', views.Books.as_view())
-
视图中Books类
class Books(View): # 如果有个需求,该视图只能接受get请求? # 重写http_method_names列表,只保留get请求即可 http_method_names = ['get'] def get(self,request): print(self.request) # 这里可以通过self.request获取到request对象 return HttpResponse("ok")
-
项目启动时执行
@classonlymethod # 就是classmethod装饰器,django重写了classmethod而已 def as_view(cls, **initkwargs): ... def view(request, *args, **kwargs): ... ... return view
-
路由匹配时执行, 将执行的类进行实例化, 通过执行路由匹配中类实例化出的对象就可以执行到继承父类View中定义的dispatch方法
def view(request, *args, **kwargs): self = cls(**initkwargs) if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get self.request = request self.args = args self.kwargs = kwargs return self.dispatch(request, *args, **kwargs)
-
通过dispatch方法中使用反射获取请求的类型去执行对应类中定义的请求的方法
def dispatch(self, request, *args, **kwargs): 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 return handler(request, *args, **kwargs) # 直接加括号调用handler
-
上面的self.http_method_names判断, 如果子类中没有派生那么就会通过View中定义的进行判断
class View(object): http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] ...
-
-
图示分析
-
切入点: urls.py中的.as_view()方法
-
视图中Books类
-
项目启动时执行
-
路由匹配时执行, 将执行的类进行实例化, 通过执行路由匹配中类实例化出的对象就可以执行到继承父类View中定义的dispatch方法
-
通过dispatch方法中使用反射获取请求的类型去执行对应类中定义的请求的方法
-
上面的self.http_method_names判断, 如果子类中没有派生那么就会通过View中定义的进行判断
-
-
总结
- 项目启动: views.Books.as_view()就是View类中的view函数内存地址
- 路由匹配: 将view加括号调用, 并传入request对象.
- 执行view: 将Books类进行实例化, 实例化出Books类的对象. 这里的self就是Books类实例化出的对象
- 执行self.dispatch方法: self就是Books类实例化出的对象, 如果之类以及对象中都没有定义, 就会执行View类中的dispatch方法
- dispatch方法中就是通过用户当前的请求的小写字符串进行反射. 当继承View的类中有get方法, 根据对象属性查找顺序就会调用子类中定义的get方法
六、drf中的APIView源码阅读
-
代码分析
-
切入点: urls.py中的.as_view()方法
url(r'^booksapiview/', views.BooksAPIView.as_view()),
-
视图中BooksAPIView类
class BooksAPIView(APIView): def get(self, request): # request 已经不是原生django的request了,是drf自己定义的request对象 # print(request._request) # print(request.data) print(request.method) print(request.query_params) # get请求,地址中的参数 print(request.GET) return HttpResponse('ok') def post(self, request): print(request.data) # urlencoded方式有数据,json格式也有,formdata也有数据 print(type(request.POST)) # 原生的POST print(request._request.POST) return HttpResponse('ok')
-
项目启动时执行
@classmethod def as_view(cls, **initkwargs): ··· view = super().as_view(**initkwargs) # 调用父类的as_view方法 view.cls = cls # cls就是BooksAPIView return csrf_exempt(view) # 整个网站就取消的csrf的验证
-
执行APIView父类(View)的as_view方法,返回父类的view方法
@classonlymethod def as_view(cls, **initkwargs): ··· def view(request, *args, **kwargs): ··· return self.dispatch(request, *args, **kwargs) return view
-
执行父类的view方法,并且调用dispatch方法,但是APIView自己写了一个dispatch方法,所以不会执行父类的dispatch方法
# APIView的dispatch方法 def dispatch(self, request, *args, **kwargs): self.args = args self.kwargs = kwargs # 重新包装成一个request对象,以后再用的request对象,就是新的request对象了 request = self.initialize_request(request, *args, **kwargs) self.request = request self.headers = self.default_response_headers # deprecate? try: # 三大认证模块 self.initial(request, *args, **kwargs) # Get the appropriate handler method 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
-
APIView自己封装的request对象方法initialize_request()方法
def dispatch(self, request, *args, **kwargs): ··· request = self.initialize_request(request, *args, **kwargs) self.request = request # 已不再是django原来的request对象了 def initialize_request(self, request, *args, **kwargs): parser_context = self.get_parser_context(request) return Request( request, parsers=self.get_parsers(), authenticators=self.get_authenticators(), negotiator=self.get_content_negotiator(), parser_context=parser_context )
-
APIView自己的认证方法initial()方法
def dispatch(self, request, *args, **kwargs): ··· try: # 三大认证模块 self.initial(request, *args, **kwargs) # 和view里面的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: ··· # APIView的initial方法 def initial(self, request, *args, **kwargs): # 认证组件:校验用户 - 游客、合法用户、非法用户 # 游客:代表校验通过,直接进入下一步校验(权限校验) # 合法用户:代表校验通过,将用户存储在request.user中,再进入下一步校验(权限校验) # 非法用户:代表校验失败,抛出异常,返回403权限异常结果 self.perform_authentication(request) # 权限组件:校验用户权限 - 必须登录、所有用户、登录读写游客只读、自定义用户角色 # 认证通过:可以进入下一步校验(频率认证) # 认证失败:抛出异常,返回403权限异常结果 self.check_permissions(request) # 频率组件:限制视图接口被访问的频率次数 - 限制的条件(IP、id、唯一键)、频率周期时间(s、m、h)、频率的次数(3/s) # 没有达到限次:正常访问接口 # 达到限次:限制时间内不能访问,限制时间达到后,可以重新访问 self.check_throttles(request)
-
继承APIView视图的类中含有的方法分析:
-
django drf request _request FILES FILES GET query_params -
drf中的data对标django中的request,但是django中的request只能接收urlencoded和form-data格式的数据,drf中的data能接收urlencoded、form-data和json格式的数据
from rest_framework.request import Request # 只要继承了APIView,视图类中的request对象,都是新的,也就是上面那个request的对象了 # 老的request在新的request._request # 以后使用reqeust对象,就像使用之前的request是一模一样(因为重写了__getattr__方法) def __getattr__(self, attr): try: return getattr(self._request, attr) #通过反射,取原生的request对象,取出属性或方法 except AttributeError: return self.__getattribute__(attr) # request.data 感觉是个数据属性,其实是个方法,@property,修饰了 它是一个字典,post请求不管使用什么编码,传过来的数据,都在request.data #get请求传过来数据,从哪取? request.GET @property def query_params(self): """ More semantically correct name for request.GET. """ return self._request.GET #视图类中 print(request.query_params) #get请求,地址中的参数 # 原来在 print(request.GET)
-
-
-
图示分析
-
切入点: urls.py中的.as_view()方法
-
视图中BooksAPIView类
-
项目启动时执行
-
执行APIView父类(View)的as_view方法,返回父类的view方法
-
执行父类的view方法,并且调用dispatch方法,但是APIView自己写了一个dispatch方法,所以不会执行父类的dispatch方法
-
APIView自己封装的request对象方法initialize_request()方法
-
APIView自己的认证方法initial()方法
-
继承APIView视图的类中含有的方法分析:
-
data,原装的request == _request
-
query_params,原装的GET == _request.GET
-
FILES
-
-
七、拓展
-
一切皆对象,函数也是对象
def foo(a, b): return a + b foo.name = 'lqz' # 由于一切皆对象,函数也是个对象,对象放值 print(foo(2, 3)) print(foo.name) # lqz
-
局部禁用csrf方法和csrf_exempt
# 提示: 在视图函数上加装饰器@csrf_exempt. 与csrf_exempt(view)这么写和在视图函数上加装饰器是一模一样的. # urls.py中这种写法本质就是返回的views.test, 只是在原来基础之上不修改的调用方式, 以及原代码的情况下, 去除了csrf认证 path('test/', csrf_exempt(views.test)),