59 - Django Rest Framework
参考:
https://www.cnblogs.com/wupeiqi/articles/7805382.html
其他博文推荐
内容回顾&今日内容
内容回顾: 1. git add commit push pull fetch merge rebase stash branch checkout 协同开发: - 每个人一个分支 - review - bug分支 - dev - master/release tag 做版本管理 2. celery - 立即执行任务 - 指定时间触发任务 - 多任务管理目录结构 今日内容相关的内容回顾 3. 面向对象的封装 request = 请求相关所有的数据 class NewRequest(object): def __init__(self,req,parser,auth): self._request = req self.parser = parser self.auth = auth obj = NewRequest(request,'x1','x2') obj.parser obj.auth obj._request obj._request.POST 4. 列表生成式 v = [ i for i in range(10)] v = [0,1,2,3,...9] 示例: class Auth1: pass class Auth2: pass class Foo(object): cls_list = [Auth1,Auth2] def get_cls_list_obj(self): # return [Auth1(),Auth2() ] return [ cl() for cl in self.cls_list] obj = Foo() ret = obj.get_cls_list_obj() print(ret) 5. 给你一个字符串 "utils.auth.Auth",帮我找到Auth类,并实例化。 6. django中可以连接memcached/redis(安装组件) PS:访问频率 今日内容: - restful api - djangorestframework框架 - 认证 - 权限 - 访问频率限制 - 版本 - 解析器 - 序列化 - 请求数据进行校验 - 对queryset进行序列化 - 分页 - 路由 - 视图 - 渲染器 内容详细: 1. restful - 什么是接口? URL: http://127.0.0.1:8000/index/ 约束类: Interface IFoo: def send(self):pass class F1(IFoo): def send(self): pass class F2(IFoo): def msg(obj): obj.send() obj = F2() msg(obj) - 学生管理(CURD) 原来的方式: url(r'^user_add/', views.user_add), url(r'^user_edit/', views.user_edit), url(r'^user_del/', views.user_del), url(r'^user_list/', views.user_list), 现在的方式(遵循了restful规范的方式): url(r'^user/', views.user) - GET,获取用户 - POST,增加用户 - PUT,修改 - DELETE,删除 - restful规范 - http/https - 在URL上标识,是API: www.luffycity.com/api/ 不存跨域 api.luffycity.com 可能存在跨域 - www.luffycity.com/api/salary 面向资源编程,把网络上的任务东西都视为资源。 - 版本: www.luffycity.com/api/v1/salary v1.luffycity.com/api/salary - 请求方法: - GET,获取用户 - POST,增加用户 - PUT,修改 - DELETE,删除 - 状态码 - - {code:1000,msg:'xxxx'} - 错误信息 {code:1000,msg:'xxxx'} - 条件过滤 https://www.example.com/v1/api/zoos?page=2&per_page=100 - 返回结果: - Hypermedia API { code:20000, data:{ id:1, name:'银秋良', age:19, # department_id:9 department_id: http://www.xxx.com/api/v1/department/9 } } -> 以后再写接口,尽量遵循restful规范 -> 以后再写程序,不遵循restful规范 什么时候会写接口: - 前后端分离: 前端:vue.js react.js angular.js 后端:接口 - App或智能设备: 终端:app、玩具 后端:接口 忠告:看看vue.js 2. djangorestframework框架 pip3 install django==1.11.7 pip3 install djangorestframework a. 基于django CBV实现API b.框架 前夕: 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', ] 1. 版本 urlpatterns = [ url(r'^(?P<version>\w+)/user/', views.UserView.as_view()), ] class UserView(APIView): # versioning_class = URLPathVersioning 优先级高于全局 def get(self,request,*args,**kwargs): print(request.version) return HttpResponse('user.get') def post(self,request,*args,**kwargs): return HttpResponse('user.post') REST_FRAMEWORK = { "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning", 'ALLOWED_VERSIONS':['v1','v2'], "DEFAULT_VERSION":'v1', } 2. 认证 需求:为授权用户访问时,显示用户未登陆(token=asdfasdfadsfasdfasdf) 3. 权限 4. 用户访问频率控制、节流 { throttle_fengfeng: [1527322520.5065649, 1527322512.5065649,1527322509.5065649,], } 第一步:去列表中pop已经失效 1527322599.5065649 - 60 = 1527322539.5065649 fengfeng:[] 第二步:计算个数: fengfeng:[1527322599.5065649,] 5. 序列化 - 对QuerySet、对象进行序列化 - 对用户请求数据进行校验 6. 解析器 - 根据请求头Content-Type的值,进行请求体数据的解析。 7. 分页 - 页码 - limit offset - cursor 8. 路由 # 列表: SaView.get # 添加: SaView.post url(r'^sa/$', views.SaView.as_view()), # 详细 : SaView.get # 修改 : SaView.put # 局部 : SaView.patch # 删除 : SaView.delete url(r'^sa/(?P<pk>\d+)/$', views.SaView.as_view()), 9. 视图: from django.views import View from rest_framework.views import APIView # 所有都没有 from rest_framework.generics import GenericAPIView # 如果视图继承我以及我的子孙,路由中必须添加参数。 from rest_framework.viewsets import GenericViewSet from rest_framework.viewsets import ModelViewSet # 所有功能 10. 渲染器
基于django CBV实现的API
目录结构
代码详细
settings.py
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app01.apps.App01Config', # 添加app ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', # 'django.middleware.csrf.CsrfViewMiddleware', # 注释掉csrf,使用rest_framework的时候就不用注释掉这个了,会自动处理掉csrf的问题 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]
m1day05/urls.py
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.UserView.as_view()), ]
m1day05/views.py
from django.shortcuts import render,HttpResponse from django.views import View class UserView(View): def get(self,request,*args,**kwargs): return HttpResponse('user.get') def post(self,request,*args,**kwargs): return HttpResponse('user.post')
结果:
GET 访问 http://127.0.0.1:8000/user/ user.get
POST访问 http://127.0.0.1:8000/user/ 返回 user.post
重点:CBV的逻辑,找核心的dispatch方法,从url的as_views开始找。
rest_framework版本API
目录结构
代码详细
settings.py
INSTALLED_APPS = [
'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app01.apps.App01Config', # 安装app 'rest_framework', # 添加rest_framework,使错误信息变的好看,会用到这个 ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', # 不需要关闭csrf 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'm1day05.urls'
..........
..........
..........
STATIC_URL = '/static/' # REST_FRAMEWORK的所有相关配置都放在这里 REST_FRAMEWORK = {
# 版本控制相关 "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning", #还有其他的类,比如QueryParameterVersioning
'ALLOWED_VERSIONS':['v1','v2'], # 支持的版本号
"DEFAULT_VERSION":'v1', # 默认的版本号
}
m1day05/urls.py
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+)/user/', views.UserView.as_view()), # 定义了版本控制的字段,必须填写版本!!!这块也是可以做路由分发的,下面的例子会是路由分发的模式 ]
m1day05/views.py
from django.shortcuts import render,HttpResponse from django.views import View from rest_framework.views import APIView from rest_framework.versioning import URLPathVersioning,QueryParameterVersioning class UserView(APIView): # 继承rest_framework的APIView,而不是原来CBV继承的VIEW def get(self,request,*args,**kwargs): # 做了封装,request是新request # request._request是老的request # print(request.authenticators) # print(self.versioning_class) self.dispatch # 这块是用来看源码的入口 print(request.version) return HttpResponse('user.get') def post(self,request,*args,**kwargs): return HttpResponse('user.post')
结果:
GET 访问 http://127.0.0.1:8000/v1/user/ user.get
POST访问 http://127.0.0.1:8000/v1/user/ 返回 user.post
十大特性
1,版本控制
第一步:添加参数
第二步:
第三步:
详细例子参考上面 rest_framework版本API 的代码示例
备注
1,version 放在配置文件则全局使用,放在视图函数或者类中则单独使用
2,跨域问题解决
jsonp
cors 分为:
简单请求
复杂请求 先发送预检(options),然后在发送get/post
3,form表单只支持 get、post 两种方式,所以可以不遵循restful接口规范。
4,使用rest的Response,web好看,curl 加header json返回的就是字典
备注:
Django Rest Framework源码流程:
2,用户认证
认证可以返回3种情况
1,返回(user,auth)的元组
2,触发异常(认证失败)
3,返回None(这种就是匿名用户)
目录结构
代码详细
m1day05/settings.py
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app01.apps.App01Config', # app 'rest_framework', # rest_framework ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', #csrf可不关 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'm1day05.urls' .......... .......... .......... STATIC_URL = '/static/' REST_FRAMEWORK = { "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning", 'ALLOWED_VERSIONS':['v1','v2'], "DEFAULT_VERSION":'v1', # 认证相关,定义匿名用户 "UNAUTHENTICATED_USER":None, # 直接定义为None "UNAUTHENTICATED_TOKEN":None, # "UNAUTHENTICATED_USER":lambda :None, # 定义为匿名函数,执行返回None # "UNAUTHENTICATED_TOKEN":lambda :None, # 认证相关,定义认证使用的类,如果在CBV的类中定义了authentication_classes,authentication_classes的优先级更高 # 使用app01.utils.auth.TokenAuthtication这种字符串的方式定义,django会跟进字符串找到对应的类 # 采用这种方式的话,就必须将TokenAuthtication类放在utils目录auth模块下 'DEFAULT_AUTHENTICATION_CLASSES': ['app01.utils.auth.TokenAuthtication',] }
m1day05/urls.py
from django.conf.urls import url,include urlpatterns = [ # 将api标识和version版本控制放在这里,那么在路由分发的情况下,api标识和version版本控制在下面的app中都适用 url(r'^api/(?P<version>\w+)/', include('app01.urls')), ]
app01/utils/auth.py
from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from app01 import models class TokenAuthtication(BaseAuthentication): def authenticate(self, request): """ BaseAuthentication类中的authenticate这个方法如下: Authenticate the request and return a two-tuple of (user, token). raise NotImplementedError(".authenticate() must be overridden.") :param request: :return: (user,auth) 表示认证成功,并将元组分别复制给request.user/request.auth raise AuthenticationFailed('认证失败') 表示认证失败 None, 表示匿名用户 """ token = request.query_params.get('token') if not token: raise AuthenticationFailed('用户Token未携带') token_obj = models.UserToken.objects.filter(token=token).first() if not token_obj: raise AuthenticationFailed('token已失效或错误') return (token_obj.user.username,token_obj) # return a two-tuple of (user, token)
app01/models.py
from django.db import models class UserInfo(models.Model): username = models.CharField(max_length=32) password = models.CharField(max_length=64) class UserToken(models.Model): # 一般将token单独存放在一张表,当然存放在一起也是可以的,onetoone仅是拆表的效果 user = models.OneToOneField('UserInfo') token = models.CharField(max_length=64)
表内容
app01/urls.py
from django.conf.urls import url from app01 import views urlpatterns = [ url(r'^auth/$', views.AuthView.as_view()), url(r'^user/$', views.UserView.as_view()), ]
app01/views.py
# encoding:utf-8 from django.shortcuts import render,HttpResponse from django.http import JsonResponse from rest_framework.views import APIView from app01 import models import uuid class AuthView(APIView): authentication_classes = [] # 这个的优先级更高,这个会覆盖settings中定义的TokenAuthtication,所以这块不需要token验证 def post(self,request,*args,**kwargs): response = {'code':1000} # 安装restful的规范,返回code + 内容 user = request.data.get('username') # request.data是从解析器中获取数据 pwd = request.data.get('password') obj = models.UserInfo.objects.filter(username=user,password=pwd).first() if not obj: response['code'] = 1001 response['msg'] = '用户或密码错误' return JsonResponse(response,json_dumps_params={'ensure_ascii':False}) # 使得返回信息显示中文 token = str(uuid.uuid4()) # update_or_create,没有则创建,存在则更新 models.UserToken.objects.update_or_create(user=obj,defaults={'token':token}) response['token'] = token return JsonResponse(response,json_dumps_params={'ensure_ascii':False}) class UserView(APIView): # 这里继承settings中定义的TokenAuthtication验证方式,所以需要token验证 def get(self,request,*args,**kwargs): self.dispatch print(request.user) print(request.auth) return HttpResponse('user.get') def post(self,request,*args,**kwargs): return HttpResponse('user.post')
效果
执行如下脚本,生成token
import requests ret = requests.post( url='http://127.0.0.1:8000/api/v1/auth/', json={ 'username':'alex', 'password':'123' }, headers={ # 'Content-Type':'application/json' } ) print(ret.text)
输出
{"code": 1000, "token": "f7c1622a-ae2f-4cf2-a121-0822e3d2fa87"}
GET 访问 http://127.0.0.1:8000/api/v1/user/?token=f7c1622a-ae2f-4cf2-a121-0822e3d2fa87 输出 user.get
POST 访问 http://127.0.0.1:8000/api/v1/user/?token=f7c1622a-ae2f-4cf2-a121-0822e3d2fa87 输出 user.post
携带错误token 访问 http://127.0.0.1:8000/api/v1/user/?token=f7c1622a-ae2f-4cf2-a121-0822e3dFSDFSF 显示 "detail": "token已失效或错误"
不带token访问 "detail": "用户Token未携带" 显示 detail": "用户Token未携带
3,用户权限
权限的控制,返回True即有权限,返回False即没权限
必须经过认真后才可以判断权限,因为这个权限的判断是基于认真返回的元组中的request.a
目录结构
代码详细
m1day05/settings.py 同上用户认证
m1day05/urls.py 同上用户认证
app01/utils/auth.py 同上用户认证
app01/utils/permission.py
from rest_framework.permissions import BasePermission class UserPermission(BasePermission): """ 权限的控制,返回True即有权限,返回False即没权限 必须经过认真后才可以判断权限,因为这个权限的判断是基于认真返回的元组中的request.auth这个对象来判断权限的 """ def has_permission(self,request,view): print(request.user) # 返回的是字符串的oldboy print(request.auth) # 返回的是数据库中的对象(UserToken object) user_type_id = request.auth.user.user_type # 可以根据返回的对象做表的关联,然后匹配user_type字段 if user_type_id > 0: return True return False class ManagerPermission(BasePermission): def has_permission(self,request,view): user_type_id = request.auth.user.user_type if user_type_id > 1: return True return False
app01/models.py
from django.db import models class UserInfo(models.Model): username = models.CharField(max_length=32) password = models.CharField(max_length=64) user_type_choices = ( (1,'员工'), (2,'主管'), ) user_type = models.IntegerField(choices=user_type_choices,default=1) # 这样定义choice字段 class UserToken(models.Model): user = models.OneToOneField('UserInfo') token = models.CharField(max_length=64)
表记录
app01/urls.py
from django.conf.urls import url from app01 import views urlpatterns = [ url(r'^auth/$', views.AuthView.as_view()), url(r'^user/$', views.UserView.as_view()), url(r'^salary/$', views.SalaryView.as_view()), # 新增,这个url有权限控制 ]
app01/views.py
from django.shortcuts import render,HttpResponse from django.http import JsonResponse from rest_framework.views import APIView from app01 import models import uuid from app01.utils.permission import UserPermission,ManagerPermission class AuthView(APIView): authentication_classes = [] def post(self,request,*args,**kwargs): response = {'code':1000} user = request.data.get('username') pwd = request.data.get('password') obj = models.UserInfo.objects.filter(username=user,password=pwd).first() if not obj: response['code'] = 1001 response['msg'] = '用户或密码错误' return JsonResponse(response,json_dumps_params={'ensure_ascii':False}) token = str(uuid.uuid4()) models.UserToken.objects.update_or_create(user=obj,defaults={'token':token}) response['token'] = token return JsonResponse(response,json_dumps_params={'ensure_ascii':False}) class UserView(APIView): permission_classes = [UserPermission,] def get(self,request,*args,**kwargs): self.dispatch print(request.user) print(request.auth) return HttpResponse('user.get') def post(self,request,*args,**kwargs): return HttpResponse('user.post') class SalaryView(APIView): # permission_classes 定义的权限判断类 permission_classes = [UserPermission,ManagerPermission, ] def get(self,request,*args,**kwargs): return HttpResponse('...')
使用如下脚本分别为两个用户生成token
import requests # oldboy: 6c5190f7-0d69-4abc-bf23-23bbfd5c6583 # alex : 27699bec-eba6-49e1-b276-d0bc2e65edc0 ret = requests.post( url='http://127.0.0.1:8000/api/v1/auth/', json={ # 'username':'oldboy', 'username':'alex', 'password':'123' }, headers={ 'Content-Type':'application/json' } ) print(ret.text)
访问
alex的token访问 http://127.0.0.1:8000/api/v1/salary/?token=27699bec-eba6-49e1-b276-d0bc2e65edc0 显示 "detail": "You do not have permission to perform this action."
oldboy的token访问 http://127.0.0.1:8000/api/v1/salary/?token=6c5190f7-0d69-4abc-bf23-23bbfd5c6583 显示 ....
4. 用户访问频率控制、节流
实现原理:记录每次访问的时间戳,并记录到列表中,然后计算当前请求的时间戳时间和列表中倒数第一个的时间差,于限流的时间单位做比较
{ throttle_fengfeng: [1527322520.5065649, 1527322512.5065649,1527322509.5065649,], } 第一步:去列表中pop已经失效 1527322599.5065649 - 60 = 1527322539.5065649 fengfeng:[] 第二步:计算个数: fengfeng:[1527322599.5065649,]
源码逻辑参考:
目录结构
代码详细
m1day05/settings.py
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', ] 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', ] # Database # https://docs.djangoproject.com/en/1.11/ref/settings/#databases ....... ....... ....... # 添加缓存相关配置 CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', 'LOCATION': 'D:\m1\m1day05\cache', } } # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.11/howto/static-files/ STATIC_URL = '/static/' REST_FRAMEWORK = { "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning", 'ALLOWED_VERSIONS':['v1','v2'], "DEFAULT_VERSION":'v1', "UNAUTHENTICATED_USER":None, "UNAUTHENTICATED_TOKEN":None, # "UNAUTHENTICATED_USER":lambda :None, # "UNAUTHENTICATED_TOKEN":lambda :None, 'DEFAULT_AUTHENTICATION_CLASSES': ['app01.utils.auth.TokenAuthtication',], # 添加限流相关的全局配置,可以在逻辑中根据用户角色在调整 'DEFAULT_THROTTLE_RATES':{ 'user':'3/m' # 这块写min也行,源码中取的是第一个字母,同样的支持s、m、h、d,但不支持每3h这样的限速 } }
m1day05/urls.py 同上用户权限
app01/utils/auth.py 同上用户权限
app01/utils/permission.py 同上用户权限
app01/utils/throttle.py
from rest_framework.throttling import SimpleRateThrottle class UserRateThrottle(SimpleRateThrottle): scope = 'user' # 这个是全局配置文件中定义的'user':'3/m' def get_cache_key(self, request, view): if request.user: # 如果已经登录,用户名作为缓存的key ident = request.user else: # 如果没有登录,IP作为缓存的key ident = self.get_ident(request) # 'throttle_%(scope)s_%(ident)s' # 给缓存的key加个标识,以方便看出 # throttle_user_fengfeng return self.cache_format % { 'scope': self.scope, 'ident': ident } def allow_request(self, request, view): if request.auth.user.user_type == 1: # 在这块的逻辑,可以根据用户的权限,配置不同的限流开关,默认是走settings.py全局中的限制 # self.num_requests = 3 # self.duration = 60 pass else: self.num_requests = 6 # 对应全局配置中 3/m 中的数字3 return super(UserRateThrottle,self).allow_request(request, view)
app01/models.py 同上
表记录
app01/urls.py同上
app01/views.py
from django.shortcuts import render,HttpResponse from django.http import JsonResponse from rest_framework.views import APIView from app01 import models import uuid from app01.utils.permission import UserPermission,ManagerPermission from app01.utils.throttle import UserRateThrottle from rest_framework.versioning import BaseVersioning from rest_framework.authentication import BaseAuthentication from rest_framework.permissions import BasePermission from rest_framework.throttling import BaseThrottle class AuthView(APIView): authentication_classes = [] def post(self,request,*args,**kwargs): response = {'code':1000} user = request.data.get('username') pwd = request.data.get('password') obj = models.UserInfo.objects.filter(username=user,password=pwd).first() if not obj: response['code'] = 1001 response['msg'] = '用户或密码错误' return JsonResponse(response,json_dumps_params={'ensure_ascii':False}) token = str(uuid.uuid4()) models.UserToken.objects.update_or_create(user=obj,defaults={'token':token}) response['token'] = token return JsonResponse(response,json_dumps_params={'ensure_ascii':False}) class UserView(APIView): permission_classes = [UserPermission,] throttle_classes = [UserRateThrottle, ] def get(self,request,*args,**kwargs): self.dispatch print(request.user) print(request.auth) return HttpResponse('user.get') def post(self,request,*args,**kwargs): return HttpResponse('user.post') class SalaryView(APIView): # 这里是应用权限 permission_classes = [UserPermission,ManagerPermission, ] # 这里是应用限流 throttle_classes = [UserRateThrottle,] def get(self,request,*args,**kwargs): return HttpResponse('...')
效果:
根据如下脚本获取token
import requests # alex : 8fe765fc-661f-4d6d-924f-cfc5c4013293 # oldboy: b596cab8-3991-448b-9308-80efb253ddc9 ret = requests.post( url='http://127.0.0.1:8000/api/v1/auth/', json={ # 'username':'oldboy', 'username':'alex', 'password':'123' }, headers={ 'Content-Type':'application/json' } ) print(ret.text)
访问
1,alex的token 访问 http://127.0.0.1:8000/api/v1/salary/?token=8fe765fc-661f-4d6d-924f-cfc5c4013293 提示:"detail": "You do not have permission to perform this action."
因为alex的user_type 不大于1,所以没有权限
2,oldboy的token访问6次 http://127.0.0.1:8000/api/v1/salary/?token=b596cab8-3991-448b-9308-80efb253ddc9 后出现 "detail": "Request was throttled. Expected available in 58 seconds."
注意,这个时间每次访问是会变化的
5. 序列化
支持两种序列化
# 继承自serializers.Serializer的序列化
# 继承自serializers.ModelSerializer的序列化
目录结构
代码详细
m1day05/settings.py 同上,但是不依赖上面settings中的配置
m1day05/urls.py 同上用户权限
app01/utils/auth.py 同上用户权限【不依赖】
app01/utils/permission.py 同上用户权限【不依赖】
app01/utils/throttle.py 同上用户权限【不依赖】
app01/models.py
from django.db import models class UserInfo(models.Model): username = models.CharField(max_length=32) password = models.CharField(max_length=64) user_type_choices = ( (1,'员工'), (2,'主管'), ) user_type = models.IntegerField(choices=user_type_choices,default=1) class UserToken(models.Model): user = models.OneToOneField('UserInfo') token = models.CharField(max_length=64) class Host(models.Model): # 序列化和分页用的是这个model hostname = models.CharField(max_length=32) port = models.IntegerField(default=80) host_types = ( (1,'web'), (2,'存储'), ) htype = models.IntegerField(choices=host_types,default=1) sa = models.ForeignKey('Sa') departs = models.ManyToManyField('Depart') class Depart(models.Model): # 解析器用的是这个model title = models.CharField(max_length=32) class Sa(models.Model): # 路由用的是这个model name = models.CharField(max_length=32)
表数据如下
app01/urls.py
from django.conf.urls import url from app01 import views urlpatterns = [ # url(r'^auth/$', views.AuthView.as_view()), # url(r'^user/$', views.UserView.as_view()), # url(r'^salary/$', views.SalaryView.as_view()), url(r'^host/$', views.HostView.as_view()), # 这行生效 # url(r'^depart/$', views.DepartView.as_view()), # url(r'^parser/$', views.ParserView.as_view()), # url(r'^pager/$', views.PagerView.as_view()), # 列表: SaView.get # 添加: SaView.post # url(r'^sa/$', views.SaView.as_view()), # 详细 : SaView.get # 修改 : SaView.put # 局部 : SaView.patch # 删除 : SaView.delete # url(r'^sa/(?P<pk>\d+)/$', views.SaView.as_view()), ]
app01/views.py
对QuerySet、对象进行序列化
v1: # 继承自serializers.Serializer的序列化
from rest_framework.views import APIView from rest_framework.response import Response from app01 import models from rest_framework import serializers # 继承自serializers.Serializer的序列化 class HostSerializer(serializers.Serializer): # 定义序列化 hostname = serializers.CharField() port = serializers.IntegerField() sa_id = serializers.CharField(source='sa.id') # source指定关联的源字段信息 sa_name = serializers.CharField(source='sa.name') htype_id = serializers.CharField(source='htype') htype_title = serializers.CharField(source='get_htype_display') # 获取choice中数字对应的中文 departs = serializers.SerializerMethodField() # 指定回调函数 def get_departs(self,obj): # 定义上面字段对应的回调函数 return [{'id':row.id,'title':row.title} for row in obj.departs.all()] class HostView(APIView): authentication_classes = [] def get(self,request,*args,**kwargs): ret = {'code':2001} try: # 序列化多个对象 queryset = models.Host.objects.all() ser = HostSerializer(instance=queryset,many=True) # 使用自定义的序列化来序列化queryset集合 # 序列化单个对象 # obj = models.Host.objects.all().first() # ser = HostSerializer(instance=obj, many=False) # 使用自定义的序列化来序列化单个queryset对象 ret['data'] = ser.data except Exception as e: ret['code'] = 2002 ret['msg'] = '获取数据失败' return Response(ret)
访问http://127.0.0.1:8000/api/v1/host/,返回
{ "code": 2001, "data": [ { "hostname": "c1.com", "port": 80, "sa_id": "1", "sa_name": "峰峰", "htype_id": "1", "htype_title": "web", "departs": [ { "id": 1, "title": "公关部" }, { "id": 2, "title": "市场部" } ] }, { "hostname": "c2.com", "port": 80, "sa_id": "1", "sa_name": "峰峰", "htype_id": "1", "htype_title": "web", "departs": [ { "id": 1, "title": "公关部" }, { "id": 2, "title": "市场部" }, { "id": 3, "title": "销售部" } ] }, { "hostname": "c3.com", "port": 80, "sa_id": "2", "sa_name": "银秋良", "htype_id": "1", "htype_title": "web", "departs": [ { "id": 1, "title": "公关部" }, { "id": 3, "title": "销售部" } ] }, { "hostname": "c4.com", "port": 80, "sa_id": "2", "sa_name": "银秋良", "htype_id": "1", "htype_title": "web", "departs": [ { "id": 2, "title": "市场部" }, { "id": 3, "title": "销售部" } ] } ] }
v2: # 继承自serializers.ModelSerializer的序列化【更简单】
from rest_framework.views import APIView from rest_framework.response import Response from app01 import models
from rest_framework import serializers # 继承自serializers.ModelSerializer的序列化 class NewHostSerializer(serializers.ModelSerializer): htype_title = serializers.CharField(source='get_htype_display') xxxxxxx = serializers.SerializerMethodField() # 指定回调函数 class Meta: model = models.Host # fields = '__all__' ,这种方式拿不到choice中数字对应的中文 # 可以配合exclude结合使用
# exclude=["authors",] #显示authors以外的字段(单独使用)
fields = ['hostname','port','htype','htype_title','sa','departs','xxxxxxx'] # depth代表关联表的层级(根据foreignkey 一级一级的找),默认是0,范围 0-10,特别耗性能一般不建议使用 # depth获得的数据,也可以通过可以自定义回调函数SerializerMethodFiel的方式获取 depth = 1 def get_xxxxxxx(self, obj): # 定义上面字段对应的回调函数 """ get_xxxxxxx的效果和depth = 1 的效果一样 :param obj: :return: """ return [{'id': row.id, 'title': row.title} for row in obj.departs.all()] class HostView(APIView): authentication_classes = [] def get(self,request,*args,**kwargs): ret = {'code':2001} try: queryset = models.Host.objects.all() ser = NewHostSerializer(instance=queryset,many=True) # 使用自定义的序列化来序列化queryset集合 # obj = models.Host.objects.all().first() # ser = NewHostSerializer(instance=obj, many=False) # 使用自定义的序列化来序列化单个queryset对象 ret['data'] = ser.data except Exception as e: ret['code'] = 2002 ret['msg'] = '获取数据失败' return Response(ret)
访问 http://127.0.0.1:8000/api/v1/host/
返回
{ "code": 2001, "data": [ { "hostname": "c1.com", "port": 80, "htype": 1, "htype_title": "web", "sa": { "id": 1, "name": "峰峰" }, "departs": [ { "id": 1, "title": "公关部" }, { "id": 2, "title": "市场部" } ], "xxxxxxx": [ { "id": 1, "title": "公关部" }, { "id": 2, "title": "市场部" } ] }, { "hostname": "c2.com", "port": 80, "htype": 1, "htype_title": "web", "sa": { "id": 1, "name": "峰峰" }, "departs": [ { "id": 1, "title": "公关部" }, { "id": 2, "title": "市场部" }, { "id": 3, "title": "销售部" } ], "xxxxxxx": [ { "id": 1, "title": "公关部" }, { "id": 2, "title": "市场部" }, { "id": 3, "title": "销售部" } ] }, { "hostname": "c3.com", "port": 80, "htype": 1, "htype_title": "web", "sa": { "id": 2, "name": "银秋良" }, "departs": [ { "id": 1, "title": "公关部" }, { "id": 3, "title": "销售部" } ], "xxxxxxx": [ { "id": 1, "title": "公关部" }, { "id": 3, "title": "销售部" } ] }, { "hostname": "c4.com", "port": 80, "htype": 1, "htype_title": "web", "sa": { "id": 2, "name": "银秋良" }, "departs": [ { "id": 2, "title": "市场部" }, { "id": 3, "title": "销售部" } ], "xxxxxxx": [ { "id": 2, "title": "市场部" }, { "id": 3, "title": "销售部" } ] } ] }
对用户请求数据进行校验
app01/views.py
from rest_framework import serializers class DepartSerializer(serializers.Serializer): title = serializers.CharField(min_length=6,max_length=18) # 验证函数的格式:validate_字段名称 def validate_title(self,value): from rest_framework import exceptions if value == 'alexxxx': raise exceptions.ValidationError('姓名不能是傻逼') return value class DepartView(APIView): authentication_classes = [] def post(self,request,*args,**kwargs): ret = {'code':3001} try: ser = DepartSerializer(data=request.data) if ser.is_valid(): # 通过对象.is_valid()方法进行验证 # 验证成功 models.Depart.objects.create(**ser.data) # 创建的时候传递 **ser.data else: ret['code'] = 3002 ret['msg'] = ser.errors except Exception as e: ret['code'] = 3003 ret['msg'] = '添加失败' return Response(ret)
验证函数:validate_字段名称,然后调用 对象.is_valid() 方法来验证。
validate_title 钩子函数,类似clean_
6. 解析器
- 根据请求头Content-Type的值,进行请求体数据的解析。
支持多种解析器
from rest_framework.parsers import JSONParser,FormParser
但是默认一般默认只支持json即可
只需要在settings.py中添加如下
REST_FRAMEWORK = { .......'DEFAULT_PARSER_CLASSES':['rest_framework.parsers.JSONParser',], ....... }
app01/urls.py
urlpatterns = [ ....... url(r'^parser/$', views.ParserView.as_view()), ....... ]
app01/views.py
class ParserView(APIView): authentication_classes = [] def post(self,request,*args,**kwargs): self.dispatch print(request.data,type(request.data)) return Response('okok 666')
postman模拟请求头访问
指定 'Content-Type':'application/json' 才可以
7. 分页
支持多种分页方式:
from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination,CursorPagination
代码详细
PageNumberPagination方式
在settings.py中指定
REST_FRAMEWORK = { .........'PAGE_SIZE':2 }
app01/urls.py
from django.conf.urls import url from app01 import views urlpatterns = [ ...... url(r'^pager/$', views.PagerView.as_view()), ]
app01/views.py
from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination,CursorPagination class MyPageNumberPagination(PageNumberPagination): page_size_query_param = 'size' # 这个定义了如果想显示指定每页显示的条目数的参数,默认使用settings.py全局中定义的PAGE_SIZE max_page_size = 10 # 这个定义了每个分页最大的条目数,防止一页10w条数据这样的请求 # Client can control the page using this query parameter. page_query_param = 'page' # 这个是从 PageNumberPagination 这个类里面获取,并在MyPageNumberPagination这个子类中定义的 # 也可以不设置page_query_param,则默认用父类中指定的 class PagerSerializer(serializers.ModelSerializer): class Meta: model = models.Host fields = ['hostname','port',] class PagerView(APIView): authentication_classes = [] def get(self,request,*args,**kwargs): queryset = models.Host.objects.all().order_by('id') pg = MyPageNumberPagination() ret = pg.paginate_queryset(queryset=queryset,request=request,view=self) ser = PagerSerializer(instance=ret,many=True) return Response(ser.data)
效果,请求的时候可以同时指定size【每页显示的个数,如果不指定默认从settings.py中读取PAGE_SIZE】参数和page参数【第几页】
不指定size参数
指定size参数
CursorPagination方式
app01/views.py
from rest_framework.pagination import CursorPagination class MyCursorPagination(CursorPagination): page_size = 1 ordering = 'id' # 默认是根据_create 排序的,所以这个改成自己的字段 class PagerSerializer(serializers.ModelSerializer): class Meta: model = models.Host fields = ['hostname','port',] class PagerView(APIView): authentication_classes = [] def get(self,request,*args,**kwargs): queryset = models.Host.objects.all().order_by('id') pg = MyCursorPagination() ret = pg.paginate_queryset(queryset=queryset,request=request,view=self) ser = PagerSerializer(instance=ret,many=True) response = { 'code':1000, 'next':pg.get_next_link(), # 下一页链接 'prev':pg.get_previous_link(), # 上一页链接 'data':ser.data } return Response(response)
postman访问示例
应用场景
cursor 页数加密的应用场景 数据库1000条 数据库1000w条 越往后扫描越慢,页数控制最多200页,博客园和抽屉 如果扫10000-10010,快速的方法是 添加where id > 9999 然后在进行limit offset,但是直接page=1000000000也不可以 最终的解决是: 最小id < 查找 < 最大id cursor 会记录当前页,和最小id和最大id
8. 路由(自动化路由)
一般不建议使用
# from rest_framework import routers # router = routers.DefaultRouter() # router.register(r'sa', views.SaView)
9. 视图
from django.views import View from rest_framework.views import APIView #所有功能都没有,自己定义【适用于复杂的逻辑】 from rest_framework.generics import GenericAPIView # 如果视图继承我以及我的子孙,路由中必须添加桉树 from rest_framework.viewsets import GenericViewSet from rest_framework.viewsets import ModelViewSet #所有功能都有,无需自己定义【使用于仅简单的增删改查】
继承自APIView
代码详细
app01/urls.py
from django.conf.urls import url from app01 import views urlpatterns = [ # ....... # 列表: SaView.get # 添加: SaView.post url(r'^sa/$', views.SaView.as_view()), # 详细 : SaView.get # 修改 : SaView.put # 局部 : SaView.patch # 删除 : SaView.delete url(r'^sa/(?P<pk>\d+)/$', views.SaView.as_view()), ]
app01/views.py
class SaSerialiser(serializers.ModelSerializer): class Meta: model = models.Sa fields = "__all__" class SaView(APIView): authentication_classes = [] def get(self,request,*args,**kwargs): pk = kwargs.get('pk') if not pk: queryset = models.Sa.objects.all() ser = SaSerialiser(instance=queryset,many=True) else: obj = models.Sa.objects.filter(id=pk).first() ser = SaSerialiser(instance=obj, many=False) return Response(ser.data) def post(self,request,*args,**kwargs): pass def put(self,request,*args,**kwargs): kwargs.get('pk') pass def patch(self,request,*args,**kwargs): kwargs.get('pk') pass def delete(self,request,*args,**kwargs): kwargs.get('pk') pass
访问单个详情
访问所有元素
继承自ModelViewSet
app01/urls.py
urlpatterns = [ ######APIView # # 列表: SaView.get # # 添加: SaView.post # url(r'^sa/$', views.SaView.as_view()), # # 详细 : SaView.get # # 修改 : SaView.put # # 局部 : SaView.patch # # 删除 : SaView.delete # url(r'^sa/(?P<pk>\d+)/$', views.SaView.as_view()),
######看这里ModelViewSet # 列表: SaView.get # 添加: SaView.post url(r'^sa/$', views.SaView.as_view({'get':'list','post':'create'})), url(r'^sa\.(?P<format>[a-z0-9]+)$', views.SaView.as_view({'get':'list','post':'create'})), # 支持后缀.json的形式访问 # 详细 : SaView.get # 修改 : SaView.put # 局部 : SaView.patch # 删除 : SaView.delete url(r'^sa/(?P<pk>\d+)/$', views.SaView.as_view({'get':"retrieve",'put':'update','patch':'partial_update','delete':'destroy'})), url(r'^sa/(?P<pk>\d+)\.(?P<format>[a-z0-9]+)$', views.SaView.as_view({'get':"retrieve",'put':'update','patch':'partial_update','delete':'destroy'})), # url(r'^', include(router.urls)), ]
put对所有字段修改
patch 对单个字段修改
app01/views.py
class SaSerialiser(serializers.ModelSerializer): class Meta: model = models.Sa fields = "__all__" # from django.views import View # from rest_framework.views import APIView # from rest_framework.generics import GenericAPIView # from rest_framework.viewsets import GenericViewSet from rest_framework.viewsets import ModelViewSet from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer,AdminRenderer class SaView(ModelViewSet): authentication_classes = [] renderer_classes = [JSONRenderer,BrowsableAPIRenderer] # 这个指定的是渲染器,一般可不用,BrowsableAPIRendere不能单独存在 queryset = models.Sa.objects.all() serializer_class = SaSerialiser
如果只使用增加,则只继承一个类即可
class ModelViewSet(mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, GenericViewSet):
访问http://127.0.0.1:8000/api/v1/sa/
访问http://127.0.0.1:8000/api/v1/sa.json
10. 渲染器
如下就是渲染器
全局使用渲染器
REST_FRAMEWORK = { ....... 'DEFAULT_RENDERER_CLASSES':['rest_framework.renderers.JSONRenderer',] }
局部使用渲染器
from rest_framework.viewsets import ModelViewSet from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer,AdminRenderer,DocumentationRenderer # 这个是导入渲染器 class SaView(ModelViewSet): authentication_classes = [] renderer_classes = [JSONRenderer,BrowsableAPIRenderer] # 局部范围使用渲染器,BrowsableAPIRendere不能单独存在 queryset = models.Sa.objects.all() serializer_class = SaSerialis
自动生成API文档
http://www.django-rest-framework.org/topics/documenting-your-api/
知识点补充
1,使用json返回汉字而不是unide字符串,添加参数 ensure_ascii=False
示例
# encoding:utf-8 ret = { 'name':'理解' } import json v1 = json.dumps(ret) v2 = json.dumps(ret,ensure_ascii=False) print(v1) print(v2)
返回结果
{"name": "\u7406\u89e3"} {"name": "理解"}
在django中使用如下 json_dumps_params={'ensure_ascii':False} 来实现相同的效果
JsonResponse(response,json_dumps_params={'ensure_ascii':False}) # response是要返回的字典
强制返回json格式,渲染器时:请求?format = json
参考:https://www.cnblogs.com/wupeiqi/articles/7805382.html extra_args 给上面的字段传参数
RESTful 规范
https://blog.csdn.net/aaronthon/article/details/81714497