前言:
Django REST framework,是1个基于Django搭建 REST风格API的框架;
1、什么是API呢?
API就是访问即可获取数据的url地址,下面是一个最简单的 Django API,访问http://127.0.0.1:8000/,返回用户列表;
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^user/', views.user), ]
from django.shortcuts import render,HttpResponse from django.http import JsonResponse users=['大波','张开','人间' ] def user(request): return JsonResponse(users,safe=False)
2、什么是restfunAPI呢?
如果新增增加用户功能,再向用户暴露1个API接口 http://127.0.0.1:8000/useradd/?new_user='A'
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^user/', views.user), url(r'^useradd/', views.user_add), #增加用户功能 ]
from django.shortcuts import render,HttpResponse from django.http import JsonResponse users=['大波','张开','人间' ] def user(request): return JsonResponse(users,safe=False) def user_add(request): #增加用户功能 new_user=request.GET.get('new_user') users.append(new_user) return JsonResponse(users,safe=False)
在增加用户功能上再添加删除、编辑用户功能 向用户暴露 4个api接口
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^user/', views.user), #显示用户列表 url(r'^useradd/$', views.user_add), #增加用户功能 url(r'^userdelete/\d+/$', views.user_delete), # 删除用户功能 url(r'^useredit/\d+/$', views.user_edit), #编辑用户信息 ]
如果按照这种开发模式,随着项目功能的完善,弊端也会暴露出来,维护的API接口也会越来越多,在协作开发中出现种种问题;
于是一种API编写规范出现了,他就是RESTful规范;
在1个视图中通过判断用户的请求method不同(get/post/put/delete),后台做不同的操作;这样就可以只暴露1个API给用户并且维护了代码的整洁,这就是restful 规范之一;
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^user/', views.user), #显示用户列表 url(r'^user/(?P<pk>\d+)/$',views.user), #编辑和删除需要一个PK 在URL中 ]
from django.shortcuts import render,HttpResponse from django.http import JsonResponse users=['大波','张开','人间' ] def user(request,*args,**kwargs): if request.method=='GET': #如果请求方法为 GET 获取数据列表 return JsonResponse(users,safe=False) elif request.method=='POST': # 如果请求方法为 POST 添加列表数据 return JsonResponse(users, safe=False) elif request.method=='PUT': # 如果请求方法为 PUT,更新操作 pk=kwargs.get('pk') return JsonResponse(users, safe=False) elif request.method=='DELETE': pk=kwargs.get('pk') # 如果请求方法为 DELETE,删除操作 return JsonResponse(users, safe=False)
总结:
restful 风格API有很多规范,接下来我们介绍一下它都有哪些规范?
3、REST framework 请求的生命周期
一、RESTful API设计规范
RESTful API的设计需要遵循很多规范,但是只是建议,如果不遵守这种规范也是可以的,以下是 它部分规范介绍;
1、面向资源
面向资源指得是把网络中可以访问到的任何url都想象成1个资源,这个资源可以是1个文件、 1张图片、1个视频....。
例如:
http://www.le.com/videos/
http://www.le.com/files/
http://www.le.com/pictures/
注意 1级域名后边(videos、files、pictures)都是名词
2、版本号
我们的API不是一成不变的,如果版本迭代也需要体现在url上,以供用户选择不同版本;
例如:
http://www.le.com/v1/files/ 访问版本1API资源
http://www.le.com/v2/files/ 访问版本2API资源
3、返回值
API在放回数据的同时也返回请求的状态码;
例如:
return HttpRespose(josn.dumps(ret),status=200) 访问成功
return HttpRespose(josn.dumps(ret),status=3XX) 权限错误
return HttpRespose(josn.dumps(ret),status=4XX) 资源存在
return HttpRespose(josn.dumps(ret),status=5XX) 服务器错误
详细:
OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。 Accepted - [*]:表示一个请求已经进入后台排队(异步任务) NO CONTENT - [DELETE]:用户删除数据成功。 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。 更多看这里:http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html 常用状态码列表
4、API与用户的通信协议,总是使用https协议
5、域名规范
例如:
前端Vue使用:https://www.le.com
后端Django:https://api.le.com 注意会出现跨域问题
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、返回结果: 根据用户不同的操作,服务器放过的结果应该符合以下规范
GET /collection:返回资源对象的列表(数组) GET /collection/resource:返回单个资源对象 POST /collection:返回新生成的资源对象 PUT /collection/resource:返回完整的资源对象 PATCH /collection/resource:返回完整的资源对象 DELETE /collection/resource:返回一个空文档
8、Hypermedia API,RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。
[ { 'name':'alex', 'age':'18', 'user_type': 'http://www.le.com/api/v1/usergroups/1/' }, { 'name':'alex', 'age':'18', 'user_type': 'DS' }, { 'name':'alex1', 'age':'18', 'user_type': 'DS' }, { 'name':'alex2', 'age':'18', 'user_type': 'DS' } ]
9.总结
restful 规范本质就是1个约束 web 接口,增、删、改、查方法的规范,方便调用方维护、记忆url;
三. 基于DjangoRest Framework框架实现 restful API
如果自己去实现1个restful api需要遵循以上很多restful API规则,实现起来比较困难,
于是Django的 Rest framework框架出现了,通过它就可以无需关注众多restful规范, 快速实现restful API;
0、安装 djangorestframwork pip install djangorestframework -i https://pypi.douban.com/simple/ 1、安装 virtual env(虚拟环境,相当于做了环境隔离) pip install virtualenv 2、创建1个存放虚拟环境的目录 mkdir virtualenv cd virtualenv 3、创建1个干净的虚拟环境(--no-site-packaes) virtualenv --no-site-package Using base prefix 'd:\\python3.6.1' New python executable in D:\virtualenv\ven Installing setuptools, pip, wheel...done. 4、激活虚拟环境 linux source venv/bin/activate DOS cdvirtualenv\venv1\Scripts activate 安装Django >>> exit() (venv1) D:\virtualenv\venv1\Scripts>pip install django 5、退出虚拟环境 deactivate
Django framework源码分析
想要深入了解 Rest framework框架是如何实现restful API的就要分析它的源码;
Django framework源码入口
由于所有CBV首先要执行dispatch方法,所有它就是Django framework源码的入口
from django.shortcuts import render,HttpResponse from rest_framework.views import APIView #导入 APIView #类继承了APIView就是 restfulAPI了,但是APIView继承了Django的view本质是对Django view的一种二次封装; class UsersView(APIView): def dispatch(self, request, *args, **kwargs): """ `.dispatch()` is pretty much the same as Django's regular dispatch, but with extra hooks for startup, finalize, and exception handling. """ self.args = args self.kwargs = kwargs #1.二次封装request(原request,parsers=解析用户请求的数据,authenticators=认证相关, negotiator=选择相关, parser_context='封装的self和参数') request = self.initialize_request(request, *args, **kwargs) self.request = request #2、request被更换为封装完毕的request self.headers = self.default_response_headers # deprecate? #3、执行initial 获取版本信息、 认证、权限、节流 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 def get(self,request,*args,**kwargs): self.dispatch() # 执行APIView的 dispatch 也是 framwork的源码入口 return HttpResponse('...') #
Django framework分析结果
经过对Django framework分析得知 Django restframework的生命周期
1、中间件 2、路由系统 3、CBV/FBV(视图) 4、执行dispath方法 二次封装request(原request,解析用户请求的数据,authenticators=认证相关, negotiator=选择相关, parser_context='封装的self和参数') try 获取版本信息 认证 权限 节流 respose=执行 GET/POST/PUT/DELETE方法 except respose = 异常 finally 处理响应内容
1、版本号设置
我们的API不是一成不变的,如果版本迭代就需要以版本号加以区别,让用户选择不同的版本;
1.1 版本控制源码分析
1.2 配置使用
视图配置
方式1: http://127.0.0.1:8000/users/?version=v1
from django.shortcuts import render,HttpResponse from rest_framework.views import APIView #导入 APIView from rest_framework.versioning import QueryParameterVersioning from rest_framework.versioning import URLPathVersioning class UsersView(APIView): versioning_class=QueryParameterVersioning #http://127.0.0.1:8000/users/?version=v1 def get(self,request,*args,**kwargs): print(request.version) return HttpResponse('...')
方式2:http://127.0.0.1:8000/v3/users/
from rest_framework.views import APIView #导入 APIView from rest_framework.versioning import QueryParameterVersioning from rest_framework.versioning import URLPathVersioning class UsersView(APIView): versioning_class = URLPathVersioning #http://127.0.0.1:8000/v3/users/ def get(self,request,*args,**kwargs): print(request.version) return HttpResponse('...')
全局配置
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' #组册 rest_framework APP ] REST_FRAMEWORK = { "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",#设置使用的类 "VERSION_PARAM":"version", #设置默认参数名称 "DEFAULT_VERSION":'v1', #设置默认版本 "ALLOWED_VERSIONS":['v1','v2'] #设置允许的版本 }
获取和反向生成url
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^(?P<version>\w+)/users/$', views.UsersView.as_view(),name='xxxxx'), # url(r'^(?P<version>\w+)/users/$', views.UsersView.as_view()), ]
from django.shortcuts import render,HttpResponse from rest_framework.views import APIView #导入 APIView # from rest_framework.versioning import QueryParameterVersioning # from rest_framework.versioning import URLPathVersioning class UsersView(APIView): # versioning_class=QueryParameterVersioning #http://127.0.0.1:8000/users/?version=v1 # versioning_class = URLPathVersioning #http://127.0.0.1:8000/v3/users/ def get(self,request,*args,**kwargs): print(request.versioning_scheme.reverse(viewname='xxxxx',request=request)) #反向生成URL return HttpResponse('...')
2、用户身份认证
根据用户请求(tocken)做用户身份认证,成功request.user能获取到用户名,否则AnonymousUser /none;
2.1 认证源码分析
APIView 设置认证相关功能
Request类调用认证功能
class UsersView(APIView): def get(self,request,*args,**kwargs): self.dispatch() print(self.authentication_classes ) return HttpResponse('...')
class APIView(View): '''1.3 去系统配置文件,获取认证相关的类s [BasicAuthentication(),SessionAuthentication()] ''' authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES def dispatch(self, request, *args, **kwargs): self.args = args self.kwargs = kwargs #1 二次封装 request(self.initialize_request) request = self.initialize_request(request, *args, **kwargs) self.request = request #替换成新的request self.headers = self.default_response_headers # deprecate? try: #2、执行认证功能 self.initial(request, *args, **kwargs) except Exception as exc: pass return self.response def initialize_request(self, request, *args, **kwargs): '''1.1 执行initialize_reques,执行authenticators=self.get_authenticators() 调用get_authenticators()获取[auth对象,auth对象] ''' return Request( request, authenticators=self.get_authenticators(), ) def get_authenticators(self): '''1.2 get_authenticators 去系统配置文件中获取 认证相关的类, 返回列表''' return [auth() for auth in self.authentication_classes] #2.1 认证功能调用perform_authentication方法 def initial(self, request, *args, **kwargs): self.perform_authentication(request) #2.2 perform_authentication调用了 request类的user方法 def perform_authentication(self, request): request.user
#2、调用request的user方法,执行认证功能 class Request(): def __init__(request,authenticators=None): self._request = request self.authenticators = authenticators or () #or 元祖 #2.1 request.user方法调用了 _authenticat方法 @property def user(self): if not hasattr(self, '_user'): self._authenticate() return self._user def _authenticate(self): # 2.2遍历 self.authenticators,request初始环节封装的[认证对象,认证对象 ],并执行认证对象的authenticate方法 for authenticator in self.authenticators: ''' 异常终止整个循环 返回元祖,整个循环终止 返回None,进入下一次循环 ''' try: user_auth_tuple = authenticator.authenticate(self) except exceptions.APIException: self._not_authenticated() raise if user_auth_tuple is not None: self._authenticator = authenticator self.user, self.auth = user_auth_tuple return self._not_authenticated() #2.3 如果循环出现 异常,则设置 request.user = 匿名用户 def _not_authenticated(self): 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
2.2 使用配置
通过对 APIView 认证环节的源码分析,得知认证环节本质就是执行 BasicAuthentication、SessionAuthentication类中的authenticate方法;
如果authenticate方法执行成功 return (username,None ) ,则设置 request.user属性为username,否则设置为AnonymousUser匿名用户;
如果authenticate方法执行成功 return None ,则继续执行 下一个认证类的authenticate方法;
如果authenticate方法 raise exceptions.AuthenticationFailed('认证失败')抛出异常,则整个认证环节终止;
局部视图使用认证功能
# 源码中api_settings都代指 Django setings配置文件中的REST_FRAMEWORK 里的东西 REST_FRAMEWORK = { "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",#设置使用的类 "VERSION_PARAM":"version", #设置默认参数名称 "DEFAULT_VERSION":'v1', #设置默认版本 "ALLOWED_VERSIONS":['v1','v2'] , #设置允许的版本 # 设置为 None,没有通过认证的用户,request.user匿名用户 =None "UNAUTHENTICATED_USER":None, # 设置为UNAUTHENTICATED_TOKEN tocken =None 'UNAUTHENTICATED_TOKEN':None }
from django.shortcuts import render,HttpResponse from rest_framework.views import APIView #导入 APIView from rest_framework.authentication import BasicAuthentication from rest_framework.authentication import SessionAuthentication from rest_framework.request import Request from rest_framework import exceptions class CustomAuthentication(BasicAuthentication): def authenticate(self, request): return ('egon',None ) #authenticate 返回哪个用户,request.user=哪个用户 def authenticate_header(self, request): pass class UsersView(APIView): authentication_classes =[CustomAuthentication ] def get(self,request,*args,**kwargs): print(request.user) return HttpResponse('...')
自定制认证失败错误信息
from rest_framework.authentication import SessionAuthentication from rest_framework.request import Request from rest_framework import exceptions class CustomAuthentication(BasicAuthentication): def authenticate(self, request): #自定义认证失败异常信息 raise exceptions.AuthenticationFailed('认证失败') def authenticate_header(self, request): pass class UsersView(APIView): authentication_classes =[CustomAuthentication ] def get(self,request,*args,**kwargs): print(request.user) return HttpResponse('...')
全局视图使用认证功能
from rest_framework.authentication import BasicAuthentication from rest_framework.authentication import SessionAuthentication class CustomAuthentication(BasicAuthentication): def authenticate(self, request): return ('alex',None) def authenticate_header(self, request): pass
# 源码中api_settings都代指 Django setings配置文件中的REST_FRAMEWORK 里的东西 REST_FRAMEWORK = { "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",#设置使用的类 "VERSION_PARAM":"version", #设置默认参数名称 "DEFAULT_VERSION":'v1', #设置默认版本 "ALLOWED_VERSIONS":['v1','v2'] , #设置允许的版本 # 设置为 None,没有通过认证的用户,request.user匿名用户 =None "UNAUTHENTICATED_USER":None, # 设置为UNAUTHENTICATED_TOKEN tocken =None 'UNAUTHENTICATED_TOKEN':None, "DEFAULT_AUTHENTICATION_CLASSES":( "app01.utils.CustomAuthentication", ), }
from rest_framework.authentication import SessionAuthentication from rest_framework import exceptions class UsersView(APIView): def get(self,request,*args,**kwargs): print(request.user) # print(self.dispatch()) return HttpResponse('...')
2.3 扩展
基于token的用户认证
token:服务端动态生成的1串用来检验用户身份的字符串,可以放在header、cokies、url参数(安全性较差)、请求体(CSRF token);
token和session类似,不同于 session的是token比较灵活,不仅仅可以cokies里;
from django.shortcuts import render,HttpResponse from rest_framework.views import APIView #导入 APIView from rest_framework.authentication import BasicAuthentication from rest_framework.authentication import SessionAuthentication from rest_framework import exceptions from rest_framework.request import Request from rest_framework.response import Response from app01 import models from django.http import JsonResponse def gen_tcoken(username): import random,hashlib,time ctime=str(time.time()) hash=hashlib.md5(username.encode('utf-8')) hash.update(ctime.encode('utf-8')) return hash.hexdigest() class CustomAuthentication(BasicAuthentication): #认证类 def authenticate(self, request): #从 url的参数中获取客户端携带的tocken tocken=request.query_params.get('tocken') #去数据库中检查tocken是否存在? tocken_obj=models.Token.objects.filter(token=tocken).first() #如果存在 if tocken_obj: #注意还可以返回对象 return (tocken_obj.user,tocken_obj) #否则 自定义错误信息 认证失败 raise exceptions.AuthenticationFailed('认证失败') def authenticate_header(self, request): pass class ZhanggenAuthentication(): ''' 局部使用认证功能的情况下,优先继承该类 ''' authentication_classes = [CustomAuthentication] class LoginView(APIView): #允许匿名用户访问 def post(self, request, *args, **kwargs): ret = {'coede': 1000, 'msg': ''} username = request.data.get('username') pwd = request.data.get('password') user = models.Userinfo.objects.filter(user=username, pwd=pwd) if user: # 根据user=user去查找,如果找到更新 如果没有找到创建defaults={} 中的数据 tk = gen_tcoken(username) models.Token.objects.update_or_create(user=user, defaults={'token': tk}) ret['coede'] = 1001 ret['token'] = tk else: ret['msg'] = '用户名或者密码错误' return JsonResponse(ret) class IndexView(ZhanggenAuthentication,APIView): #不允许匿名用户访问 def get(self,request,*args,**kwargs): print(request.user) print(request.user.user) return Response('通过认证!')
from django.db import models class Userinfo(models.Model): user=models.CharField(max_length=32) pwd=models.CharField(max_length=64) email=models.CharField(max_length=64) user_type_choices=( (1,'普通用户'), (2,'文艺用户'), (3,'其他用户'), ) user_type_id=models.IntegerField(choices=user_type_choices,default=1) #设置 one to one 1个用户不能在不同设备上登录 #设置 Freikey 支持1个用户 在不同设备上同时登录 class Token(models.Model): user=models.OneToOneField(Userinfo) token=models.CharField(max_length=64)
def gen_tcoken(username): import random,hashlib,time ctime=str(time.time()) hash=hashlib.md5(username.encode('utf-8')) hash.update(ctime.encode('utf-8')) return hash.hexdigest()
class LoginView(APIView): #允许匿名用户访问 def post(self, request, *args, **kwargs): ret = {'coede': 1000, 'msg': ''} username = request.data.get('username') pwd = request.data.get('password') user = models.Userinfo.objects.filter(user=username, pwd=pwd) if user: # 根据user=user去查找,如果找到更新 如果没有找到创建defaults={} 中的数据 tk = gen_tcoken(username) models.Token.objects.update_or_create(user=user, defaults={'token': tk}) ret['coede'] = 1001 ret['token'] = tk else: ret['msg'] = '用户名或者密码错误' return JsonResponse(ret)
class CustomAuthentication(BasicAuthentication): #认证类 def authenticate(self, request): #从 url的参数中获取客户端携带的tocken tocken=request.query_params.get('tocken') #去数据库中检查tocken是否存在? tocken_obj=models.Token.objects.filter(token=tocken).first() #如果存在 if tocken_obj: #注意还可以返回对象 return (tocken_obj.user,tocken_obj) #否则 自定义错误信息 认证失败 raise exceptions.AuthenticationFailed('认证失败') def authenticate_header(self, request): pass
class ZhanggenAuthentication(): ''' 局部使用认证功能的情况下,优先继承该类 ''' authentication_classes = [CustomAuthentication] class IndexView(ZhanggenAuthentication,APIView): #不允许匿名用户访问 def get(self,request,*args,**kwargs): print(request.user) print(request.user.user) return Response('通过认证!')
认证失败后定制响应头
def authenticate_header(self, request): return 'Basic realm=api' #设置响应头,认证失败后,浏览器弹窗,再向server发送请求
使用RestAPI认证功能小结
1、创建2张表userinfo 和token表
2、认证类的authenticate方法去请求头中获取token信息,然后去token表中查询token是否存在;
3、查询到token 是正常用户(返回 用户名)否则为匿名用户(raise异常终止认证、或者 return none进行下一个认证)
4、局部应用
方式1::哪个CBV需要认证在类中定义authentication_classes =[CustomAuthentication ]
方式2:额外定义1个类,CBV多继承
方式3:全局配置使用认证功能,那个CBV不使用authentication_classes =[ ]
5、全局使用 在配置文件中配置 ,注意重新创建一个模块,把认证类放里面;
3、权限相关
社会3、6、9,如何实现对API接口的访问权限设置呢?基于IP ?和用户名?
需求:普通用户对UserGroupView视图无访问权限
局部使用:
from django.db import models class Userinfo(models.Model): user=models.CharField(max_length=32) pwd=models.CharField(max_length=64) email=models.CharField(max_length=64) user_type_choices=( (1,'普通用户'), (2,'文艺用户'), (3,'其他用户'), ) user_type_id=models.IntegerField(choices=user_type_choices,default=1) #设置 one to one 1个用户不能在不同设备上登录 #设置 Freikey 支持1个用户 在不同设备上同时登录 class Token(models.Model): user=models.OneToOneField(Userinfo) token=models.CharField(max_length=64)
#权限相关 from rest_framework.permissions import BasePermission,AllowAny class CustomPermission(BasePermission): def has_permission(self, request, view): #view代指那个视图 #如果用户为普通用户,访问的视图是UserGroupView,没有权限拒绝访问 if request.user.user_type_id==1 and isinstance(view,UserGroupView) : return False #返回False 代表没有权限访问 return True # return True #返回True 代表都有权限访问
class UserGroupView(APIView): permission_classes=[CustomPermission] def get(self, request, *args, **kwargs): # print(request.user) print(self.permission_classes) print(request.user.user) return Response('通过认证!')
全局使用:
REST_FRAMEWORK = { "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",#设置使用的类 "VERSION_PARAM":"version", #设置默认参数名称 "DEFAULT_VERSION":'v1', #设置默认版本 "ALLOWED_VERSIONS":['v1','v2'] , #设置允许的版本 # 设置为 None,没有通过认证的用户,request.user匿名用户 =None "UNAUTHENTICATED_USER":None, # 设置为UNAUTHENTICATED_TOKEN tocken =None 'UNAUTHENTICATED_TOKEN':None, "DEFAULT_AUTHENTICATION_CLASSES":( "utils.auth.auth1.CustomAuthentication", ), 'DEFAULT_PERMISSION_CLASSES':[ "utils.permission.per1.CustomPermission",], }
#权限相关 from rest_framework.permissions import BasePermission,AllowAny from app01 import views class CustomPermission(BasePermission): message='无权限访问' #定制错误信息 def has_permission(self, request, view): #view代指那个视图 #如果用户为普通用户,访问的视图是UserGroupView,没有权限拒绝访问 if request.user.user_type_id==1 and isinstance(view,views.UserGroupView) : return False #返回False 代表没有权限访问 return True # return True #返回True 代表都有权限访问
class UserGroupView(APIView): def get(self, request, *args, **kwargs): # print(request.user) print(self.permission_classes) print(request.user.user) return Response('通过认证!')
RestApi访问权限使用小结:
创建权限的类,类中has_permission(self, request, view) return flase代表没有权限访问,return True代表有权限访问,request代指请求信息、view参数代指视图
RestApi的访问权限控制工作在视图层 dispatch方法,RBAC工作在中间件;
4、限制用户访问频率
Django Rest Framework 对用户的访问频率是通过在Django缓存中记录每位用户访问时间,进行访问频率限制的;
用户访问时间记录:以用户唯一标识(IP/token)做键,访问时间戳列表做值,列表的长度为用户访问次数;
{
用户A:[ 访问时间戳1,访问时间戳2],
用户B:[ 访问时间戳1,访问时间戳2]
}
当用户A请求到来:
根据用户的唯一标识(IP/Token/用户名)获取用户A的所有访问记录时间戳列表;
获取当前时间,根据当前时间戳和访问记录列表中的时间戳、列表中时间戳个数,做对比来决定用户A是否可以继续访问;
#根据用户的唯一标识 获取当我用户的所有访问记录 self.history = self.cache.get(self.key, []) #获取当前时间 [最近访问,最早访问 ] self.now = self.timer() # Drop any requests from the history which have now passed the # throttle duration #做限制请求频率操作 while self.history and self.history[-1] <= self.now - self.duration: self.history.pop()#控制用户访问记录列表的中的访问记录,最后1个在配置时间范围内 #超出合理长度 返回False,不能继续访问 if len(self.history) >= self.num_requests: return self.throttle_failure() #访问记录列表在合理长度,return Ture可继续访问 return self.throttle_success()
4.1、局部应用(基于用户对单个视图进行访问频率限制self.key = request.user.user+view.__class__.__name__)
{
用户A视图1:[ 访问时间戳1,访问时间戳2],
用户A视图2:[ 访问时间戳1,访问时间戳2]
}
# 限制访问次数相关 from rest_framework.throttling import BaseThrottle,AnonRateThrottle,SimpleRateThrottle #根据IP class CustomAnonThrottle(SimpleRateThrottle): # 代指配置文件中定义的访问频率的类 {'anon':'5/m'} scope = 'anon' def allow_request(self, request, view): # 已经登录管不着 if request.user: return True # 获取当前访问用户的唯一标识 get_cache_key是抽象方法 ip+字符串格式化 self.key = self.get_cache_key(request, view) # 根据用户的唯一标识 获取当我用户的所有访问记录 self.history = self.cache.get(self.key, []) # 获取当前时间 [最近访问,最早访问 ] self.now = self.timer() # 做限制请求频率操作 while self.history and self.history[-1] <= self.now - self.duration: self.history.pop() # 根据配置文件 次数/时间,中的时间控制用户访问记录列表在合理的时间区间内 # 根据根据配置文件次数/时间中的次数,和列表长度决定用户是否可继续访问 # 超出合理长度 返回False,不能继续访问 if len(self.history) >= self.num_requests: return self.throttle_failure() # 访问记录列表在合理长度,return Ture可继续访问 return self.throttle_success() def get_cache_key(self, request, view): return self.cache_format % { 'scope': self.scope, 'ident': self.get_ident(request) } #根据用户名 class CustomUserThrottle(SimpleRateThrottle): #代指配置文件中定义的访问频率的类 {'anon':'5/m'} scope = 'user' def allow_request(self, request, view): #已经登录管不着 if not request.user: return True #获取当前访问用户的唯一标识 用户名 #self.key = request.user.user#用户对所页面进行访问频率限制 #用户对单个视图 进行访问频率限制 self.key = request.user.user+view.__class__.__name__ #根据用户的唯一标识 获取当我用户的所有访问记录 self.history = self.cache.get(self.key, []) #获取当前时间 [最近访问,最早访问 ] self.now = self.timer() #做限制请求频率操作 while self.history and self.history[-1] <= self.now - self.duration: self.history.pop()#根据配置文件 次数/时间,中的时间控制用户访问记录列表在合理的时间区间内 #根据根据配置文件次数/时间中的次数,和列表长度决定用户是否可继续访问 # 超出合理长度 返回False,不能继续访问 if len(self.history) >= self.num_requests: return self.throttle_failure() #访问记录列表在合理长度,return Ture可继续访问 return self.throttle_success()
4.1、全局应用(基于用户对每个视图进行访问频率限制self.key = request.user.user)
{
用户A:[ 访问时间戳1,访问时间戳2],
}
url(r'^(?P<version>\w+)/usergroup/$', views.UserGroupView.as_view(), name='xxxxx'), #登录用户 4/m url(r'^(?P<version>\w+)/test/$', views.TestView.as_view(), name='xxxxx'), #匿名用户5/m
# 源码中api_settings都代指 Django setings配置文件中的REST_FRAMEWORK 里的东西 REST_FRAMEWORK = { "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",#设置使用的类 "VERSION_PARAM":"version", #设置默认参数名称 "DEFAULT_VERSION":'v1', #设置默认版本 "ALLOWED_VERSIONS":['v1','v2'] , #设置允许的版本 # 设置为 None,没有通过认证的用户,request.user匿名用户 =None "UNAUTHENTICATED_USER":None, # 设置为UNAUTHENTICATED_TOKEN tocken =None 'UNAUTHENTICATED_TOKEN':None, #认证相关配置 "DEFAULT_AUTHENTICATION_CLASSES":( "utils.auth.auth1.CustomAuthentication", ), #权限相关配置 'DEFAULT_PERMISSION_CLASSES':[ "utils.permission.per1.CustomPermission"], #限制访问次数 'DEFAULT_THROTTLE_RATES':{ 'anon':'5/m', #匿名用户5次 'user':'4/m', #登录用户6次 } }
# 限制访问次数相关 from rest_framework.throttling import BaseThrottle,AnonRateThrottle,SimpleRateThrottle #根据IP class CustomAnonThrottle(SimpleRateThrottle): # 代指配置文件中定义的访问频率的类 {'anon':'5/m'} scope = 'anon' def allow_request(self, request, view): # 已经登录管不着 if request.user: return True # 获取当前访问用户的唯一标识 get_cache_key是抽象方法 ip+字符串格式化 self.key = self.get_cache_key(request, view) # 根据用户的唯一标识 获取当我用户的所有访问记录 self.history = self.cache.get(self.key, []) # 获取当前时间 [最近访问,最早访问 ] self.now = self.timer() # 做限制请求频率操作 while self.history and self.history[-1] <= self.now - self.duration: self.history.pop() # 根据配置文件 次数/时间,中的时间控制用户访问记录列表在合理的时间区间内 # 根据根据配置文件次数/时间中的次数,和列表长度决定用户是否可继续访问 # 超出合理长度 返回False,不能继续访问 if len(self.history) >= self.num_requests: return self.throttle_failure() # 访问记录列表在合理长度,return Ture可继续访问 return self.throttle_success() def get_cache_key(self, request, view): return self.cache_format % { 'scope': self.scope, 'ident': self.get_ident(request) } #根据用户名 class CustomUserThrottle(SimpleRateThrottle): #代指配置文件中定义的访问频率的类 {'anon':'5/m'} scope = 'user' def allow_request(self, request, view): #已经登录管不着 if not request.user: return True #获取当前访问用户的唯一标识 用户名 self.key = request.user.user #根据用户的唯一标识 获取当我用户的所有访问记录 self.history = self.cache.get(self.key, []) #获取当前时间 [最近访问,最早访问 ] self.now = self.timer() #做限制请求频率操作 while self.history and self.history[-1] <= self.now - self.duration: self.history.pop()#根据配置文件 次数/时间,中的时间控制用户访问记录列表在合理的时间区间内 #根据根据配置文件次数/时间中的次数,和列表长度决定用户是否可继续访问 # 超出合理长度 返回False,不能继续访问 if len(self.history) >= self.num_requests: return self.throttle_failure() #访问记录列表在合理长度,return Ture可继续访问 return self.throttle_success()
class UserGroupView(APIView): # permission_classes = [] throttle_classes=[CustomAnonThrottle,CustomUserThrottle] def get(self, request, *args, **kwargs): # print(self.dispatch()) print(request.user) # self.dispatch() # print(self.throttle_classes) # print(self.permission_classes) # print(request.user.user) # print(request.META.get('REMOTE_ADDR')) # print(request.META.get('HTTP_X_FORWARDED_FOR')) return Response('通过认证!') class TestView(APIView): permission_classes = [] authentication_classes = [] throttle_classes = [CustomAnonThrottle, CustomUserThrottle] def get(self, request, *args, **kwargs): return Response('test通过认证!')
匿名用户使用IP作为用户唯一标识
class CustomThrottle(SimpleRateThrottle): scope = 'anon' def get_cache_key(self, request, view): # if request.user.is_authenticated: # return None # Only throttle unauthenticated requests. return self.cache_format % { 'scope': self.scope, 'ident': self.get_ident(request) }
#限制访问次数 'DEFAULT_THROTTLE_RATES':{'anon':'5/m'}
class UserGroupView(APIView): # permission_classes = [] throttle_classes=[CustomThrottle] def get(self, request, *args, **kwargs): # print(request.user) # self.dispatch() # print(self.throttle_classes) # print(self.permission_classes) # print(request.user.user) return Response('通过认证!')
匿名用户5/m
url(r'^(?P<version>\w+)/test/$', views.TestView.as_view(), name='xxxxx'), #匿名用户5/m
#限制访问次数 'DEFAULT_THROTTLE_RATES':{ 'anon':'5/m', #匿名用户5次 'user':'4/m', #登录用户6次 }
#根据IP class CustomAnonThrottle(SimpleRateThrottle): # 代指配置文件中定义的访问频率的类 {'anon':'5/m'} scope = 'anon' def allow_request(self, request, view): # 已经登录管不着 if request.user: return True # 获取当前访问用户的唯一标识 get_cache_key是抽象方法 ip+字符串格式化 self.key = self.get_cache_key(request, view) # 根据用户的唯一标识 获取当我用户的所有访问记录 self.history = self.cache.get(self.key, []) # 获取当前时间 [最近访问,最早访问 ] self.now = self.timer() # 做限制请求频率操作 while self.history and self.history[-1] <= self.now - self.duration: self.history.pop() # 根据配置文件 次数/时间,中的时间控制用户访问记录列表在合理的时间区间内 # 根据根据配置文件次数/时间中的次数,和列表长度决定用户是否可继续访问 # 超出合理长度 返回False,不能继续访问 if len(self.history) >= self.num_requests: return self.throttle_failure() # 访问记录列表在合理长度,return Ture可继续访问 return self.throttle_success() def get_cache_key(self, request, view): return self.cache_format % { 'scope': self.scope, 'ident': self.get_ident(request) }
class TestView(APIView): permission_classes = [] authentication_classes = [] throttle_classes = [CustomAnonThrottle, CustomUserThrottle] def get(self, request, *args, **kwargs): return Response('test通过认证!')
登录用户4/m
url(r'^(?P<version>\w+)/usergroup/$', views.UserGroupView.as_view(), name='xxxxx'), #登录用户 4/m
#限制访问次数 'DEFAULT_THROTTLE_RATES':{ 'anon':'5/m', #匿名用户5次 'user':'4/m', #登录用户6次 }
#根据用户名 class CustomUserThrottle(SimpleRateThrottle): #代指配置文件中定义的访问频率的类 {'anon':'5/m'} scope = 'user' def allow_request(self, request, view): #已经登录管不着 if not request.user: return True #获取当前访问用户的唯一标识 用户名 self.key = request.user.user #根据用户的唯一标识 获取当我用户的所有访问记录 self.history = self.cache.get(self.key, []) #获取当前时间 [最近访问,最早访问 ] self.now = self.timer() #做限制请求频率操作 while self.history and self.history[-1] <= self.now - self.duration: self.history.pop()#根据配置文件 次数/时间,中的时间控制用户访问记录列表在合理的时间区间内 #根据根据配置文件次数/时间中的次数,和列表长度决定用户是否可继续访问 # 超出合理长度 返回False,不能继续访问 if len(self.history) >= self.num_requests: return self.throttle_failure() #访问记录列表在合理长度,return Ture可继续访问 return self.throttle_success()
class UserGroupView(APIView): # permission_classes = [] throttle_classes=[CustomAnonThrottle,CustomUserThrottle] def get(self, request, *args, **kwargs): # print(self.dispatch()) print(request.user) # self.dispatch() # print(self.throttle_classes) # print(self.permission_classes) # print(request.user.user) # print(request.META.get('REMOTE_ADDR')) # print(request.META.get('HTTP_X_FORWARDED_FOR')) return Response('通过认证!')
4.3、练习题
要求:
登录URL: 无权限限制,要求用户登录并返回token;
首页URL:无权限限制,匿名用户限制 2/m, 登录用户 4/m ;
订单URL: 匿名用户无法查看(权限) ,登录用户 4/m;
from django.db import models from django.db import models class Userinfo(models.Model): user=models.CharField(max_length=32) pwd=models.CharField(max_length=64) email=models.CharField(max_length=64) user_type_choices=( (1,'普通用户'), (2,'文艺用户'), (3,'其他用户'), ) user_type_id=models.IntegerField(choices=user_type_choices,default=1) #设置 one to one 1个用户不能在不同设备上登录 #设置 Freikey 支持1个用户 在不同设备上同时登录 class Token(models.Model): user=models.OneToOneField(Userinfo) token=models.CharField(max_length=64)
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^login/',views.LoginView.as_view()), url(r'^index/',views.IndexView.as_view()), url(r'^order/',views.OrderView.as_view()), ]
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' #组册 rest_framework APP ] # 源码中api_settings都代指 Django setings配置文件中的REST_FRAMEWORK 里的东西 REST_FRAMEWORK = { "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",#设置使用的类 "VERSION_PARAM":"version", #设置默认参数名称 "DEFAULT_VERSION":'v1', #设置默认版本 "ALLOWED_VERSIONS":['v1','v2'] , #设置允许的版本 # 设置为 None,没有通过认证的用户,request.user匿名用户 =None "UNAUTHENTICATED_USER":None, # 设置为UNAUTHENTICATED_TOKEN tocken =None 'UNAUTHENTICATED_TOKEN':None, # 权限相关配置 'DEFAULT_PERMISSION_CLASSES': ["utils.permission.permission1.CustomPermission"], #认证相关配置 "DEFAULT_AUTHENTICATION_CLASSES":( "utils.auth.auth1.CustomAuthentication", ), #限制访问次数 'DEFAULT_THROTTLE_RATES':{ 'anon':'2/m', #匿名用户5次 'user':'4/m', #登录用户6次 } }
from rest_framework import exceptions from rest_framework.authentication import BasicAuthentication from app01 import models class CustomAuthentication(BasicAuthentication): #认证类 def authenticate(self, request): #从 url的参数中获取客户端携带的tocken tocken=request.query_params.get('tocken') #如果是匿名用户 url中没有携带tocken返回None不抛出异常,表示运行匿名用户访问; if not tocken: return (None,None) #raise exceptions.AuthenticationFailed('认证失败') 抛出异常表示不允许匿名用户登录 #去数据库中检查tocken是否存在? tocken_obj=models.Token.objects.filter(token=tocken).first() #如果没有在数据中查询到用户携带tocken也允许访问 if not tocken_obj: return (None,None) #如果存在 return (tocken_obj.user,tocken_obj) #如果自定义认证类的authenticate方法return了(None,None),没有raise异常代表允许匿名用户访问
#权限相关 from rest_framework.permissions import BasePermission,AllowAny from app01 import views class CustomPermission(BasePermission): message='匿名用户无权限访问,订单页面。' #定制错误信息 def has_permission(self, request, view): #view代指那个视图 #如果用户为普通用户,访问的视图是UserGroupView,没有权限拒绝访问 if not request.user and isinstance(view,views.OrderView) : return False #返回False 代表没有权限访问 return True # return True #返回True 代表都有权限访问
from django.shortcuts import render from utils.throttle.throttle1 import CustomAnonThrottle,CustomUserThrottle from rest_framework.views import APIView #导入 APIView from rest_framework.authentication import BasicAuthentication from rest_framework.authentication import SessionAuthentication from app01 import models from rest_framework.request import Request from rest_framework.response import Response from django.http import JsonResponse #生成动态Tocken def gen_tcoken(username): import random,hashlib,time ctime=str(time.time()) hash=hashlib.md5(username.encode('utf-8')) hash.update(ctime.encode('utf-8')) return hash.hexdigest() ##认证相关 class LoginView(APIView): authentication_classes = [] def get(self,request,*args,**kwargs): ret={'code':666,'token':None,'msg':None} username=request.GET.get('username') # 由于API对request进行二次封装GET请获取参数可以使用request.query_params.get('pwd') #由于API对request进行二次封装 POST获取参数可以使用request.data.get('pwd') pwd=request.query_params.get('pwd') user_obj=models.Userinfo.objects.filter(user=username,pwd=pwd).first() if user_obj: tk=gen_tcoken(username) models.Token.objects.update_or_create(user=user_obj, defaults={'token': tk}) ret['token']=tk else: ret['code']=444 ret['msg']='用户名或者密码错误' return JsonResponse(ret) class IndexView(APIView): # 如果在全局配置了权限认证,在单个authentication_classes = []代指本视图不做认证 throttle_classes = [CustomAnonThrottle, CustomUserThrottle] def get(self,request,*args,**kwargs): return Response('通过主页认证') class OrderView(APIView): ''' 订单页面:用户登录之后才能访问,每分钟访问4次 认证环节:允许匿名用户 权限:不允许匿名用户对 访问 OrderView视图 节流:4/m ''' throttle_classes = [CustomUserThrottle] def get(self,request): return Response('通过订单页面认证')
5、解析器(parser)
REST framework的解析器:根据客户端发送的content-type请求头, 选择对应的解析器, 对请求体内容进行处理。
5.1源码逻辑
a、首先把self.get_parsers(姑娘)和get_content_negotiator(劳保)都封装到request对象中;
return Request( request, parsers=self.get_parsers(), negotiator=self.get_content_negotiator(), )
b、执行request.data调用解析功能negotiator根据 content_type挑选对应的parsers做解析工作
5.2:限制API接口仅处理请求头中设置content-type/json 和content-type/form的请求体
from rest_framework.parsers import JSONParser from rest_framework.parsers import FormParser from rest_framework.parsers import MultiPartParser from rest_framework.negotiation import DefaultContentNegotiation class ParserView(APIView): parser_classes=[JSONParser,FormParser] #只处理请求头中包含:Content - Type:application/jison和Content - Type:application/form def get(self,request,*args,**kwargs): return Response ('Parsing success') def post(self,request,*args,**kwargs): print(request.data) # self.dispatch() return Response('Parsing success')
接口测试:PostmanAPI接口测试工具
6、framework序列化和用户请求数据验证
后台通过Django ORM查询到数据都是QuerySet数据类型,这种数据类型不可以直接json.dumps()序列化响应给客户端,REST framework自带序列化功能,可以将数据库查询到的Foreign Key、Many to Many、Choice字段序列化为字典响应给客户端,还可以对用户提交的数据进行的验证(功能类似Form验证);
序列化
6.1:单表查询结果序列化
#自定义序列化类 from rest_framework import serializers class UserSerializers(serializers.Serializer): user=serializers.CharField() pwd=serializers.CharField() email=serializers.CharField() user_type_id=serializers.IntegerField() #应用 class SerializeView(APIView): def get(self,request,*args,**kwargs): user_list=models.Userinfo.objects.all() #同时想返回序列化数据 many=True,如果是models.Userinfo.objects.all().first()使用many=Flase ser=UserSerializers(instance=user_list,many=True) return Response(ser.data)
6.2:source连表查询结果序列化(外键 1对多关系)
#自定义序列化类 from rest_framework import serializers class UserSerializers(serializers.Serializer): user=serializers.CharField() pwd=serializers.CharField() email=serializers.CharField() user_type_id=serializers.IntegerField() #source代指连表操作获取数据 ug=serializers.CharField(source='ug.title') #应用 class SerializeView(APIView): def get(self,request,*args,**kwargs): user_list=models.Userinfo.objects.all() #同时想返回序列化数据 many=True,如果是models.Userinfo.objects.all().first()使用many=Flase ser=UserSerializers(instance=user_list,many=True) return Response(ser.data)
正向连表 外键字段.xx
class TestSerializer(serializers.ModelSerializer): #1对多字段 house=serializers.CharField(source='House.addr') #1对多字段 car=serializers.CharField(source='car.titile') #多对多字段 madam=serializers.CharField(source='madam.all') car_level=serializers.CharField(source='car.get_car_level_display') class Meta: model=models.Person fields=['name','house','car','madam','car_level']
source还支持反向连表查询
person=serializers.CharField(source='person_set.all')
depth 连表深度:自动连表,连表深度最大为10
class UserSerializers(serializers.ModelSerializer): pwd=serializers.HyperlinkedIdentityField(view_name='xxxx') class Meta: model=models.Userinfo fields='__all__' depth=2 #自动联表
如果是数据库中普通字段在自定制serializers类中定义fileds=[‘字段名称’ ]属性即可,但是遇到choices(中文)、Many to Many字段怎么处理呢?
获取choice字段中文: get_choice字段_display
获取多对多字段数据
方法1:
CharField()中的source参数之所以支持 外键跨表、choice字段获取中文名、多对多字段获取,原理是因为CharField类中的2个方法:
1、get_attribute 去数据库中获取值
2、to_representation 在页面上显示值
如果自己实现这2种方法,也可以自定制获取、显示外键、多对多、choice字段数据;
class MyFielf(serializers.CharField): def get_attribute(self,instance): #instance 代表每一行 madam_list=instance.madam.all() return madam_list def to_representation(self,value): #value 就是get_attribute return的结果 ret=[] for row in value: ret.append({'name':row.name}) return ret class TestSerializer(serializers.ModelSerializer): madams=MyFielf() class Meta: model=models.Person fields=['name','madams']
方法2:
SerializerMethodField字段结合method_name=' 自定制方法'参数
class ModelCourseDetaiSerializer(serializers.ModelSerializer): course_name=serializers.CharField(source='course.name') #外键 price_policy=serializers.SerializerMethodField(method_name='get_price_policyss')# 多对多 recommend_courses=serializers.SerializerMethodField(method_name='get_recommend_courses_list') # ContentType teacher_list = serializers.SerializerMethodField(method_name='get_teacher_lists') class Meta: model=models.CourseDetail fields=['id','course_name','price_policy','recommend_courses','teacher_list'] #推荐课程 def get_recommend_courses_list(self,obj): ret=[] courses_list=obj.recommend_courses.all() for i in courses_list: ret.append({'id':i.id,'name':i.name}) return ret #价格策略 def get_price_policyss(self,obj): ret = [] price=obj.course.price_policy.all() for i in price: ret.append({'id':i.id,'name':i.valid_period,'price':i.price}) return ret #课程讲师 def get_teacher_lists(self,obj): ret = [] teachers=obj.teachers.all() for t in teachers: ret.append({'name':t.name,'brief ':t.brief,'role':t.get_role_display()}) return ret class Course_DetailView(APIView): def get(self,*args,**kwargs): pk=kwargs.get('pk',None) detail=models.CourseDetail.objects.get(course_id=pk) ser = ModelCourseDetaiSerializer(instance=detail,many=False) return Response(ser.data)
对提交到API接口的数据做校验
serializers不仅可以把后台ORM获取到的数据序列化后响应客户端,还可以对客户端提交的数据进行验证(类似Form验证功能);
#自定义序列化类 from rest_framework import serializers #复杂验证规则 class PasswordValidator(object): def __init__(self, base): self.base = base def __call__(self, value): if value != self.base: message = '密码必须是 %s.' % self.base raise serializers.ValidationError(message) class UserSerializers(serializers.Serializer): user=serializers.CharField(error_messages={"required":"用户名不能为空"}) #PasswordValidator验证类 pwd=serializers.CharField(validators=[PasswordValidator(base='666')]) email=serializers.CharField() user_type_id=serializers.IntegerField() #source代指连表操作获取数据 ug=serializers.CharField(source='ug.title') #应用 class SerializeView(APIView): def get(self,request,*args,**kwargs): user_list=models.Userinfo.objects.all() #同时想返回序列化数据 many=True,如果是models.Userinfo.objects.all().first()使用many=Flase ser=UserSerializers(instance=user_list,many=True) return Response(ser.data) def post(self, request, *args, **kwargs): #对用户提交数据进行验证 data=request.dat ser = UserSerializers(data=request.data) #验证成功返回 正确数据 if ser.is_valid(): return Response(ser.validated_data) else: #验证失败放回错误信息 print(ser.errors) return Response(ser.errors)
serializers的数据验证功能类似Form验证,Form验证可以结合Models做ModelForm验证,serializers也是可以的;
class UserSerializers(serializers.ModelSerializer): class Meta: model=models.Userinfo fields="__all__" #应用 class SerializeView(APIView): def get(self,request,*args,**kwargs): user_list=models.Userinfo.objects.all() #同时想返回序列化数据 many=True,如果是models.Userinfo.objects.all().first()使用many=Flase ser=UserSerializers(instance=user_list,many=True) return Response(ser.data)
HyperlinkedIdentityField字段,(根据PK反向生成URL)
""" Django settings for RestFrameWor练习 project. Generated by 'django-admin startproject' using Django 1.11.4. For more information on this file, see https://docs.djangoproject.com/en/1.11/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/1.11/ref/settings/ """ import os # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = '#zh*tb-b3=2w^lka#47nztp=-shjy0a1j^8&pqypcaug3d%6oq' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = [] # Application definition 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' #组册 rest_framework APP ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'RestFrameWor练习.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates'),], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'RestFrameWor练习.wsgi.application' # Database # https://docs.djangoproject.com/en/1.11/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } # Password validation # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization # https://docs.djangoproject.com/en/1.11/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.11/howto/static-files/ STATIC_URL = '/static/' # 源码中api_settings都代指 Django setings配置文件中的REST_FRAMEWORK 里的东西 REST_FRAMEWORK = { "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",#设置使用的类 "VERSION_PARAM":"version", #设置默认参数名称 "DEFAULT_VERSION":'v1', #设置默认版本 "ALLOWED_VERSIONS":['v1','v2'] , #设置允许的版本 # 设置为 None,没有通过认证的用户,request.user匿名用户 =None "UNAUTHENTICATED_USER":None, # 设置为UNAUTHENTICATED_TOKEN tocken =None 'UNAUTHENTICATED_TOKEN':None, # 权限相关配置 'DEFAULT_PERMISSION_CLASSES': ["utils.permission.permission1.CustomPermission"], #认证相关配置 "DEFAULT_AUTHENTICATION_CLASSES":( "utils.auth.auth1.CustomAuthentication", ), #限制访问次数 'DEFAULT_THROTTLE_RATES':{ 'anon':'2/m', #匿名用户2次 'user':'4/m', #登录用户4次 } }
注意如果配置文件中配置了版本控制功能,在反向生成URL的时候也要体现;
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^login/',views.LoginView.as_view()), url(r'^index/',views.IndexView.as_view()), url(r'^order/',views.OrderView.as_view()), url(r'^parser/', views.ParserView.as_view()), url(r'^serialize/',views.SerializeView.as_view()), url(r'^serialize\.(?P<format>\w+)',views.SerializeView.as_view()), url(r'test/(?P<pk>\d+)/(?P<version>[v1|v2]+)', views.SerializeView.as_view(),name='xxxx'), ]
class UserSerializers(serializers.ModelSerializer): pwd=serializers.HyperlinkedIdentityField(view_name='xxxx') class Meta: model=models.Userinfo fields='__all__' #应用 class SerializeView(APIView): def get(self,request,*args,**kwargs): user_list=models.Userinfo.objects.all() #同时想返回序列化数据 many=True,如果是models.Userinfo.objects.all().first()使用many=Flase ser=UserSerializers(instance=user_list,many=True,context={'request':request}) return Response(ser.data)
自动生成url字段
class UserSerializers(serializers.HyperlinkedModelSerializer): class Meta: model=models.Userinfo fields=['id','user','pwd','email','url'] #应用 class SerializeView(APIView): def get(self,request,*args,**kwargs): user_list=models.Userinfo.objects.all() #同时想返回序列化数据 many=True,如果是models.Userinfo.objects.all().first()使用many=Flase ser=UserSerializers(instance=user_list,many=True,context={'request':request}) return Response(ser.data) def post(self, request, *args, **kwargs): #对用户提交数据进行验证 data=request.dat ser = UserSerializers(data=request.data) #验证成功返回 正确数据 if ser.is_valid(): return Response(ser.validated_data) else: #验证失败放回错误信息 print(ser.errors) return Response(ser.errors)
Serializer小结:
Serializer虽然叫序列化,却有2大功能 (序列化、Form验证)3大父类(Serializer,ModelSerializer,HyperlinkedModelSerializer);
自定义的serializers类,有3中可继承的父类;
1、serializers.Serializer:手动指定需要序列化和验证的字段
2、serializers.ModelSerializer:自动获取需要序列化和验证的字段(类似ModelFrom功能)
3、serializers.HyperlinkedModelSerializer 自动生成超链接
7、分页
当用户向我们的REST framework提交查询请求时候,怎么能把数据库里的全部数据list出来响应给用户呢?这就涉及到数据分页了,不仅要分页还要考虑分页性能;
a.根据 url参数携带的页码,进行分页;
url(r'^pager/$', views.PagerView.as_view()),
#分页相关配置 from rest_framework.pagination import PageNumberPagination class ZhanggenPagination(PageNumberPagination): #默认一页显示多少条数据 page_size=1 #http://127.0.0.1:8008/pager/?page=1 url参数名称 page_query_param='page' #http://127.0.0.1:8008/pager/?page=1&page_size=2,通过url传参定制一页显示多少条数据; page_size_query_param = 'page_size' #序列化相关配置 class PagerSerializers(serializers.ModelSerializer): #新增一个字段 zhanggen=serializers.CharField(source='ug.title') class Meta: model=models.Userinfo fields='__all__' #CBV应用分页和序列化功能 class PagerView(APIView): def get(self,request,*args,**kwargs): user_list=models.Userinfo.objects.all() #实例化 自定制分页类 obj=ZhanggenPagination() #调用分页类的paginate_queryset方法,根据url的参数 获取分页数据; page_user_list=obj.paginate_queryset(user_list,request,self ) #序列化 ser=PagerSerializers(instance=page_user_list,many=True) #返回结果包页码"next": "http://127.0.0.1:8008/pager/?page=2",点击跳转到下一页; # respose=obj.get_paginated_response(ser.data) # return respose #返回结果不包含页码 return Response(ser.data)
b. 基于某位置偏移进行分页 offset limit
urlpatterns = [url(r'^pager/$', views.PagerView.as_view()), ]
#分页相关配置 from rest_framework.pagination import PageNumberPagination from rest_framework.pagination import LimitOffsetPagination from rest_framework.pagination import CursorPagination #http://127.0.0.1:8008/pager/?offset=2&limit=5 获取3-8条(移动到2,基于2偏移获取5条数据) class Zhanggen1Pagination(LimitOffsetPagination): # 默认每页显示的数据条数 default_limit = 1 # URL中传入的数据位置的参数(基于这个位置偏移) offset_query_param = 'offset' # 偏移量参数 limit_query_param = 'limit' # 最大每页显得条数 max_limit = None #序列化相关配置 class PagerSerializers(serializers.ModelSerializer): #新增一个字段 zhanggen=serializers.CharField(source='ug.title') class Meta: model=models.Userinfo fields='__all__' #CBV应用分页和序列化功能 class PagerView(APIView): def get(self,request,*args,**kwargs): user_list=models.Userinfo.objects.all() #实例化 自定制分页类 obj=Zhanggen1Pagination() #调用分页类的paginate_queryset方法,根据url的参数 获取分页数据; page_user_list=obj.paginate_queryset(user_list,request,view=self ) #序列化 ser=PagerSerializers(instance=page_user_list,many=True) #返回结果包页码"next": "http://127.0.0.1:8008/pager/?page=2",点击跳转到下一页; respose=obj.get_paginated_response(ser.data) return respose #返回结果不包含页码 # return Response(ser.data)
c. 游标分页
urlpatterns = [ url(r'^pager/$', views.PagerView.as_view()), ]
#分页相关配置 from rest_framework.pagination import CursorPagination #http://127.0.0.1:8008/pager/?cursor=cD0x class Zhanggen2Pagination(CursorPagination): # URL传入的游标参数 cursor_query_param = 'cursor' # 默认每页显示的数据条数 page_size = 1 # URL传入的每页显示条数的参数 page_size_query_param = 'page_size' # 每页显示数据最大条数 max_page_size = 1000 # 根据ID从大到小排列 ordering = "id" #序列化相关配置 class PagerSerializers(serializers.ModelSerializer): #新增一个字段 zhanggen=serializers.CharField(source='ug.title') class Meta: model=models.Userinfo fields='__all__' #CBV应用分页和序列化功能 class PagerView(APIView): def get(self,request,*args,**kwargs): user_list=models.Userinfo.objects.all() #实例化 自定制分页类 obj=Zhanggen2Pagination() #调用分页类的paginate_queryset方法,根据url的参数 获取分页数据; page_user_list=obj.paginate_queryset(user_list,request,view=self ) #序列化 ser=PagerSerializers(instance=page_user_list,many=True) #返回结果包页码"next": "http://127.0.0.1:8008/pager/?page=2",点击跳转到下一页; respose=obj.get_paginated_response(ser.data) return respose #返回结果不包含页码 # return Response(ser.data)
分页功能小结:
a. REST framework一共有3中分页方式
1、根据页码和每次能查看的条目数:http://127.0.0.1:8008/pager/?page=1&page_size=2 获取1-2条
2、基于offset偏移位置做limit获取:http://127.0.0.1:8008/pager/?offset=2&limit=5 获取3-8条(移动到2,基于2偏移获取5条数据)
3、游标分页(加密页面,1页1页得翻页)
b. 浅谈分页性能
分页方式1和方式2的缺陷:
以上分页方式最终转化成SQL语句本质是 offset和 limit
第1页:select * from 表 offset 0 limit 5
第2页:select * from 表 offset 5 limit 5
第3页:select * from 表 offset 10 limit 5
.................................
第N页:select * from 表 offset X limit 5
越往后翻 offset的值就会越大,已经翻阅过的数据量越多,需要扫描的数据就会越多,每次翻页数据库查询会把已经翻阅的数据的从头开始再扫描一遍,速度就会越慢;
解决方案:
游标分页之所以会把页码加密,只让用户点击上一页和下一页,进行1页1页的翻页。。。
原因:普通翻页和直接跳转到某页会涉及全表扫描,除非 查询数据库SQL语句中包含 id < n , id >n
翻页时记住当前页最大ID和最小ID,想要到上一页 id>min, 想要到下一页 id>max select * from 表 where id >n offset 0 limit 5
缺点:无法直接跳转到某一页
8、路由系统和视图
REST framework根据配置自动生成 增、删、改、list的url并且自动生成视图中的list、add、delete、put方法;解放你的双手!
自建路由
urlpatterns = [ #路由相关 #http://127.0.0.1:8008/router/ # 支持:GET 查询显示list页面 、 POST:增加 url(r'^router/$', views.RouterView.as_view()), #http://127.0.0.1:8008/router.json #支持:携带.json后缀 url(r'^router\.(?P<format>\w+)$', views.RouterView.as_view()), #http://127.0.0.1:8008/router/1/ # 支持:put修改、delete 删除 url(r'^router/(?P<pk>\d+)/$', views.RouterView.as_view()), #http://127.0.0.1:8008/router/1.json #支持 get 获取单条数据 url(r'^router/(?P<pk>\d+)\.(?P<format>\w+)$', views.RouterView.as_view()), ]
#路由相关 #路由序列化相关 class RouteSerializers(serializers.ModelSerializer): class Meta: model=models.Userinfo fields='__all__' #应用 class RouterView(APIView): def get(self,*args,**kwargs): pk=kwargs.get('pk') if pk: user_obj= models.Userinfo.objects.get(pk=pk) ser=RouteSerializers(instance=user_obj,many=False) else: user_list=models.Userinfo.objects.all() ser = RouteSerializers(instance=user_list, many=True) return Response(ser.data)
GenericAPIView内置了一些方法 完成 分页、序列化功能,无需自己实例化对象了,路由没作任何改变;
from app01 import views urlpatterns = [ # 半自动创建路由 #http://127.0.0.1:8008/router/ # 支持:GET 查询显示list页面 、 POST:增加 url(r'^router/$', views.RouterView.as_view()), #http://127.0.0.1:8008/router.json #支持:携带.json后缀 url(r'^router\.(?P<format>\w+)$', views.RouterView.as_view()), #http://127.0.0.1:8008/router/1/ # 支持:put修改、delete 删除 url(r'^router/(?P<pk>\d+)/$', views.RouterView.as_view()), #http://127.0.0.1:8008/router/1.json #支持 get 获取单条数据 url(r'^router/(?P<pk>\d+)\.(?P<format>\w+)$', views.RouterView.as_view()), ]
#路由序列化相关 from rest_framework import serializers class RouteSerializers(serializers.ModelSerializer): class Meta: model=models.Userinfo fields='__all__' #路由分页相关类 from rest_framework.pagination import PageNumberPagination class ZhanggenPagination(PageNumberPagination): #默认一页显示多少条数据 page_size=1 #http://127.0.0.1:8008/pager/?page=1 url参数名称 page_query_param='page' #http://127.0.0.1:8008/pager/?page=1&page_size=2,通过url传参定制一页显示多少条数据; page_size_query_param = 'page_size' #半自动路由 from rest_framework.generics import GenericAPIView class RouterView(GenericAPIView): #数据库数据 queryset = models.Userinfo.objects.all() #序列化类 serializer_class = RouteSerializers #分页类 pagination_class = ZhanggenPagination def get(self,request,*args,**kwargs): #获取数据库数据 user_list=self.get_queryset() #获取分页相关,对数获取到的数据进行分页 page_user_list=self.paginate_queryset(user_list) #获取序列化相关,对已经分页得数据库数据进行序列化 ser=self.get_serializer(instance=page_user_list,many=True) respose=self.get_paginated_response(ser.data) return respose
半自动路由: as_view({'get': "retrieve", "put": 'update','delete':'destroy'})) 更加as.view方法中的参数,自动触发视图执行
from app01 import views urlpatterns = [ #半自动路由:根据不同的请求自动触发 不同的视图做不同的操作 url(r'^router/$',views.RouterView.as_view({'get':"list","post":'create' } )), #http://127.0.0.1:8008/router/1/ #get请求获取某条数据 put请求某条数据 delete请求删除单条数据 url(r'^router/(?P<pk>\d+)/$', views.RouterView.as_view({'get': "retrieve", "put": 'update','delete':'destroy'})), ]
#路由序列化相关 from rest_framework import serializers class RouteSerializers(serializers.ModelSerializer): class Meta: model=models.Userinfo fields='__all__' from rest_framework.viewsets import ModelViewSet class RouterView(ModelViewSet): #数据库数据 queryset = models.Userinfo.objects.all() #序列化类 serializer_class = RouteSerializers
全自动路由
from django.conf.urls import url,include from app01 import views from rest_framework.routers import DefaultRouter router=DefaultRouter() #http://127.0.0.1:8008/zhanggen/2/ 设置url前缀对应的视图 router.register('zhanggen',views.RouterView) urlpatterns = [ url(r'^',include(router.urls)), ]
#路由序列化相关 class RouteSerializers(serializers.ModelSerializer): class Meta: model=models.Userinfo fields='__all__' from rest_framework.viewsets import ModelViewSet from rest_framework import mixins class RouterView(ModelViewSet): #数据库数据 queryset = models.Userinfo.objects.all() #序列化类 serializer_class = RouteSerializers
framework视图类总结
继承最底层APIView:功能少,可定制性强
继承ModelViewSet:功能丰富,FBV里list、add、delet..方法可定制性查;
9、渲染
REST framework根据用户请求url后缀(http://127.0.0.1:8008/render.json,http://127.0.0.1:8008/render/form/)的不同,渲染出不同的数据格式响应给客户端;
注意:后端渲染功能是结合路由系统中设置format传入的参数来做渲染的,所有不要忘了在路由系统中设置format来接收url传来的参数;
urlpatterns = [ #渲染相关 url(r'^render/$', views.RenderView.as_view()), #http://127.0.0.1:8008/render.json url(r'^render\.(?P<format>\w+)$', views.RenderView.as_view()), #http://127.0.0.1:8008/render/json/ url(r'^render/(?P<format>\w+)/$', views.RenderView.as_view()), ]
#渲染相关 from rest_framework.renderers import JSONRenderer,AdminRenderer,BrowsableAPIRenderer,HTMLFormRenderer #序列化 class RenderSerializers(serializers.ModelSerializer): class Meta: model=models.Userinfo fields='__all__' # class RenderView(APIView): # #支持响应 json、admin 格式的数据 # renderer_classes =[JSONRenderer,AdminRenderer,BrowsableAPIRenderer] # def get(self,request,*args,**kwargs): # user_list=models.Userinfo.objects.all() # ser=RenderSerializers(instance=user_list,many=True) # return Response(ser.data) class RenderView(APIView): #支持响应form格式的数据 renderer_classes =[HTMLFormRenderer] def get(self,request,*args,**kwargs): #注意返回form格式的数据instance必须为单个对象 user_list=models.Userinfo.objects.all().first() ser=RenderSerializers(instance=user_list,many=False) return Response(ser.data)
使用RET framework开展项目总结
0、RET framework 内置了 版本、用户认证、访问权限、限制访问频率公共功能;(全局使用在配置文件配置,在全局使用的前提下,局部视图使用 xxclass=[ ]代表局部不使用)
1、虽然使用全自动url结合ModelViewSet视图可以快速搭建一个简单的API,但是慎用因为后期项目扩展和更新很难修改;
class Zhanggen(APIView): #继承APIView可扩展性强 def get(self, request, *args, **kwargs): pass def post(self, request, *args, **kwargs): pass def put(self, request, *args, **kwargs): pass def delete(self, request, *args, **kwargs): pass
2、1个视图对应4个标准url
#http://127.0.0.1:8008/router/ # 支持:GET 查询显示list页面 、 POST:增加 url(r'^router/$',views.RouterView.as_view()), #http://127.0.0.1:8008/router.json #支持:携带.json后缀 url(r'^router\.(?P<format>\w+)$', views.RouterView.as_view()), #http://127.0.0.1:8008/router/1/ # 支持:put修改、delete 删除 url(r'^router/(?P<pk>\d+)/$', views.RouterView.as_view()), #http://127.0.0.1:8008/router/1.json #支持 get 获取单条数据 url(r'^router/(?P<pk>\d+)\.(?P<format>\w+)$', views.RouterView.as_view()),
3、版本控制
url(r'test/(?P<pk>\d+)/(?P<version>[v1|v2]+)$', views.SerializeView.as_view(),name='xxxx'),