django rest framework 官网

在学习django rest framework(下面简称drf)之前需要知道

  • 对RESTful API设计有一定了解 restful api设计风格
  • 对django框架有一定认识,本身drf就是基于django做的
  • 对python面向对象编程有了解(drf会对一些原生的django类做封装)

一、前言

在学习drf之前的时候,先简单说一下需要的预备知识。在django中,路由匹配之后,会进行路由分发,这个时候会有两种选择模式的选择。也就是FBVCBV

1、FBV

fbv就是在url中一个路径对应一个函数

  1. urlpatterns = [
  2. url(r'^admin/', admin.site.urls),
  3. url(r'^index/', views.index)
  4. ]

在视图函数中

  1. def index(request):
  2. return render(request, 'index.html')

2、CBV

cbv就是在url中一个路径对应一个类,drf主要使用CBV

  1. urlpatterns = [
  2. url(r'^admin/', admin.site.urls),
  3. url(r'^index/', views.IndexView.as_view()) # 执行类后面的as_view()方法,是父类里面的方法
  4. ]

在视图函数中

  1. from django.views import View
  2. class IndexView(View):
  3. # 以get形式访问会执行get函数,一般情况下获取数据
  4. def get(self, *args, **kwargs):
  5. return HttpResponse('666')
  6. # 以post形式访问的话会执行post函数,一般情况下发送数据
  7. def post(self, *args, **kwargs):
  8. return HttpResponse('999')

我们在路由匹配的时候看到url(r'^index/', views.IndexView.as_view()),那这个as_view()是什么,既然我们在视图类中没有定义这个as_view()方法,就应该到父类(也就是IndexView的父类View)中看一下View。以下是django源码,路径是\django\views\generic\base.py

  1. class View:
  2. http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] # 支持的各种http方法
  3. def __init__(self, **kwargs):
  4. pass
  5. @classonlymethod
  6. def as_view(cls, **initkwargs): # url路由匹配进入as_view方法
  7. def view(request, *args, **kwargs):
  8. return self.dispatch(request, *args, **kwargs) # 返回dispath方法
  9. return view
  10. def dispatch(self, request, *args, **kwargs): # dispath方法是drf的关键,dispath方法会通过反射,通过请求的方法,分发到各个视图类的方法中
  11. pass

3、django的请求周期

因此根据CBV和FBVdjango的生命周期可以又两类

  • FBV:请求通过uwsgi网关,中间件,然后进入路由匹配,进入视图函数,连接数据库ORM操作,模板渲染,返回经过中间件,最终交给浏览器response字符串。
  • CBV:请求通过uwsgi网关,中间件,然后进入路由匹配,这里就与FBV有区别了,因为不再是试图函数而是视图类,说的详细一点,先经过父类View的dispath方法,进行请求方法的判断,在分发到视图类的方法,连接数据库ORM操作,模板渲染,返回经过中间件,最终交给浏览器response字符串。

而再drf中主要使用CBV,生命周期就变成了如下

请求通过uwsgi网关,中间件,然后进入路由匹配,这里就有区别了,先经过drfAPIView类中的dispath方法(这里假定视图类没有重写APIView中的dispath方法),在dispath中对request请求进行封装,反射回到视图类,连接数据库ORM操作,模板渲染,返回经过中间件,最终交给浏览器响应字符串。

4、面向对象

说到面向对象就是三个特性,封装,多态,继承。

<1>、子类重写父类方法

我们在继承父类的时候往往会重写父类中的方法,例如

  1. class A:
  2. def get_name(self):
  3. return self.name
  4. def return_name(self):
  5. if hasattr(self, 'name'):
  6. return 'name: ' + getattr(self, 'name', None)
  7. class B(A):
  8. name = "b"
  9. def get_name(self):
  10. return self.name
  11. b = B()
  12. b.get_name() # 输出B
  13. 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模式

  1. class X(object):
  2. def f(self):
  3. print( 'x')
  4. class A(X):
  5. def f(self):
  6. print('a')
  7. def extral(self):
  8. print('extral a')
  9. class B(X):
  10. def f(self):
  11. print('b')
  12. def extral(self):
  13. print( 'extral b')
  14. class C(A, B, X):
  15. def f(self):
  16. super(C, self).f()
  17. print('c')
  18. print(C.mro())
  19. c = C()
  20. c.f()
  21. c.extral()

