Django-rest Framework
1. FBV CBV
1.1 开发模式
- 普通开发方式(前后端放在一起写)
- 前后端分离
1.2 后端开发
为前端提供URL(API/接口的开发)
注:永远返回HttpResponse
1.3 Django FBV、CBV
# FBV(function base view)
def users(request):
user_list = ['alex','oldboy']
return HttpResponse(json.dumps((user_list)))
# CBV(class base view)
# 路由:
url(r'^students/', views.StudentsView.as_view()),
# 视图:
from django.views import View
class StudentsView(View):
def get(self,request,*args,**kwargs):
return HttpResponse('GET')
def post(self, request, *args, **kwargs):
return HttpResponse('POST')
def put(self, request, *args, **kwargs):
return HttpResponse('PUT')
def delete(self, request, *args, **kwargs):
return HttpResponse('DELETE')
1.3.1 CBV详解
CBV,基于反射实现根据请求方式不同,执行不同的方法。
-
原理:
url -> view方法 -> dispatch方法(反射执行其他:GET/POST/DELETE/PUT)
-
流程:
class StudentsView(View):
def dispatch(self, request, *args, **kwargs):
print('before')
ret = super(StudentsView,self).dispatch(request, *args, **kwargs)
print('after')
return ret
def get(self,request,*args,**kwargs):
return HttpResponse('GET')
def post(self, request, *args, **kwargs):
return HttpResponse('POST')
def put(self, request, *args, **kwargs):
return HttpResponse('PUT')
def delete(self, request, *args, **kwargs):
return HttpResponse('DELETE')
【扩展】:
-
super()方法,
super 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。总之前人留下的经验就是:保持一致性。要不全部用类名调用父类,要不就全部用 super,不要一半一半
1.3.1 回顾Django中间件
中间件顾名思义,是介于request与response处理之间的一道处理过程,相对比较轻量级,并且在全局上改变django的输入与输出
- 中间件流程图
# 重要的四个方法
process_request(self, request)
process_view(self, request, view, *args, **kwargs)
process_exception(self, request, exception)
process_response(self, request, response)
# 模板处理的方法
process_template_response
- 使用中间件做过什么?
- 权限
- 做IP访问频率限制
- 某些IP访问服务器的频率过高,进行拦截,比如限制每分钟不能超过20次。
- 用户登录认证
- django的csrf是如何实现?
process_view方法
- 检查视图是否被 @csrf_exempt (免除csrf_token认证)
- 去请求体或cookie中获取token
FBV,csrf_token认证情况
情况一:所有函数都要认证,某一个不需要认证 (@csrf_exempt)
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',
]
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt # 该函数无需认证
def users(request):
user_list = ['tom','jeck']
return HttpResponse(json.dumps((user_list)))
情况二:所有都不认证,某个函数需要认证(@csrf_protect)
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',
]
from django.views.decorators.csrf import csrf_exempt
@csrf_protect # 该函数需认证
def users(request):
user_list = ['tom','jeck']
return HttpResponse(json.dumps((user_list)))
CBV,csrf_token认证时需要使用
- @method_decorator(csrf_exempt)
- 在dispatch方法中(单独方法无效)
# 方式一:
from django.views.decorators.csrf import csrf_exempt,csrf_protect
from django.utils.decorators import method_decorator
class StudentsView(View):
@method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
return super(StudentsView,self).dispatch(request, *args, **kwargs)
def get(self,request,*args,**kwargs):
print('get方法')
return HttpResponse('GET')
def post(self, request, *args, **kwargs):
return HttpResponse('POST')
def put(self, request, *args, **kwargs):
return HttpResponse('PUT')
def delete(self, request, *args, **kwargs):
return HttpResponse('DELETE')
# 方式二:
from django.views.decorators.csrf import csrf_exempt,csrf_protect
from django.utils.decorators import method_decorator
@method_decorator(csrf_exempt,name='dispatch')
class StudentsView(View):
def get(self,request,*args,**kwargs):
print('get方法')
return HttpResponse('GET')
def post(self, request, *args, **kwargs):
return HttpResponse('POST')
def put(self, request, *args, **kwargs):
return HttpResponse('PUT')
def delete(self, request, *args, **kwargs):
return HttpResponse('DELETE')
2. restful设计规范
什么是RESTful
- REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”
- REST从资源的角度类审视整个网络,它将分布在网络中某个节点的资源通过URL进行标识,客户端应用通过URL来获取资源的表征,获得这些表征致使这些应用转变状态
- REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”
- 所有的数据,不过是通过网络获取的还是操作(增删改查)的数据,都是资源,将一切数据视为资源是REST区别与其他架构风格的最本质属性
- 对于REST这种面向资源的架构风格,有人提出一种全新的结构理念,即:面向资源架构(ROA:Resource Oriented Architecture)
RESTful主要是面向资源(视网络上任何东西都是资源)设计它的接口的, 它有四个规范:
- 每个资源都存在唯一的标识URI
- 每个资源都具有四个动作谓词, 分别是GET/POST/PUT/DELETE
- 每次的动作都是无状态的, 即是HTTP的短连接(Connection: close|keep-alive)
- 交互的资源数据类型一般是json或xml.
3. DRF 框架
安装
pip3 install djangorestframework
3.1 基本流程
url.py
from django.urls import path, include
from web.views.api import TestView
urlpatterns = [
path('test/', TestView.as_view),
]
views.py
from rest_framework.views import APIView
from rest_framework.response import Response
class TestView(APIView):
def dispatch(self, request, *args, **kwargs):
"""
请求到来之后,都要执行dispatch方法,dispatch方法根据请求方式不同触发 get/post/put等方法
注意:APIView中的dispatch方法有好多好多的功能
"""
return super().dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
return Response('GET请求,响应内容')
def post(self, request, *args, **kwargs):
return Response('POST请求,响应内容')
def put(self, request, *args, **kwargs):
return Response('PUT请求,响应内容')
3.2 认证/权限
局部&全局设置
3.2.1 源码
...
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
# 对原生request进行加工
# Request(
# request,
# parsers=self.get_parsers(),
# authenticators=self.get_authenticators(),
# negotiator=self.get_content_negotiator(),
# parser_context=parser_context )
# request(原生的request, [BasicAuthentication对象,]) \
# 获取原生request, 用request._request
# 获取认证类的对象, request.authentications
# 1.封装request
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
# 2.认证
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 initial(self, request, *args, **kwargs):
"""
Runs anything that needs to occur prior to calling the method handler.
"""
self.format_kwarg = self.get_format_suffix(**kwargs)
# Perform content negotiation and store the accepted info on the request
neg = self.perform_content_negotiation(request)
request.accepted_renderer, request.accepted_media_type = neg
# Determine the API version, if versioning is in use.
version, scheme = self.determine_version(request, *args, **kwargs)
request.version, request.versioning_scheme = version, scheme
# Ensure that the incoming request is permitted
# 3.实现认证
self.perform_authentication(request)
# 4.权限判断
self.check_permissions(request)
# 5.访问频率
self.check_throttles(request)
3.2.1.1 实现认证:
def perform_authentication(self, request):
"""
Perform authentication on the incoming request.
Note that if you override this and simply 'pass', then authentication
will instead be performed lazily, the first time either
`request.user` or `request.auth` is accessed.
"""
request.user
@property
def user(self):
"""
Returns the user associated with the current request, as authenticated
by the authentication classes provided to the request.
"""
if not hasattr(self, '_user'):
with wrap_attributeerrors():
# 获取认证对象,进行一步步认证
self._authenticate()
return self._user
def _authenticate(self):
"""
Attempt to authenticate the request using each authentication instance
in turn.
"""
# 循环认证对象[BasicAuthentication对象,]
for authenticator in self.authenticators:
try:
# 执行认证类的authenticate方法
# ① 如果authenticate方法抛出异常,self._not_authenticated()执行
# ② 有返回值,必须是元组(self.user, self.auth)
# ③ 返回None,我不管,下一个认证
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()
def _not_authenticated(self):
"""
Set authenticator, user & authtoken representing an unauthenticated request.
Defaults are None, AnonymousUser & None.
"""
self._authenticator = None
if api_settings.UNAUTHENTICATED_USER:
self.user = api_settings.UNAUTHENTICATED_USER() # AnonymousUser 设置默认值表示匿名用户
else:
self.user = None
if api_settings.UNAUTHENTICATED_TOKEN:
self.auth = api_settings.UNAUTHENTICATED_TOKEN() # None
else:
self.auth = None
3.2.1.2 权限判断
def check_permissions(self, request):
"""
Check if the request should be permitted.
Raises an appropriate exception if the request is not permitted.
"""
# 遍历[权限类的对象,权限类的对象,]
for permission in self.get_permissions():
if not permission.has_permission(request, self):
self.permission_denied(
request,
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
)
权限类对象:
def get_permissions(self):
"""
Instantiates and returns the list of permissions that this view requires.
"""
return [permission() for permission in self.permission_classes] # 列表生成式
如果上面permission.has_permission(request, self):
为False
def permission_denied(self, request, message=None, code=None):
"""
If request is not permitted, determine what kind of exception to raise.
"""
if request.authenticators and not request.successful_authenticator:
raise exceptions.NotAuthenticated()
raise exceptions.PermissionDenied(detail=message, code=code) # 抛出异常 表示权限验证失败
3.2.1.3 访问频率
def check_throttles(self, request):
"""
Check if request should be throttled.
Raises an appropriate exception if the request is throttled.
"""
throttle_durations = []
for throttle in self.get_throttles():
if not throttle.allow_request(request, self):
throttle_durations.append(throttle.wait())
if throttle_durations:
# Filter out `None` values which may happen in case of config / rate
# changes, see #1438
durations = [
duration for duration in throttle_durations
if duration is not None
]
duration = max(durations, default=None)
self.throttled(request, duration)
def get_throttles(self):
"""
Instantiates and returns the list of throttles that this view uses.
"""
return [throttle() for throttle in self.throttle_classes]
3.2.2 认证使用
from django.views import View
from rest_framework.views import APIView
from rest_framework.authentication import BasicAuthentication
from rest_framework import exceptions
from rest_framework.request import Request
class MyAuthentication(object):
def authenticate(self,request):
token = request._request.GET.get('token')
# 获取用户名和密码,去数据校验
if not token:
raise exceptions.AuthenticationFailed('用户认证失败')
return ("zhb",None)
def authenticate_header(self,val):
pass
class DogView(APIView):
authentication_classes = [MyAuthentication,]
def get(self,request,*args,**kwargs):
print(request)
print(request.user) # 拿到上面authenticate方法返回值元组里的第一个元素"zhb"
# self.dispatch # 源码流程入口
ret = {
'code':1000,
'msg':'xxx'
}
return HttpResponse(json.dumps(ret),status=201)
def post(self,request,*args,**kwargs):
return HttpResponse('创建Dog')
def put(self,request,*args,**kwargs):
return HttpResponse('更新Dog')
def delete(self,request,*args,**kwargs):
return HttpResponse('删除Dog')
3.2.3 全局配置(示例代码)
urls.py文件:
from django.conf.urls import url
from django.contrib import admin
from api import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api/v1/auth/$', views.AuthView.as_view()),
url(r'^api/v1/order/$', views.OrderView.as_view()),
url(r'^api/v1/info/$', views.UserInfoView.as_view()),
]
model.py文件 (创建两张表)
from django.db import models
class UserInfo(models.Model):
user_type_choices = (
(1,'普通用户'),
(2,'VIP'),
(3,'SVIP'),
)
user_type = models.IntegerField(choices=user_type_choices)
username = models.CharField(max_length=32,unique=True)
password = models.CharField(max_length=64)
class UserToken(models.Model):
user = models.OneToOneField(to='UserInfo')
token = models.CharField(max_length=64)
settings.py 文件里面全局设置认证,视图里面则不需要设置
REST_FRAMEWORK = {
# 全局使用的认证类
"DEFAULT_AUTHENTICATION_CLASSES":['api.utils.auth.FirstAuthtication','api.utils.auth.Authtication', ],
# "DEFAULT_AUTHENTICATION_CLASSES":['api.utils.auth.FirstAuthtication', ],
# "UNAUTHENTICATED_USER":lambda :"匿名用户"
"UNAUTHENTICATED_USER":None, # 匿名,request.user = None
"UNAUTHENTICATED_TOKEN":None,# 匿名,request.auth = None
"DEFAULT_PERMISSION_CLASSES":['api.utils.permission.SVIPPermission'], # 权限判断全局设置
"DEFAULT_THROTTLE_CLASSES":["api.utils.throttle.UserThrottle"],
"DEFAULT_THROTTLE_RATES":{
"Luffy":'3/m', # 匿名用户每分钟3次
"LuffyUser":'10/m', # 登录用户没分中10次
}
}
api–>views.py
from django.shortcuts import render,HttpResponse
from django.http import JsonResponse
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework import exceptions
from rest_framework.authentication import BasicAuthentication
from api.utils.permission import SVIPPermission
from api.utils.permission import MyPermission1
from api.utils.throttle import VisitThrottle
from api import models
ORDER_DICT = {
1:{
'name': "媳妇",
'age':18,
'gender':'男',
'content':'...'
},
2:{
'name': "老狗",
'age':19,
'gender':'男',
'content':'...。。'
},
}
def md5(user):
import hashlib
import time
ctime = str(time.time())
m = hashlib.md5(bytes(user,encoding='utf-8'))
m.update(bytes(ctime,encoding='utf-8'))
return m.hexdigest()
class AuthView(APIView):
"""
用于用户登录认证
"""
authentication_classes = [] # 全局设置了,这里设置空列表,表示此处不需要认证
permission_classes = []
throttle_classes = [VisitThrottle,]
def post(self,request,*args,**kwargs):
ret = {'code':1000,'msg':None}
try:
user = request._request.POST.get('username')
pwd = request._request.POST.get('password')
obj = models.UserInfo.objects.filter(username=user,password=pwd).first()
if not obj:
ret['code'] = 1001
ret['msg'] = "用户名或密码错误"
# 为登录用户创建token
token = md5(user)
# 存在就更新,不存在就创建
models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})
ret['token'] = token
except Exception as e:
ret['code'] = 1002
ret['msg'] = '请求异常'
return JsonResponse(ret)
class OrderView(APIView):
"""
订单相关业务(只有SVIP用户有权限)
"""
def get(self,request,*args,**kwargs):
# request.user
# request.auth
self.dispatch
ret = {'code':1000,'msg':None,'data':None}
try:
ret['data'] = ORDER_DICT
except Exception as e:
pass
return JsonResponse(ret)
class UserInfoView(APIView):
"""
订单相关业务(普通用户、VIP)
"""
permission_classes = [MyPermission1, ]
def get(self,request,*args,**kwargs):
return HttpResponse('用户信息')
3.2.3.1 auth 认证
api --> utils --> auth.py 将认证类抽取出来
from rest_framework import exceptions
from api import models
from rest_framework.authentication import BaseAuthentication
class FirstAuthtication(BaseAuthentication):
def authenticate(self,request):
pass
def authenticate_header(self, request):
pass
class Authtication(BaseAuthentication):
def authenticate(self,request):
token = request._request.GET.get('token')
token_obj = models.UserToken.objects.filter(token=token).first()
if not token_obj:
raise exceptions.AuthenticationFailed('用户认证失败')
# 在rest framework内部会将整个两个字段赋值给request,以供后续操作使用
return (token_obj.user, token_obj)
def authenticate_header(self, request):
return 'Basic realm="api"'
3.2.3.2 认证小结
a. 问题:有些API需要用户登录成功之后,才能访问;有些无需登录就能访问。
b. 基本使用认证组件
解决:
1. 创建两张表
2. 用户登录(返回token并保存到数据库)
c. 认证流程原理
- 上面源码
d. 再看一遍源码
1. 局部视图使用&全局使用
2. 匿名是request.user = None
e. 内置认证类
1. 认证类,必须继承:from rest_framework.authentication import BaseAuthentication
2. 其他认证类:BasicAuthentication
梳理:
1. 使用
- 创建类:继承BaseAuthentication; 实现:authenticate方法
- 返回值:
- None,我不管了,下一认证来执行。
- raise exceptions.AuthenticationFailed('用户认证失败') # from rest_framework import exceptions
- (元素1,元素2) # 元素1赋值给request.user; 元素2赋值给request.auth
- 局部使用
from rest_framework.authentication import BaseAuthentication,BasicAuthentication
class UserInfoView(APIView):
"""
订单相关业务
"""
authentication_classes = [BasicAuthentication,]
def get(self,request,*args,**kwargs):
print(request.user)
return HttpResponse('用户信息')
- 全局使用:
REST_FRAMEWORK = {
# 全局使用的认证类
"DEFAULT_AUTHENTICATION_CLASSES:['api.utils.auth.FirstAuthtication','api.utils.auth.Authtication', ],
# "UNAUTHENTICATED_USER":lambda :"匿名用户"
"UNAUTHENTICATED_USER":None, # 匿名,request.user = None
"UNAUTHENTICATED_TOKEN":None,# 匿名,request.auth = None
}
2. 源码流程
- dispatch
- 封装request
- 获取定义的认证类(全局/局部),通过列表生成时创建对象。
- initial
- perform_authentication
request.user(内部循环....)
3.2.4 权限
api --> utils --> permission.py 将权限判断类抽取出来
from rest_framework.permissions import BasePermission
class SVIPPermission(BasePermission): # 继承BasePermission(内置类方法)
message = "必须是SVIP才能访问"
def has_permission(self,request,view):
if request.user.user_type != 3:
return False
return True
class MyPermission1(BasePermission):
def has_permission(self,request,view):
if request.user.user_type == 3:
return False
return True
3.2.5 权限小结
1. 使用
- 类,必须继承:BasePermission,必须实现:has_permission方法
from rest_framework.permissions import BasePermission
class SVIPPermission(BasePermission):
message = "必须是SVIP才能访问"
def has_permission(self,request,view):
if request.user.user_type != 3:
return False
return True
- 返回值:
- True, 有权访问
- False,无权访问
- 局部
class UserInfoView(APIView):
"""
订单相关业务(普通用户、VIP)
"""
permission_classes = [MyPermission1, ]
def get(self,request,*args,**kwargs):
return HttpResponse('用户信息')
- 全局
REST_FRAMEWORK = {
"DEFAULT_PERMISSION_CLASSES":['api.utils.permission.SVIPPermission']
}
2. 源码流程
...
3.2.6 访问频率(节流)
api --> utils --> throttle.py 将访问频率类抽取出来
自己实现:
import time
VISIT_RECORD = {}
class VisitThrottle(BaseThrottle):
"""
60S内只能访问3次
"""
def __init__(self):
self.history = None
def allow_request(self,request,view):
# 1. 获取用户IP
remote_addr = self.get_ident(request)
# 获取当前时间
ctime = time.time()
if remote_addr not in VISIT_RECORD:
VISIT_RECORD[remote_addr] = [ctime,]
return True
history = VISIT_RECORD.get(remote_addr)
self.history = history
# 当前IP对应的列表中的最后一个时间记录<当前时间减60秒,即60S前的时间删除
while history and history[-1] < ctime - 60:
history.pop()
# 判断当前IP对应的列表中个数<3个,则把当前时间插入列表
if len(history) < 3:
history.insert(0,ctime)
return True
# return True # 表示可以继续访问
# return False # 表示访问频率太高,被限制
def wait(self):
# 还需要等多少秒才能访问
ctime = time.time()
return 60 - (ctime - self.history[-1])
使用Django内置类
from rest_framework.throttling import BaseThrottle,SimpleRateThrottle
class VisitThrottle(SimpleRateThrottle):
scope = "Luffy"
def get_cache_key(self, request, view):
return self.get_ident(request)
class UserThrottle(SimpleRateThrottle):
scope = "LuffyUser"
def get_cache_key(self, request, view):
return request.user.username
SimpleRateThrottle 类里面实现的方法(源码):
主要是allow_request()
wait()
方法
class SimpleRateThrottle(BaseThrottle):
"""
A simple cache implementation, that only requires `.get_cache_key()`
to be overridden.
The rate (requests / seconds) is set by a `rate` attribute on the View
class. The attribute is a string of the form 'number_of_requests/period'.
Period should be one of: ('s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day')
Previous request information used for throttling is stored in the cache.
"""
cache = default_cache
timer = time.time
cache_format = 'throttle_%(scope)s_%(ident)s'
scope = None
THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES
def __init__(self):
if not getattr(self, 'rate', None):
self.rate = self.get_rate()
self.num_requests, self.duration = self.parse_rate(self.rate)
def get_cache_key(self, request, view):
"""
Should return a unique cache-key which can be used for throttling.
Must be overridden.
May return `None` if the request should not be throttled.
"""
raise NotImplementedError('.get_cache_key() must be overridden')
def get_rate(self):
"""
Determine the string representation of the allowed request rate.
"""
if not getattr(self, 'scope', None):
msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
self.__class__.__name__)
raise ImproperlyConfigured(msg)
try:
return self.THROTTLE_RATES[self.scope]
except KeyError:
msg = "No default throttle rate set for '%s' scope" % self.scope
raise ImproperlyConfigured(msg)
def parse_rate(self, rate):
"""
Given the request rate string, return a two tuple of:
<allowed number of requests>, <period of time in seconds>
"""
if rate is None:
return (None, None)
num, period = rate.split('/')
num_requests = int(num)
duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
return (num_requests, duration)
def allow_request(self, request, view):
"""
Implement the check to see if the request should be throttled.
On success calls `throttle_success`.
On failure calls `throttle_failure`.
"""
if self.rate is None:
return True
self.key = self.get_cache_key(request, view)
if self.key is None:
return True
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()
if len(self.history) >= self.num_requests:
return self.throttle_failure()
return self.throttle_success()
def throttle_success(self):
"""
Inserts the current request's timestamp along with the key
into the cache.
"""
self.history.insert(0, self.now)
self.cache.set(self.key, self.history, self.duration)
return True
def throttle_failure(self):
"""
Called when a request to the API has failed due to throttling.
"""
return False
def wait(self):
"""
Returns the recommended next request time in seconds.
"""
if self.history:
remaining_duration = self.duration - (self.now - self.history[-1])
else:
remaining_duration = self.duration
available_requests = self.num_requests - len(self.history) + 1
if available_requests <= 0:
return None
return remaining_duration / float(available_requests)
DRF (django rest framework) 补充
DRF,是django第三方组件,能帮助我们快速实现遵循rest ful 规范的接口。
本质是一个Django的组件(app);
帮助我们快速实现遵循rest ful 规范的接口;
- 帮助我们做了csrf的豁免
- 页面渲染(将json放到页面了)
- 序列化(直接对QuerySet对象序列化)
- request.data (拿到用户数据并反序列化)
# 安装
pip install djangorestframework
使用drf开始restful API :
1. 在app中注册drf组件
在settings的app中注册:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework'
]
2. 序列化
直接对QuerySet对象序列化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7cLfX6qA-1610934599975)(C:\Users\zhanghaibo04\AppData\Roaming\Typora\typora-user-images\image-20210113154607228.png)]
【扩展】:视图继承的类:
APIView: 没有提供增删改查功能,全部需要自己写。
ModelViewSet: 内部实现了增删改查,无需自己写。
url.py
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eA50QCb6-1610934599977)(C:\Users\zhanghaibo04\AppData\Roaming\Typora\typora-user-images\image-20210113162942561.png)]
view.py 视图函数实现(也需要 定义上面的实例化类)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9xjfQK2j-1610934599978)(C:\Users\zhanghaibo04\AppData\Roaming\Typora\typora-user-images\image-20210113162825955.png)]
3. drf 版本控制
3.1 局部控制
局部控制,只能某个接口能获取到值
路由:
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api/(?P<version>\w+)/', include('api.urls')), # api/ 后设置版本
]
urlpatterns = [
url(r'^order/$', views.OrderView.as_view()),
]
导入模块及视图函数:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import QueryParameterVersioning,URLPathVersioning # 导入版本模块
class OrderView(APIView):
# 原本 versioning_class=None,此处重写versioning_class
versioning_class = URLPathVersioning
def get(self,request,*args,**kwargs):
print(request.version) # request.version获取版本
return Response('...')
3.2 全局控制
全局控制,多有接口都能能获取到值
在settings.py中配置如下REST_FRAMEWORK
代码:
REST_FRAMEWORK = {
'DEFAULT_VERSIONING_CLASS': "rest_framework.versioning.URLPathVersioning"
}
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api/(?P<version>\w+)/', include('api.urls')),
]
urlpatterns = [
url(r'^order/$', views.OrderView.as_view()),
]
from rest_framework.views import APIView
from rest_framework.response import Response
class OrderView(APIView):
def get(self,request,*args,**kwargs):
print(request.version)
return Response('...')
4. 视图
视图继承类的三种情况:
-
直接继承APIView,适用于非ORM简单操作(即不操作数据库),需要自定义功能时,使用。
from rest_framework.views import APIView class TestView(APIView): def get(self,request,*args,**kwargs): pass
-
继承ListAPIView,DestroyAPIView,UpdateAPIView,RetrieveAPIView,CreateAPIView,项目中只要实现某几个接口时,而不是增删改查。
from api import models from rest_framework import serializers from rest_framework.generics import ListAPIView,DestroyAPIView,UpdateAPIView,RetrieveAPIView,CreateAPIView class CourseSerializers(serializers.ModelSerializer): class Meta: model = models.Course fields = "__all__" class CourseView(ListAPIView,CreateAPIView): # 只实现增加功能 queryset = models.Course.objects.all() serializer_class = CourseSerializers
-
继承ModelViewSet,功能中需要实现对表的增删改查时。
from rest_framework import serializers from rest_framework.viewsets import ModelViewSet class CourseSerializers(serializers.ModelSerializer): class Meta: model = models.Course fields = "__all__" class CourseView(ModelViewSet): queryset = models.Course.objects.all() serializer_class = CourseSerializers
面试题:GenericAPIView的作用?
指定了接口执行的流程。
如果继承了GenericAPIView的类,他们的在内部取数据时,调用 self.get_queryset()它定义在GenericAPIView,它内部返回self.queryset
5. 案例
5.1 zhb项目
创建zhb项目,5 张表
from django.db import models
class Tag(models.Model):
"""
标签表
"""
caption = models.CharField(verbose_name='标签名称',max_length=32)
class Course(models.Model):
"""
课程表,例如:Linux、Python、测试课程
"""
title = models.CharField(verbose_name='课程名称',max_length=32)
class Module(models.Model):
"""
模块表
"""
name = models.CharField(verbose_name='模块名',max_length=32)
course = models.ForeignKey(verbose_name='课程', to='Course', on_delete=models.CASCADE)
class Video(models.Model):
"""
视频表
"""
title = models.CharField(verbose_name='视频名称', max_length=32)
vid = models.CharField(verbose_name='xxx视频ID', max_length=64)
tag = models.ManyToManyField(verbose_name='标签',to='Tag',)
5.2 第一个接口
实现接口返回一个字符串(含有版本)
# settings 中注册
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'api.apps.ApiConfig',
'rest_framework',
]
# settings中配置版本
REST_FRAMEWORK = {
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
'ALLOWED_VERSIONS': ['v1', 'v2'] # 允许版本
}
urlpatterns = [
path('admin/', admin.site.urls),
re_path('api/(?P<version>\w+)/test/', views.TestView.as_view()),
]
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import URLPathVersioning
class TestView(APIView):
def get(self, request, *args, **kwargs):
print(request.version)
return Response('成功')
5.3 第二个接口
访问接口时,返回一个XML文件
<cross-domain-policy>
<allow-access-from domain="*.polyv.net"/>
</cross-domain-policy>
urlpatterns = [
path('admin/', admin.site.urls),
re_path('api/(?P<version>\w+)/test/', views.TestView.as_view()),
re_path('api/(?P<version>\w+)/crossdomain/', views.CrossView.as_view()),
]
class CrossView(APIView):
def get(self, request, *args, **kwargs):
with open('crossdomain.xml') as f:
data = f.read()
return Response(data)
5.4 基于APIView
实现对课程表的:
-
获取所有的数据
-
增加数据
urlpatterns = [ path('admin/', admin.site.urls), re_path('api/(?P<version>\w+)/test/', views.TestView.as_view()), re_path('api/(?P<version>\w+)/cross/', views.CrossView.as_view()), re_path('api/(?P<version>\w+)/course/', views.CourseView.as_view()), ]
# 新建ser.py 用作新建序列化类 from api import models from rest_framework import serializers class CourseSerializer(serializers.ModelSerializer): class Meta: model = models.Course fields = "__all__"
# view函数 from api import models from api.ser import CourseSerializer from rest_framework.views import APIView from rest_framework.response import Response class CourseView(APIView): def get(self, request, *args, **kwargs): result = {'status':1000, 'data':None, 'error':None} queryset = models.Course.objects.all() ser = CourseSerializer(instance=queryset, many=True) result['data'] = ser.data return Response(result) def post(self, request, *args, **kwargs): # 1.获取用户提交的数据 request.data # 2.校验数据的合法性 序列化 # 3.校验通过save # 4.不通过报错 result = {'status':1000, 'data':None, 'error':None} ser = CourseSerializer(data=request.data) if ser.is_valid(): ser.save() return Response(result) result['error'] = ser.errors result['status'] = 2000 return Response(result)
5.5 基于ListAPIView,CreateAPIView
实现5.4功能
urlpatterns = [
path('admin/', admin.site.urls),
re_path('api/(?P<version>\w+)/test/', views.TestView.as_view()),
re_path('api/(?P<version>\w+)/cross/', views.CrossView.as_view()),
re_path('api/(?P<version>\w+)/course/$', views.CourseView.as_view()),
re_path('api/(?P<version>\w+)/course/new/$', views.CourseNewView.as_view()),
]
from api import models
from api.ser import CourseSerializer
from rest_framework.response import Response
from rest_framework.generics import ListAPIView, CreateAPIView
class CourseNewView(ListAPIView,CreateAPIView):
queryset = models.Course.objects.all()
serializer_class = CourseSerializer
5.6 对Course表实现增删改查
urlpatterns = [
path('admin/', admin.site.urls),
re_path('api/(?P<version>\w+)/test/', views.TestView.as_view()),
re_path('api/(?P<version>\w+)/cross/', views.CrossView.as_view()),
re_path('api/(?P<version>\w+)/course/$', views.CourseView.as_view()),
re_path('api/(?P<version>\w+)/course/new/$', views.CourseNewView.as_view()),
re_path('api/(?P<version>\w+)/course/curd/$', views.CourseCurdView.as_view({'get': 'list', 'post': 'create'})),
re_path('api/(?P<version>\w+)/course/curd/(?P<pk>\d+)/$', views.CourseCurdView.as_view({'get':'retrieve','put':'update','delete':'destroy','patch':'partial_update'})),
]
from api import models
from api.ser import CourseSerializer
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
class CourseCurdView(ModelViewSet):
queryset = models.Course.objects.all()
serializer_class = CourseSerializer
5.7 对5.6的功能改变
对Course表实现增删改查,对列表页面的功能不要再去数据库获取,而去文件中获取即可
urlpatterns = [
path('admin/', admin.site.urls),
re_path('api/(?P<version>\w+)/test/', views.TestView.as_view()),
re_path('api/(?P<version>\w+)/cross/', views.CrossView.as_view()),
re_path('api/(?P<version>\w+)/course/$', views.CourseView.as_view()),
re_path('api/(?P<version>\w+)/course/new/$', views.CourseNewView.as_view()),
re_path('api/(?P<version>\w+)/course/curd/$', views.CourseCurdView.as_view({'get': 'list', 'post': 'create'})),
re_path('api/(?P<version>\w+)/course/curd/(?P<pk>\d+)/$', views.CourseCurdView.as_view({'get':'retrieve','put':'update','delete':'destroy','patch':'partial_update'})),
re_path('api/(?P<version>\w+)/course/file/$', views.CourseFileView.as_view({'get': 'list', 'post': 'create'})),
re_path('api/(?P<version>\w+)/course/file/(?P<pk>\d+)/$', views.CourseFileView.as_view({'get':'retrieve','put':'update','delete':'destroy','patch':'partial_update'})),
class CourseFileView(ModelViewSet):
queryset = models.Course.objects.all()
serializer_class = CourseSerializer
# 重写list方法,改变原有的功能
def list(self, request, *args, **kwargs):
with open('crossdomain.xml') as f:
data = f.read()
return Response(data)
5.8 获取列表数据
基于APIView + serializer实现对模块表 实现获取数据 【ForeignKey】
-
显示所有数据
class ModuleSerializer(serializers.ModelSerializer): class Meta: model = models.Module fields = "__all__"
class ModuleView(APIView): def get(self, request, *args, **kwargs): queryset = models.Module.objects.all() ser = ModuleSerializer(instance=queryset, many=True) return Response(ser.data)
-
只显示id和name
只修改自定义序列化类ModuleSerializer即可如下:
class ModuleSerializer(serializers.ModelSerializer): class Meta: model = models.Module # fields = "__all__" # fields = ['id', 'name'] # 显示想要显示的字段 exclude = ['course'] # 排除掉course
-
显示id、name和课程名称
class ModuleSerializer(serializers.ModelSerializer): cname = serializers.CharField(source='course.title') # 定义连表需要显示的字段 class Meta: model = models.Module # fields = "__all__" fields = ['id', 'name', 'cname'] # 显示想要显示的字段
-
显示id、name、课程名称和级别
# 修改表结构,模块表添加级别字段 class Module(models.Model): """ 模块表 """ level_choise = { '1': '初级', '2': '中级', '3': '高级', } level = models.ImageField(verbose_name='级别',choices=level_choise,default=1) name = models.CharField(verbose_name='模块名',max_length=32) course = models.ForeignKey(verbose_name='课程', to='Course', on_delete=models.CASCADE)
class ModuleSerializer(serializers.ModelSerializer): cname = serializers.CharField(source='course.title') # 定义连表需要显示的字段 level_test = serializers.CharField(source='get_level_display') # 定义本表需要显示的字段(以中文显示) class Meta: model = models.Module fields = ['id', 'name', 'level_test', 'cname'] # 显示想要显示的字段
# 显示结果: [ { "id": 1, "name": "数据结构", "level_test": "初级", "cname": "Python" }, { "id": 2, "name": "正则表达式", "level_test": "初级", "cname": "Python" } ]
5.9 单条数据展示、增加
基于CreateAPIView,RetrieveAPIView等系列,实现对module表:单条数据展示、增加
urlpatterns = [
re_path('api/(?P<version>\w+)/module/new/$', views.ModuleNewView.as_view()), # 只对应POST方法
re_path('api/(?P<version>\w+)/module/new/(?P<pk>\d+)/$', views.ModuleNewView.as_view()), # 只对应GET方法
]
class ModuleNewSerializer(serializers.ModelSerializer):
class Meta:
model = models.Module
fields = "__all__"
from rest_framework.generics import ListAPIView, CreateAPIView,RetrieveAPIView
class ModuleNewView(CreateAPIView,RetrieveAPIView,):
queryset = models.Module.objects.all()
serializer_class = ModuleNewSerializer
5.10 对module表实现增删改查
基于ModelViewSet实现对module表实现增删改查:
urlpatterns = [
re_path('api/(?P<version>\w+)/module/set/$', views.ModuleSetView.as_view({'get':'list','post':'create'})),
re_path('api/(?P<version>\w+)/module/set/(?P<pk>\d+)/$', views.ModuleSetView.as_view({'get':'retrieve','delete':'destroy','patch':'partial_update','put':'update'})),
]
class ModuleSetSerializer(serializers.ModelSerializer):
class Meta:
model = models.Module
fields = "__all__"
from rest_framework.viewsets import ModelViewSet
class ModuleSetView(ModelViewSet):
queryset = models.Module.objects.all()
serializer_class = ModuleSetSerializer
# 重写partial_update方法实现部分更新,也可以不重写,因为源码里也是如此实现的
def partial_update(self, request, *args, **kwargs):
pk = kwargs.get('pk')
module_object = models.Module.objects.filter(id=pk).first()
ser = ModuleSetSerializer(instance=module_object,data=request.data,many=False,partial=True)
if ser.is_valid():
ser.save()
return Response('成功')
return Response(ser.errors)
5.11 多对多表操作
-
对Video表做接口,获取视频列表、单条视频信息
urlpatterns = [ re_path('api/(?P<version>\w+)/video/$', views.VideoView.as_view()), re_path('api/(?P<version>\w+)/video/(?P<pk>\d+)/$', views.VideoView.as_view()), ]
class VideoSerializer(serializers.ModelSerializer): class Meta: model = models.Video fields = "__all__"
class VideoView(ListAPIView, CreateAPIView): queryset = models.Video.objects.all() serializer_class = VideoSerializer
-
对Video表做增删改查
- 多对多定制显示操作
# 这样定义可以省略{'get':'list','post':'create'...} from django.urls import path, re_path, include from rest_framework import routers from api import views route = routers.DefaultRouter() route.register(r'video_set', views.VideoSetView) urlpatterns = [ re_path('api/(?P<version>\w+)/',include(route.urls)) ]
# 打印出多对多关联表的内容 class VideoSetSerializer(serializers.ModelSerializer): tag_test = serializers.SerializerMethodField() # 多对多定制显示操作 class Meta: model = models.Video fields = ['id', 'title', 'vid', 'tag', 'tag_test'] # 钩子函数(get_+自定义字段),每次展示钩子函数都会执行一遍, # obj表示每次展示每条数据对象 def get_tag_test(self, obj): # 与之关联的tag表中的所有数据 tag_list = obj.tag.all() return [{'id': row.id, 'caption': row.caption} for row in tag_list]
class VideoSetView(ModelViewSet): queryset = models.Video.objects.all() serializer_class = ser.VideoSetSerializer
6. drf提供了哪些功能
1、免除csrftoken的认证
2、提供了一些视图类:APIView、ListAPIView、ModelViewSet,他的内部帮助我们写了get/post/delete...方法,帮助我们可以快速实现增删改查。
3、渲染器,页面的渲染。
4、序列化(表单验证+序列化)
5、解析器,解析request.body中的数据格式,并将其赋值到request.data中。
6、版本控制
7. drf总结
1、HTTP请求方法有哪些?方法是干什么的
get/post/put/patch/delete
2、用drf做接口开发:数据显示
- 单表:最简单,定义files字段即可
- FK:基于source参数连表操作
- M2M:基于SerializerMethodField + 自定义一个钩子方法
- 注意:choise显示中文可以使用source字段
"get_字段名_display"
3、有三种写接口的方法
- APIView
- ListAPIView
- ModelViewSet
4、局部更新
局部更新应用用patch请求