在学习 django REST framework(下面简称 drf)之前需要知道
对 RESTful API 设计有一定了解 restful API 设计风格
对 django 框架有一定认识, 本身 drf 就是基于 django 做的
对 python 面向对象编程有了解(drf 会对一些原生的 django 类做封装)
一, 前言
在学习 drf 之前的时候, 先简单说一下需要的预备知识. 在 django 中, 路由匹配之后, 会进行路由分发, 这个时候会有两种选择模式的选择. 也就是 FBV 与 CBV.
1,FBV
fbv 就是在 url 中一个路径对应一个函数
- urlpatterns = [
- url(r'^admin/', admin.site.urls),
- url(r'^index/', views.index)
- ]
在视图函数中
- def index(request):
- return render(request, 'index.html')
- 2,CBV
cbv 就是在 url 中一个路径对应一个类, drf 主要使用 CBV
- urlpatterns = [
- url(r'^admin/', admin.site.urls),
- url(r'^index/', views.IndexView.as_view()) # 执行类后面的 as_view()方法, 是父类里面的方法
- ]
在视图函数中
- from django.views import View
- class IndexView(View):
- # 以 get 形式访问会执行 get 函数, 一般情况下获取数据
- def get(self, *args, **kwargs):
- return HttpResponse('666')
- # 以 post 形式访问的话会执行 post 函数, 一般情况下发送数据
- def post(self, *args, **kwargs):
- return HttpResponse('999')
我们在路由匹配的时候看到 url(r'^index/', views.IndexView.as_view()), 那这个 as_view()是什么, 既然我们在视图类中没有定义这个 as_view()方法, 就应该到父类 (也就是 IndexView 的父类 View) 中看一下 View. 以下是 django 源码, 路径是 \ django\views\generic\base.py,
- class View:
- http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] # 支持的各种 http 方法
- def __init__(self, **kwargs):
- pass
- @classonlymethod
- def as_view(cls, **initkwargs): # url 路由匹配进入 as_view 方法
- def view(request, *args, **kwargs):
- return self.dispatch(request, *args, **kwargs) # 返回 dispath 方法
- return view
- def dispatch(self, request, *args, **kwargs): # dispath 方法是 drf 的关键, dispath 方法会通过反射, 通过请求的方法, 分发到各个视图类的方法中
- pass
3,django 的请求周期
因此根据 CBV 和 FBVdjango 的生命周期可以又两类
FBV: 请求通过 uwsgi 网关, 中间件, 然后进入路由匹配, 进入视图函数, 连接数据库 ORM 操作, 模板渲染, 返回经过中间件, 最终交给浏览器 response 字符串.
CBV: 请求通过 uwsgi 网关, 中间件, 然后进入路由匹配, 这里就与 FBV 有区别了, 因为不再是试图函数而是视图类, 说的详细一点, 先经过父类 View 的 dispath 方法, 进行请求方法的判断, 在分发到视图类的方法, 连接数据库 ORM 操作, 模板渲染, 返回经过中间件, 最终交给浏览器 response 字符串.
而再 drf 中主要使用 CBV, 生命周期就变成了如下
请求通过 uwsgi 网关, 中间件, 然后进入路由匹配, 这里就有区别了, 先经过 drf 中 APIView 类中的 dispath 方法(这里假定视图类没有重写 APIView 中的 dispath 方法), 在 dispath 中对 request 请求进行封装, 反射回到视图类, 连接数据库 ORM 操作, 模板渲染, 返回经过中间件, 最终交给浏览器响应字符串.
4, 面向对象
说到面向对象就是三个特性, 封装, 多态, 继承.
<1>, 子类重写父类方法
我们在继承父类的时候往往会重写父类中的方法, 例如
- class A:
- def get_name(self):
- return self.name
- def return_name(self):
- if hasattr(self, 'name'):
- return 'name:' + getattr(self, 'name', None)
- class B(A):
- name = "b"
- def get_name(self):
- return self.name
- b = B()
- b.get_name() # 输出 B
- b.return_name() # 输出 name: B, 这里由于 B 类中没有实现 return_name 方法, 实例化 B 得到 b 之后, 会调用父类 A 中的 return_name 方法, hasattr 方法会查找类中是否有 name 属性, 这里虽然在类 A 中没有, 会向下查找 B 类中是否有 name 属性, 然后返回'name:' + getattr(self, 'name', None) , 也就是 name:b
这是简单的子类方法重写父类中的方法, 我们再使用 drf 的认证, 权限等组件是会经常对父类中的方法重写, 从而细粒度的实现自己的功能.
请注意: 事情往往不是绝对的, 如果像重写 python 内置的基本数据类型, 如字典, 列表中的特殊方法, 就会的到意想不到的结果, 就是实例化的对象不再调用你重写的方法, 而是调用本来的方法. 这是因为 python 的一些基本类型的方法是由 c 语言编写的, python 为了满足速度, 抄近道不会再调用你重写的特殊方法.
- <2>,mixin 模式
- class X(object):
- def f(self):
- print( 'x')
- class A(X):
- def f(self):
- print('a')
- def extral(self):
- print('extral a')
- class B(X):
- def f(self):
- print('b')
- def extral(self):
- print( 'extral b')
- class C(A, B, X):
- def f(self):
- super(C, self).f()
- print('c')
- print(C.mro())
- c = C()
- c.f()
- c.extral()
这样做也可以输出结果
- [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.X'>, <class 'object'>] # 继承的顺序是 A-->B-->X-->object 这了的 object 在 python3 中是一切类的基类, 包括 object 类本身.
- a
- c
- extral a # 虽然类 C 中没有实现接口 extral(), 却调用了父类 A 中的 extral()方法
这样的继承虽然可以实现功能, 但是有一个很明显的问题, 那就是在面向对象中, 一定要指明一个类到底是什么. 也就是说, 如果我想构造一个类, 假如是 Somthing, 那么我想让这个类实现会飞, 会游泳, 会跑, 三种行为, 我可以这样做, 同时继承, 鸟, 鱼, 马三个类, 像这样
- class Bird:
- def fly(self):
- print('fly')
- class Fish:
- def swim(self):
- print('swim')
- class Horse:
- def run(self):
- print('run')
- class Something(Bird, Fish, Horse):
- pass
- s = Something()
- s.fly()
- s.swim()
- s.run()
输出
fly swim run
可是实现会跑, 会飞, 会游泳的三种行为, 但是这个类到底是什么, 是鱼, 是马, 还是鸟, 也就是说不知道 Something 到底是个什么类. 为了解决这个问题, 我们可以引用 mixin 模式. 改写如下
- class BirdMixin:
- def fly(self):
- print('fly')
- class FishMixin:
- def swim(self):
- print('swim')
- class Horse:
- def run(self):
- print('run')
- class Something(BirdMixin, FishMixin, Horse):
- pass
这样就解决了上面的问题, 也许你会发现, 这其实没有什么变化, 只是在类的命名加上了以 Mixin 结尾, 其实这是一种默认的声明, 告诉你, Something 类其实是一种马, 父类是 HorseHorse, 继承其他两个类, 只是为了调用他们的方法而已, 这种叫做 mixin 模式, 在 drf 的源码种会用到.
例如 drf 中的 generics 路径为 rest_framework/generics.py
- class CreateAPIView(mixins.CreateModelMixin,
- GenericAPIView):
- pass
- class ListAPIView(mixins.ListModelMixin,
- GenericAPIView):
- pass
- class RetrieveAPIView(mixins.RetrieveModelMixin,
- GenericAPIView):
- pass
相当于每多一次继承, 子类可调用的方法就更多了.
二, 生成项目
1, 生成项目
这里可以使用 pycharm 作为集成开发工具, 创建 django 项目查看 Python 和第三方库源码很方便, 使用 pycharm 创建一个 django 项目, 然后将
django REST framework
作为第三方包放入 django 项目中
2, 数据库设计
先来看一下如果不使用 drf 怎么进行用户认证, 通常是用字段验证的方式, 来生成相应的数据库, 在用户登录时候, 对数据库查询, 简单的数据库设计如下
- from django.db import models
- class UserInfo(models.Model):
- USER_TYPE = (
- (1,'普通用户'),
- (2,'VIP'),
- (3,'SVIP')
- )
- user_type = models.IntegerField(choices=USER_TYPE, default=1)
- username = models.CharField(max_length=32)
- password = models.CharField(max_length=64)
- class UserToken(models.Model):
- user = models.OneToOneField(UserInfo,on_delete=models.CASCADE)
- token = models.CharField(max_length=64)
简单的用户信息, 每个用户关联一个一对一的 usertoken 做为验证
然后在项目目录下执行生成数据库命令
- python manage.py makemigrations
- python manage.py migrate
3, 路由系统
- from django.contrib import admin
- from django.urls import path
- from django.conf.urls import url
- from API.views import AuthView
- urlpatterns = [
- path('admin/', admin.site.urls),
- url(r'^api/v1/auth/$', AuthView.as_view())
- ]
API/v1/auth / 中的 API 分别代表接口和版本号, 后面会说到
4, 视图函数
md5 函数根据用户名和用户的访问时间进行加密
当用户第一次访问时, 数据库创建用户, 并将 token 字符串, 存储到数据库
当用户下次访问的时候, 需要带着这个字符串与数据库比对, 并返回相应的提示信息
这里的 token 暂时没有放回浏览器端, 真正项目中可以写入到浏览器 cookie 中
- from django.shortcuts import render, HttpResponse
- from django.http import JsonResponse
- from django.views import View
- from API import models
- def md5(user):
- import hashlib
- import time
- # 当前时间, 相当于生成一个随机的字符串
- ctime = str(time.time())
- # token 加密
- m = hashlib.md5(bytes(user, encoding='utf-8'))
- m.update(bytes(ctime, encoding='utf-8'))
- return m.hexdigest()
- class AuthView(View):
- def get(self, request, *args, **kwargs):
- ret = {'code': 1000, 'msg': 'success', 'name': '偷偷'}
- ret = JSON.dumps(ret, ensure_ascii=False)
- return HttpResponse(ret)
- def post(self, request, *args, **kwargs):
- ret = {'code': 1000, 'msg': None}
- try:
- user = request.POST.get('username')
- pwd = request.POST.get('password')
- obj = models.UserInfo.objects.filter(username=user).first()
- if not obj:
- # 如果用户第一次登陆则创建用户
- obj = models.UserInfo.objects.create(username=user, password=pwd)
- ret['code'] = 1001
- ret['msg'] = '创建用户成功'
- # 为用户创建 token
- token = md5(user)
- # 存在就更新, 不存在就创建
- models.UserToken.objects.update_or_create(user=obj, defaults={'token': token})
- ret['token'] = token
- except Exception as e:
- ret['code'] = 1002
- ret['msg'] = '请求异常'
- return JsonResponse(ret)
第一次发送请求
返回请求信息
第二次发送请求
返回请求信息
这里没有使用 drf 的认证组件
三, 使用 Django REST framewok 认证组件
1, 实例
假如用户想获取自己的订单信息, 发送请求之后返回订单信息以 JSON 格式的数据返回.
- from rest_framework.views import APIView
- from django.http import JsonResponse
- from rest_framework.authentication import BaseAuthentication
- from rest_framework import exceptions
- from API import models
- # 这里直接表示订单
- ORDER_DICT = {
- 1:{
- 'name':'apple',
- 'price':15
- },
- 2:{
- 'name':'狗子',
- 'price':100
- }
- }
- class FirstAuthenticate(BaseAuthentication):
- # 添加自己的认证逻辑, 基类 BaseAuthentication 中有一个必须要重写的接口
- def authenticate(self, request):
- pass
- def authenticate_header(self, request):
- pass
- class MyAuthenticate(BaseAuthentication):
- # 添加自己的认证逻辑, 基类 BaseAuthentication 中有两个必须要重写的接口
- def authenticate(self, request):
- token = request._request.GET.get('token') # 获取 token 参数
- token_obj = models.UserToken.objects.filter(token=token).first() # 在数据库 UserToken 查找是否有相应的对象
- if not token_obj: # 如果没有, 则报错
- raise exceptions.AuthenticationFailed('用户认证失败')
- return (token_obj.user, token_obj) # 这里需要返回两个对象, 分别是 UserInfo 对象和 UserToken 对象
- def authenticate_header(self, request): # 返回相应头信息
- pass
- class OrderView(APIView):
- # 用户想要获取订单, 就要先通过身份认证,
- # 这里的 authentication_classes 就是用户的认证类
- authentication_classes = [FirestAuthenticate, MyAuthenticate]
- def get(self, request, *args, **kwargs):
- ret = {
- 'code': 1024,
- 'msg': '订单获取成功',
- }
- try:
- ret['data'] = ORDER_DICT
- except Exception as e:
- pass
- return JsonResponse(ret)
这里继承了 REST framek 中的 APIView, 在 APIView 中将原生的 request 进行了封装, 封装了一些用于认证, 权限的类, 在请求来的时候, 会依次通过 FirestAuthenticate,MyAuthenticate 两个类, 并调用 authenticate 进行认证.
发送请求
返回订单的数据
认证成功
2, 源码分析
这里推荐使用 pycharm 作为集成开发工具, 可以 ctrl + 鼠标左键点击方法, 或者类直接进入源码查看
<1>, 第 1 步
在路由匹配之后会先进入到 APIView 中的 as_view 方法中, 然后进入到 django 的 View 中,
<2>, 第 2 步
由于子类 APIView 已经实现了 dispath 方法, 接着返回 APIView 中的 disapth 方法
<3>, 第 3 步
然后会发现 drf 对原生 request 做的操作
<4>, 第 4 步
这里的 initialize_request, 主要进行封装
<5>, 第 5 步
而 initial 则会对调用封装类中的方法, 实现各种功能
至此可以看到 request 在 drf 中大概的流程.
3,drf 认证流程
在上面第 4 步和第 5 步可以看到 APIView 中的两个方法的 initialize_request,initial
我们进入到 initialize_request, 查看 authenticators=self.get_authenticators()
这里的 authentication_classes, 其实是一个所有认证类的集合 (指的是一个可以迭代的容器对象, 如 list,tuple 等, 而不是特指 set() 内置类型),
这里的 api_settings 其实就是 django 项目的全局配置文件 settings.py, 这说明我们可以在需要认证的视图函数多的情况下使用全局配置使得每一个进行认证.
<1>, 全局与局部配置认证类
可以直接在 settings.py 中添加全局配置项
- REST_FRAMEWORK = {
- 'DEFAULT_AUTHENTICATION_CLASSES': ['api.utils.authenticate.FirstAuthenticate', 'api.utils.authenticate.MyAuthenticate'],
- }
那么如果我的个别视图类不想认证呢? 可以这样写
- class OrderView(APIView):
- # 这里没有重写 authentication_classes 属性, 则使用全局配置的 authentication_classes, 即在 setting.py 中的 authentication_classes.
- def get(self, request, *args, **kwargs):
- pass
- class CartView(APIView):
- authentication_classes = [authenticate.FirstAuthenticate,] # authentication_classes 中只包含 FirstAuthenticate, 则只通过他的认证
- def get(self, request, *args, **kwargs):
- pass
- class UserInfoView(APIView):
- authentication_classes = [] # authentication_classes 为空, 则不会进行认证
- def get(self, request, *args, **kwargs):
- pass
- <2>, 究竟如何进行认证
上面说了想要定义多个认证规则, 其实就是封装多个认证类, 那么这些认证类如何进行认证呢?
这里的 perform_authentication 就是进行主要的功能, 在 request 类中有一个_authenticate
来分析下源码
- def _authenticate(self):
- """
- Attempt to authenticate the request using each authentication instance
- in turn.
- """
- for authenticator in self.authenticators: # 找到 authentication_classes, 并循环每一个认证类
- try:
- user_auth_tuple = authenticator.authenticate(self) # 调用认证类的 authenticate 方法, 也就是上面我们实现的方法, 并将返回值赋值给 user_auth_tuple
- except exceptions.APIException:
- self._not_authenticated() # 如果出错调用_not_authenticated, 方法, 下面会说到
- raise
- if user_auth_tuple is not None: # 如果 authenticate 方法的返回值不为空
- self._authenticator = authenticator
- self.user, self.auth = user_auth_tuple # 这也就是为什么认证类的 authenticate 方法会返回两个对象的原因
- return
- self._not_authenticated() # 如果没有通过认证, 则调用_not_authenticated 方法
- def _not_authenticated(self):
- """
- Set authenticator, user & authtoken representing an unauthenticated request.
- Defaults are None, AnonymousUser & None.
- """
- self._authenticator = None
- if api_settings.UNAUTHENTICATED_USER:
- self.user = api_settings.UNAUTHENTICATED_USER()
- else:
- self.user = None
- if api_settings.UNAUTHENTICATED_TOKEN:
- self.auth = api_settings.UNAUTHENTICATED_TOKEN()
- else:
- self.auth = None
_authenticate 方法中调用 authenticator.authenticate(self)方法, 返回给 user_auth_tuple, 并通过判断 user_auth_tuple 是否为空, 其实就像是我从浏览器发送请求, request 中携带我的用户认证信息, 在进入视图类之前, 通过一次一次调用认证类来查看我携带的认证信息是否正确, 如果正确则返回数据库中正确的 User 对象. 如果不通过或者没有认证信息, 则在_not_authenticated 中按照匿名用户处理.
来看一下 authenticator.authenticate(self)中的 authenticate(self)具体做了什么
在 authenticate 中可以添加具体的认证逻辑, 当然也可以在视图类中书写, 但是 drf 中提供的组件, 可以使得代码耦合度更低, 维护性更强, 更方便.
<3>, 匿名用户认证
上面_not_authenticated 的 UNAUTHENTICATED_TOKEN,UNAUTHENTICATED_USER 说明, 也可以通过在 setting.py 中定义匿名用户的认证.
只要再 setting.py 中添加如下
- REST_FRAMEWORK = {
- 'DEFAULT_AUTHENTICATION_CLASSES': ['api.utils.authenticate.FirstAuthenticate', 'api.utils.authenticate.MyAuthenticate'],
- "UNAUTHENTICATED_USER": None, # 匿名, request.user = None
- "UNAUTHENTICATED_TOKEN": None,# 匿名, request.auth = None
- }
4, 认证总结
要理解 django REST framework , 就要先理解面向对象. 子类继承父类属性和方法, 而在基类中往往以定义抽象接口的形式, 强制使子类重写抽象接口. 不过抽象接口这往往是框架开发者做的, 而不是我们要需要做的. 实例化的对象可以调用所类的属性和方法, 其实方法也可以看作是一种属性. 子类新定义或者重写父类的属性, 实例化的对象可以调用父类中的方法查询到子类的属性, 就是说实例化的对象集所有父类子类于一身. 子类中的方法或者属性会覆盖掉父类中的方法和属性, 实例化对象调用的时候不会管父类中怎么样, 所以在变量和方法命名的时候应该注意, 或者也可以使用 super 等操作.
而在 django REST framework 中, 对原生 request 做了封装. 原本我们可以再视图类中的进行的比如访问限流, 用户认证, 权限管理等逻辑, 封装到一个一个类中的方法中, 在用户请求进入视图类之前, 会先查找并迭代相关封装的类, 然后调用这些类的相关方法, 根据返回值判断是否满足认证, 权限等功能. 如果不通过则不会进入到视图类执行下一步, 并返回相应的提示信息. 这样分开的好处是当然就是最大程度的解耦, 各个相关功能相互不影响, 又相互关联, 维护性更高.
来源: https://www.cnblogs.com/welan/p/10100563.html