这样做也可以输出结果

  1. [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.X'>, <class 'object'>] # 继承的顺序是 A-->B-->X-->object 这了的object在python3中是一切类的基类,包括object类本身。
  2. a
  3. c
  4. extral a # 虽然类C中没有实现接口extral(),却调用了父类A中的extral()方法

这样的继承虽然可以实现功能,但是有一个很明显的问题,那就是在面向对象中,一定要指明一个类到底是什么。也就是说,如果我想构造一个类,假如是Somthing,那么我想让这个类实现会飞,会游泳,会跑,三种行为,我可以这样做,同时继承,鸟,鱼,马三个类,像这样

  1. class Bird:
  2. def fly(self):
  3. print('fly')
  4. class Fish:
  5. def swim(self):
  6. print('swim')
  7. class Horse:
  8. def run(self):
  9. print('run')
  10. class Something(Bird, Fish, Horse):
  11. pass
  12. s = Something()
  13. s.fly()
  14. s.swim()
  15. s.run()

输出

  1. fly
  2. swim
  3. run

可是实现会跑,会飞,会游泳的三种行为,但是这个类到底是什么,是鱼,是马,还是鸟,也就是说不知道Something到底是个什么类。为了解决这个问题,我们可以引用mixin模式。改写如下

  1. class BirdMixin:
  2. def fly(self):
  3. print('fly')
  4. class FishMixin:
  5. def swim(self):
  6. print('swim')
  7. class Horse:
  8. def run(self):
  9. print('run')
  10. class Something(BirdMixin, FishMixin, Horse):
  11. pass

这样就解决了上面的问题,也许你会发现,这其实没有什么变化,只是在类的命名加上了以Mixin结尾,其实这是一种默认的声明,告诉你,Something类其实是一种马,父类是HorseHorse,继承其他两个类,只是为了调用他们的方法而已,这种叫做mixin模式,在drf的源码种会用到。

例如drf中的generics 路径为rest_framework/generics.py

  1. class CreateAPIView(mixins.CreateModelMixin,
  2. GenericAPIView):
  3. pass
  4. class ListAPIView(mixins.ListModelMixin,
  5. GenericAPIView):
  6. pass
  7. class RetrieveAPIView(mixins.RetrieveModelMixin,
  8. GenericAPIView):
  9. pass

相当于每多一次继承,子类可调用的方法就更多了。

二、生成项目

1、生成项目

这里可以使用pycharm作为集成开发工具,创建django项目查看Python和第三方库源码很方便,使用pycharm创建一个django项目,然后将django rest framework作为第三方包放入django项目中

2、数据库设计

先来看一下如果不使用drf怎么进行用户认证,通常是用字段验证的方式,来生成相应的数据库,在用户登录时候,对数据库查询,简单的数据库设计如下

  1. from django.db import models
  2. class UserInfo(models.Model):
  3. USER_TYPE = (
  4. (1,'普通用户'),
  5. (2,'VIP'),
  6. (3,'SVIP')
  7. )
  8. user_type = models.IntegerField(choices=USER_TYPE default=1)
  9. username = models.CharField(max_length=32)
  10. password = models.CharField(max_length=64)
  11. class UserToken(models.Model):
  12. user = models.OneToOneField(UserInfo,on_delete=models.CASCADE)
  13. token = models.CharField(max_length=64)

简单的用户信息,每个用户关联一个一对一的usertoken做为验证

然后在项目目录下执行生成数据库命令

  1. python manage.py makemigrations
  2. python manage.py migrate

3、路由系统

  1. from django.contrib import admin
  2. from django.urls import path
  3. from django.conf.urls import url
  4. from api.views import AuthView
  5. urlpatterns = [
  6. path('admin/', admin.site.urls),
  7. url(r'^api/v1/auth/$', AuthView.as_view())
  8. ]

api/v1/auth/中的api分别代表接口和版本号,后面会说到

4、视图函数

  • md5函数根据用户名和用户的访问时间进行加密
  • 当用户第一次访问时,数据库创建用户,并将token字符串,存储到数据库
  • 当用户下次访问的时候,需要带着这个字符串与数据库比对,并返回相应的提示信息

    这里的token暂时没有放回浏览器端,真正项目中可以写入到浏览器cookie
  1. from django.shortcuts import render, HttpResponse
  2. from django.http import JsonResponse
  3. from django.views import View
  4. from api import models
  5. def md5(user):
  6. import hashlib
  7. import time
  8. # 当前时间,相当于生成一个随机的字符串
  9. ctime = str(time.time())
  10. # token加密
  11. m = hashlib.md5(bytes(user, encoding='utf-8'))
  12. m.update(bytes(ctime, encoding='utf-8'))
  13. return m.hexdigest()
  14. class AuthView(View):
  15. def get(self, request, *args, **kwargs):
  16. ret = {'code': 1000, 'msg': 'success', 'name': '偷偷'}
  17. ret = json.dumps(ret, ensure_ascii=False)
  18. return HttpResponse(ret)
  19. def post(self, request, *args, **kwargs):
  20. ret = {'code': 1000, 'msg': None}
  21. try:
  22. user = request.POST.get('username')
  23. pwd = request.POST.get('password')
  24. obj = models.UserInfo.objects.filter(username=user).first()
  25. if not obj:
  26. # 如果用户第一次登陆则创建用户
  27. obj = models.UserInfo.objects.create(username=user, password=pwd)
  28. ret['code'] = 1001
  29. ret['msg'] = '创建用户成功'
  30. # 为用户创建token
  31. token = md5(user)
  32. # 存在就更新,不存在就创建
  33. models.UserToken.objects.update_or_create(user=obj, defaults={'token': token})
  34. ret['token'] = token
  35. except Exception as e:
  36. ret['code'] = 1002
  37. ret['msg'] = '请求异常'
  38. return JsonResponse(ret)

第一次发送请求

返回请求信息

第二次发送请求

返回请求信息

这里没有使用drf的认证组件

三、使用Django rest framewok 认证组件

1、实例

假如用户想获取自己的订单信息,发送请求之后返回订单信息以json格式的数据返回。

  1. from rest_framework.views import APIView
  2. from django.http import JsonResponse
  3. from rest_framework.authentication import BaseAuthentication
  4. from rest_framework import exceptions
  5. from api import models
  6. # 这里直接表示订单
  7. ORDER_DICT = {
  8. 1:{
  9. 'name':'apple',
  10. 'price':15
  11. },
  12. 2:{
  13. 'name':'狗子',
  14. 'price':100
  15. }
  16. }
  17. class FirstAuthenticate(BaseAuthentication):
  18. # 添加自己的认证逻辑,基类BaseAuthentication中有一个必须要重写的接口
  19. def authenticate(self, request):
  20. pass
  21. def authenticate_header(self, request):
  22. pass
  23. class MyAuthenticate(BaseAuthentication):
  24. # 添加自己的认证逻辑,基类BaseAuthentication中有两个必须要重写的接口
  25. def authenticate(self, request):
  26. token = request._request.GET.get('token') # 获取token参数
  27. token_obj = models.UserToken.objects.filter(token=token).first() # 在数据库UserToken查找是否有相应的对象
  28. if not token_obj: # 如果没有,则报错
  29. raise exceptions.AuthenticationFailed('用户认证失败')
  30. return (token_obj.user, token_obj) # 这里需要返回两个对象,分别是UserInfo对象和UserToken对象
  31. def authenticate_header(self, request): # 返回相应头信息
  32. pass
  33. class OrderView(APIView):
  34. # 用户想要获取订单,就要先通过身份认证、
  35. # 这里的authentication_classes 就是用户的认证类
  36. authentication_classes = [FirestAuthenticate MyAuthenticate]
  37. def get(self, request, *args, **kwargs):
  38. ret = {
  39. 'code': 1024,
  40. 'msg': '订单获取成功',
  41. }
  42. try:
  43. ret['data'] = ORDER_DICT
  44. except Exception as e:
  45. pass
  46. return JsonResponse(ret)

这里继承了rest framek中的APIView,在APIView中将原生的request进行了封装,封装了一些用于认证,权限的类,在请求来的时候,会依次通过FirestAuthenticate MyAuthenticate两个类,并调用authenticate进行认证。

发送请求

返回订单的数据

认证成功

2、源码分析

这里推荐使用pycharm作为集成开发工具,可以ctrl+鼠标左键点击方法,或者类直接进入源码查看

<1>、第1步

在路由匹配之后会先进入到APIView中的as_view方法中,然后进入到djangoView中,

<2>、第2步

由于子类APIView已经实现了dispath方法,接着返回APIView中的disapth方法

<3>、第3步

然后会发现drf对原生request做的操作

<4>、第4步

这里的initialize_request,主要进行封装

<5>、第5步

而initial则会对调用封装类中的方法,实现各种功能

至此可以看到requestdrf中大概的流程。

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中添加全局配置项

  1. REST_FRAMEWORK = {
  2. 'DEFAULT_AUTHENTICATION_CLASSES': ['api.utils.authenticate.FirstAuthenticate', 'api.utils.authenticate.MyAuthenticate'],
  3. }

那么如果我的个别视图类不想认证呢?可以这样写

  1. class OrderView(APIView):
  2. # 这里没有重写authentication_classes属性,则使用全局配置的authentication_classes,即在setting.py中的authentication_classes。
  3. def get(self, request, *args, **kwargs):
  4. pass
  5. class CartView(APIView):
  6. authentication_classes = [authenticate.FirstAuthenticate,] # authentication_classes中只包含FirstAuthenticate,则只通过他的认证
  7. def get(self, request, *args, **kwargs):
  8. pass
  9. class UserInfoView(APIView):
  10. authentication_classes = [] # authentication_classes为空,则不会进行认证
  11. def get(self, request, *args, **kwargs):
  12. pass

<2>、究竟如何进行认证

上面说了想要定义多个认证规则,其实就是封装多个认证类,那么这些认证类如何进行认证呢?

这里的perform_authentication就是进行主要的功能,在request类中有一个_authenticate

来分析下源码

  1. def _authenticate(self):
  2. """
  3. Attempt to authenticate the request using each authentication instance
  4. in turn.
  5. """
  6. for authenticator in self.authenticators: # 找到 authentication_classes,并循环每一个认证类
  7. try:
  8. user_auth_tuple = authenticator.authenticate(self) # 调用认证类的authenticate方法,也就是上面我们实现的方法,并将返回值赋值给user_auth_tuple
  9. except exceptions.APIException:
  10. self._not_authenticated() # 如果出错调用_not_authenticated,方法,下面会说到
  11. raise
  12. if user_auth_tuple is not None: # 如果authenticate方法的返回值不为空
  13. self._authenticator = authenticator
  14. self.user, self.auth = user_auth_tuple # 这也就是为什么认证类的authenticate方法会返回两个对象的原因
  15. return
  16. self._not_authenticated() # 如果没有通过认证,则调用_not_authenticated方法
  17. def _not_authenticated(self):
  18. """
  19. Set authenticator, user & authtoken representing an unauthenticated request.
  20. Defaults are None, AnonymousUser & None.
  21. """
  22. self._authenticator = None
  23. if api_settings.UNAUTHENTICATED_USER:
  24. self.user = api_settings.UNAUTHENTICATED_USER()
  25. else:
  26. self.user = None
  27. if api_settings.UNAUTHENTICATED_TOKEN:
  28. self.auth = api_settings.UNAUTHENTICATED_TOKEN()
  29. else:
  30. 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_authenticatedUNAUTHENTICATED_TOKENUNAUTHENTICATED_USER说明,也可以通过在setting.py中定义匿名用户的认证。

只要再setting.py中添加如下

  1. REST_FRAMEWORK = {
  2. 'DEFAULT_AUTHENTICATION_CLASSES': ['api.utils.authenticate.FirstAuthenticate', 'api.utils.authenticate.MyAuthenticate'],
  3. "UNAUTHENTICATED_USER": None, # 匿名,request.user = None
  4. "UNAUTHENTICATED_TOKEN": None,# 匿名,request.auth = None
  5. }

4、认证总结

要理解django rest framework ,就要先理解面向对象。子类继承父类属性和方法,而在基类中往往以定义抽象接口的形式,强制使子类重写抽象接口。不过抽象接口这往往是框架开发者做的,而不是我们要需要做的。实例化的对象可以调用所类的属性和方法,其实方法也可以看作是一种属性。子类新定义或者重写父类的属性,实例化的对象可以调用父类中的方法查询到子类的属性,就是说实例化的对象集所有父类子类于一身。子类中的方法或者属性会覆盖掉父类中的方法和属性,实例化对象调用的时候不会管父类中怎么样,所以在变量和方法命名的时候应该注意,或者也可以使用super等操作。

而在django rest framework中,对原生request做了封装。原本我们可以再视图类中的进行的比如访问限流,用户认证,权限管理等逻辑,封装到一个一个类中的方法中,在用户请求进入视图类之前,会先查找并迭代相关封装的类,然后调用这些类的相关方法,根据返回值判断是否满足认证,权限等功能。如果不通过则不会进入到视图类执行下一步,并返回相应的提示信息。这样分开的好处是当然就是最大程度的解耦,各个相关功能相互不影响,又相互关联,维护性更高。

Django Rest framework 之 认证的更多相关文章

  1. django rest framework用户认证

    django rest framework用户认证 进入rest framework的Apiview @classmethod def as_view(cls, **initkwargs): &quo ...

  2. Django rest framework 的认证流程(源码分析)

    一.基本流程举例: urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^users/', views.HostView.as_view() ...

  3. Django REST Framework之认证组件

    什么是认证 认证即需要知道是谁在访问服务器,需要有一个合法身份.认证的方式可以有很多种,例如session+cookie.token等,这里以token为例.如果请求中没有token,我们认为这是未登 ...

  4. Django Rest Framework之认证

    代码基本结构 url.py: from django.conf.urls import url, include from web.views.s1_api import TestView urlpa ...

  5. DRF Django REST framework 之 认证组件(五)

    引言 很久很久以前,Web站点只是作为浏览服务器资源(数据)和其他资源的工具,甚少有什么用户交互之类的烦人的事情需要处理,所以,Web站点的开发这根本不关心什么人在什么时候访问了什么资源,不需要记录任 ...

  6. Django REST framework 之 认证 权限 限制

    认证是确定你是谁 权限是指你有没有访问这个接口的权限 限制主要是指限制你的访问频率 认证 REST framework 提供了一些开箱即用的身份验证方案,并且还允许你实现自定义方案. 接下类我们就自己 ...

  7. 基于django rest framework做认证组件

    先导入要用到的类 from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions ...

  8. 【django后端分离】Django Rest Framework之认证系统之redis数据库的token认证(token过期时间)

    1:登录视图 redis_cli.py文件: import redis Pool= redis.ConnectionPool(host='localhost',port=6379,decode_res ...

  9. 源码剖析Django REST framework的认证方式及自定义认证

    源码剖析Django REST framework的认证方式 在前面说过,请求到达REST framework的时候,会对request进行二次封装,在封装的过程中会对客户端发送过来的request封 ...

随机推荐

  1. 记录使用 Cake 进行构建并制作 nuget 包

    书接上一回(https://www.cnblogs.com/h82258652/p/4898983.html)?[手动狗头] 前段时间折腾了一下,总算是把我自己的图片缓存控件(https://gith ...

  2. 浅谈ESB中的DataRow、DataSet、DataBag 、DataBox

    1 背景概述 笔者在学习公司产品AEAI ESB 的时候经常需要从数据库获取信息并将数据信息保存到一个结果变量中,为统计分析提供特定格式的数据以及跨数据库同步数据时通常会用到DataRow.DataS ...

  3. 788. Rotated Digits

    X is a good number if after rotating each digit individually by 180 degrees, we get a valid number t ...

  4. linux 环境安装

    lnmp.lamp.lnmpa一键安装包(Updated: 2019-02-17) 422 A+ 所属分类:工具 这个脚本是使用shell编写,为了快速在生产环境上部署lnmp/lamp/lnmpa( ...

  5. 一道面试题(C语言)

    题:输入一个数,列出所有加和等于该数的式子. 分析: 以 6 为例: 从上面的分析就比较容易找到规律了. C语言代码: #include <stdio.h> int main() { in ...

  6. 生成多个git ssh密钥

    如果你已经有了一套名为 id_rsa 的公秘钥,将要生成另外一个公钥,比如 aysee ,你也可以使用任何你喜欢的名字. 步骤如下: 1.生成一个新的自定义名称的公钥: ssh-keygen -t r ...

  7. 移动端h5页面的那些坑

    最近一直在写移动端页面,由于之前写移动端写的比较少,所以此次踩过许多坑.特此总结一下: 1.<input type='button'>背景色在ios中的兼容性,颜色发白 解决办法:在全局样 ...

  8. HttpClient和HttpURLConnection的使用和区别(下)

    转自来自点击打开链接 接着上一篇,我们继续来分析HttpURLConnection的使用,以及两者的共同点和区别. 目录 用法 HttpURLConnection 区别 引用资料 用法 HttpURL ...

  9. 678 "流浪地球"为什么是个好地方?(系统越复杂拥有好运气的机会也就越大)

    运气,其实就是一个复杂系统孕育出的,超出已知经验的解决方案.它不是没有产生机制.只不过,这个机制太复杂,涉及的因素太多.我们没法复制.所以,我们只能笼统的,把这套机制称为运气,或者命数. 举个例子,假 ...

  10. Python -- tabulate 模块,

    pip install tabulate >>> from tabulate import tabulate >>> table = [["Sun&quo ...