Django-rest framework

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的输入与输出

  1. 中间件流程图在这里插入图片描述
# 重要的四个方法
    process_request(self, request)
    process_view(self, request, view, *args, **kwargs)
    process_exception(self, request, exception)
    process_response(self, request, response)
# 模板处理的方法
	process_template_response
  1. 使用中间件做过什么?
    - 权限
  • 做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请求
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值