python Django之 DRF(一)框架介绍、源码分析

一、django rest framework 框架的介绍


Django REST framework 是用于构建 Web API 的强大而灵活的工具包。

1.什么是RESTful规范?

  • REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”。

  • REST从资源的角度类审视整个网络,它将分布在网络中某个节点的资源通过URL进行标识,客户端应用通过URL来获取资源的表征,获得这些表征致使这些应用转变状态。

  • 所有的数据,不过是通过网络获取的还是操作(增删改查)的数据,都是资源,将一切数据视为资源是REST区别与其他架构风格的最本质属性。

  • 对于REST这种面向资源的架构风格,有人提出一种全新的结构理念,即:面向资源架构(ROA:Resource Oriented Architecture)


2.RESTful API的介绍

API的意思在本质上是一个或多个路由组成,通过RESTful规范定义规则,当需从该API中获取数据时,需通过相应路由url中的视图函数的校验且需遵循drf定义的规范。那么,知道这些后,我们设置API时需要注意的问题如下:

  1. 尽量将API部署在专用域名(会存在跨域问题,解决方法jsonp等…

  2. 定义接口一般在域名后添加/api/v(\d+)/(表示更新api的版本)

  3. 路径,视网络上任何东西都是资源,均使用名词表示(可复数)

  4. 在视图函数返回请求中定义规则

    • GET :从服务器取出资源(一项或多项)
    • POST :在服务器新建一个资源
    • PUT :在服务器更新资源(客户端提供改变后的完整资源)
    • PATCH :在服务器更新资源(客户端提供改变的属性)
    • DELETE :从服务器删除资源
  5. 过滤,通过在url上传参的形式传递搜索条件(类似在url后面添加?page=1&type=“list”)获取参数值。

  6. 错误处理,状态码是4xx时,应返回错误信息,error当做key。

    {
        error: "Invalid API key"
    }
    
  7. 返回结果,针对不同操作,服务器向用户返回的结果应该符合以下规范。

    1.GET /collection:返回资源对象的列表(数组)
    2.GET /collection/resource:返回单个资源对象
    3.POST /collection:返回新生成的资源对象
    4.PUT /collection/resource:返回完整的资源对象
    5.PATCH /collection/resource:返回完整的资源对象
    6.DELETE /collection/resource:返回一个空文档
    
  8. 将获取到的参数,添加上该参数全部类似、相同信息的url地址

    {"link": {
      "rel":   "collection https://www.example.com/zoos",
      "href":  "https://api.example.com/zoos",
      "title": "List of zoos",
      "type":  "application/vnd.yourformat+json"
    }}
    
  9. 状态码

    说明
    200 OK - [GET]服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
    201 CREATED - [POST/PUT/PATCH]用户新建或修改数据成功。
    202 Accepted - [*]表示一个请求已经进入后台排队(异步任务)
    204 NO CONTENT - [DELETE]用户删除数据成功。
    400 INVALID REQUEST - [POST/PUT/PATCH]用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
    401 Unauthorized - [*]表示用户没有权限(令牌、用户名、密码错误)。
    403 Forbidden - [*]表示用户得到授权(与401错误相对),但是访问是被禁止的。
    404 NOT FOUND - [*]用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
    406 Not Acceptable - [GET]用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
    422 Unprocesable entity - [POST/PUT/PATCH]当创建一个对象时,发生一个验证错误。
    500 INTERNAL SERVER ERROR - [*]服务器发生错误,用户将无法判断发出的请求是否成功。

摘自:https://www.cnblogs.com/wupeiqi/articles/7805382.html


首先先安装一下drf框架

pip install djangorestframework

明白这些后我们就可以来尝试构造一个api来实现调用(使用上,推荐使用CBV,因为通过继承和封装、重写父类、调用父类更加灵活)


二、drf框架源码解读

要想深刻的理解drf那么阅读源码是必须的,所以接下来我们将通过阅读源码的方式来解释一下drf的一下原理和规范是如何定义的、通过什么定义的。首先我们先创建django项目。

  1. 通过命令python manage.py startapp api创建一个名为api的项目。
  2. 创建数据库(这里就使用sqlite3)
  3. 创建表结构

1.drf框架的使用

api/models.py如下:

from django.db import models


# Create your models here.
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", on_delete=models.CASCADE)
    token = models.CharField(max_length=64)

此时在命令行写入

1.python manage.py makemigrations命令即可完成数据库映射
2.python manage.py migrate命令即可完成数据库映射

此时打开sqlite3数据库添加数据:
在这里插入图片描述

urls.py如下:

from django.conf.urls import url
from api import views


urlpatterns = [
    url(r'^api/v1/auth/$', views.AuthView.as_view()),
]


api/views如下:

from django.shortcuts import render, HttpResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from rest_framework.views import APIView
from django.http import JsonResponse
from rest_framework import exceptions
from api import models


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()

# 这是csrf认证另一种写法
@method_decorator(csrf_exempt, name='dispatch')
class AuthView(APIView):

    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)
            ret['token'] = token

            # 存在就更新,不存在就创建
            models.UserToken.objects.update_or_create(defaults={'token': token}, user=obj)
        except Exception as e:
            ret['code'] = 1002
            ret['msg'] = '请求异常'

        return JsonResponse(ret)


自此我们就成功的引入了django rest_framework框架,此时我们访问http://127.0.0.1:8000/api/v1/auth/显示:

在这里插入图片描述

可以发现视图通过返回相应的请求不同,给出不同请求的参数,每个请求传参方式也有所不同,配合数据库查询、加密token可以使api不会被其他人调用,做到了限制的操作。


2.APIView源码分析

通过上面的调用我们发现,我们的类对象继承了django rest framework中的APIView方法,所以下面我们就来分析一下APIView方法中是怎么样实现的。

因为是继承于View方法,所以也是通过反射的方式调用的,所以,我们可以直接查看一下APIView的dispath函数,看一下是执行了什么。

如下:

class APIView(View):
	    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 = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?

        try:
            self.initial(request, *args, **kwargs)

            # Get the appropriate handler method
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed

            response = handler(request, *args, **kwargs)

        except Exception as exc:
            response = self.handle_exception(exc)

        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response

可以发现和View方法的dispatch类似,不过可以看到APIView在request中封装了一个initialize_request函数


此时我们查看initialize_request函数如下:

    def initialize_request(self, request, *args, **kwargs):
        """
        Returns the initial request object.
        """
        parser_context = self.get_parser_context(request)

        return Request(
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )

可以看到initialize_request函数将request封装了很多的对象,并通过类Request封装了起来(包含了原生的request,丰富了一些功能)。

那么封装的到底是什么呢?我们先点击get_authenticators函数查看一下:

    def get_authenticators(self):
        """
        Instantiates and returns the list of authenticators that this view can use.
        """

        return [auth() for auth in self.authentication_classes]

可以看到get_authenticators函数通过self.authentication_classes遍历auth,通过遍历的个数返回多个函数(如果当前找不到self.authentication_classes类就会去父类找),那么怎么去调用这个函数呢,此时我们去查看一下Request类的构造方法:

class Request:
    """
    Wrapper allowing to enhance a standard `HttpRequest` instance.

    Kwargs:
        - request(HttpRequest). The original request instance.
        - parsers(list/tuple). The parsers to use for parsing the
          request content.
        - authenticators(list/tuple). The authenticators used to try
          authenticating the request's user.
    """

    def __init__(self, request, parsers=None, authenticators=None,
                 negotiator=None, parser_context=None):
        assert isinstance(request, HttpRequest), (
            'The `request` argument must be an instance of '
            '`django.http.HttpRequest`, not `{}.{}`.'
            .format(request.__class__.__module__, request.__class__.__name__)
        )

        self._request = request
        self.parsers = parsers or ()
        self.authenticators = authenticators or ()
        self.negotiator = negotiator or self._default_negotiator()
        self.parser_context = parser_context
        self._data = Empty
        self._files = Empty
        self._full_data = Empty
        self._content_type = Empty
        self._stream = Empty

        if self.parser_context is None:
            self.parser_context = {}
        self.parser_context['request'] = self
        self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET

        force_user = getattr(request, '_force_auth_user', None)
        force_token = getattr(request, '_force_auth_token', None)
        if force_user is not None or force_token is not None:
            forced_auth = ForcedAuthentication(force_user, force_token)
            self.authenticators = (forced_auth,)

此时我们可以看到在Request类中request赋值给了self._request,所以我们如果想在dispatch类中使用原生的request,即可通过request._request方法来获取。(在dispatch函数中request=initialize_request函数,initialize_request函数返回Request方法,且构造函数时_request=request,所以如果想调用authenticators方法,可以在dispatch函数中直接request.authenticators方法即可)。

明白这些后我们继续返回APIView方法的dispatch函数中:

class APIView(View):
	    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 = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?

        try:
            self.initial(request, *args, **kwargs)

            # Get the appropriate handler method
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed

            response = handler(request, *args, **kwargs)

        except Exception as exc:
            response = self.handle_exception(exc)

        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response

继续往下走,到try中,又调用了一个initial函数,且需要带参数的值是已经封装的request(Request类),此时我们点进去查看initial函数:

    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
        self.perform_authentication(request)
        self.check_permissions(request)
        self.check_throttles(request)

参数传递先不管,我们先开一下后面initial函数调用的三个函数中的perform_authentication(身份验证)、permissions(许可验证)、throttles(节流认证、限制访问数量),可以发现这其实是DRF框架中给我们定义的规则,通过相应规范来获取数据,此时我们将一个一个查看其相应源码。

3.perform_authentication源码分析

此时点击perform_authentication函数查看:

    def perform_authentication(self, request):
  
        request.user

此时我们发现perform_authentication函数中包裹了一个request函数的user方法,此时的request函数是继承于Reuqest类的函数了,所以我们可以通过查看Reqeust类中是否有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

可以看出在Request类中确实有该方法,且加入了@property装饰器,使其在调用时,无需传入()。通过方法可以看出,当前user函数中判断了当前是否有_user函数,如果没有就调用self._authenticate函数


此时点击self._authenticate函数查看:

    def _authenticate(self):
        """
        Attempt to authenticate the request using each authentication instance
        in turn.
        """
        for authenticator in self.authenticators:
            try:
                user_auth_tuple = authenticator.authenticate(self)
            except exceptions.APIException:
                self._not_authenticated()
                raise

            if user_auth_tuple is not None:
                self._authenticator = authenticator
                self.user, self.auth = user_auth_tuple
                return

        self._not_authenticated()

可以看出,该方法通过遍历self.authenticators方法来获取认证相关的函数(如果在当前类(DataView)中有就用当前类的,没有就用父类的),之后将获取认证相关的函数调用了authenticate方法,所以能发现,如果我们想在当前类使用身份校验,需在方法中创建authenticate函数

此时点击authenticate函数:

class ForcedAuthentication:

    def __init__(self, force_user, force_token):
        self.force_user = force_user
        self.force_token = force_token

    def authenticate(self, request):
        return (self.force_user, self.force_token)

可以发现就是返回了一个user和一个token参数,所以在_authenticate函数的变量user_auth_tuple是一个元组。而在后面_authenticate函数中又添加了一个判断user_auth_tuple是否为空,如果不为空则返回出去,用我们自定义的值,反之调用了self._not_authenticated()函数
此时我们点击self._not_authenticated()函数查看:

    def _not_authenticated(self):
        self._authenticator = None

        if api_settings.UNAUTHENTICATED_USER:
            self.user = api_settings.UNAUTHENTICATED_USER()
        else:
            self.user = None

        if api_settings.UNAUTHENTICATED_TOKEN:
            self.auth = api_settings.UNAUTHENTICATED_TOKEN()
        else:
            self.auth = None

可以看到self._not_authenticated方法获取了api_settings中的参数进行判断,而参数即为APIView中的全局设置的默认参数。所以可以发现该函数就是判断用户是否传入token和user、传入是否为空,如果有一者为False,则返回默认参数

此时我们再去查看一下self.user和self.auth赋值给了哪个变量。
如下:

	@user.setter
    def user(self, value):
        
        self._user = value
        self._request.user = value
	@auth.setter
    def auth(self, value):
        
        self._auth = value
        self._request.auth = value

可以看到user和auth都是函数,不过是添加了@user.setter、@auth.setter装饰器,且把值赋给了self._request(原生的request),所以我们可以在继承APIView类的方法中通过request.user、request.auth来获取相应的token和user。


理解了这个后,在后面的 permissions(许可验证)、throttles(节流认证、限制访问数量)概念是基本一致的。

- authentication函数使用

通过源码的分析,我们大致已经能清楚内部的原理了,所以现在就可以通过authentication(身份校验)来实现一个订单和身份的校验。

urls.py如下:

from django.conf.urls import url
from api import views


urlpatterns = [
    url(r'^api/v1/auth/$', views.AuthView.as_view()),
    url(r'^api/v1/order/$', views.OrderView.as_view()),
]

api/views.py如下:

from django.shortcuts import render, HttpResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from rest_framework.views import APIView
from django.http import JsonResponse
from rest_framework.authentication import BaseAuthentication
from rest_framework import exceptions
from api import models

# perform_authentication校验类
class Authtication(object):

    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('用户认证失败')
        print(token_obj.user, token_obj)
        return (token_obj.user,token_obj)

    def authenticate_header(self, request):
        pass


class OrderView(APIView):
    """
    订单相关
    """

    authentication_classes = [Authtication, ]

    def get(self, request, *args, **kwargs):
        ret = {'code': 1000, 'msg': None, 'data': None}

        try:
            ret['data'] = {
                'name': request.user.username,
                'context': '成功啦'
            }
        except Exception as e:
            pass

        return JsonResponse(ret)

在Authtication类中的authenticate_header方法表示验证失败时返回的响应头,是必须要加的。

此时访问http://127.0.0.1:8000/api/v1/order/如下:
在这里插入图片描述
可以发现通过传入token值来判断用户身份,且在Authtication类中的authenticate方法校验时通过源码的分析,我们可以知道返回的两个参数会返回给request.user、request.auth,所以我们就可以调用了。

- authentication函数全局中使用

如果当一个类需要被多个类使用的时候如果每次都要写一遍在以后维护会相当不便,所以为了解决该问题我们需要将需要被多次调用的类放在全局中,让每个继承APIView的类都可以使用,在观察源码中,我们能发现,在APIView方法中定义了很多全局配置,api_setting,也就是我们使用时默认的配置,那么如果想通过自己的方式调用,该怎么修改呢?我们可以通过观察APIView方法中api_setting的配置信息源码。
如下:

api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)


def reload_api_settings(*args, **kwargs):
    setting = kwargs['setting']
    if setting == 'REST_FRAMEWORK':
        api_settings.reload()

可以发现,api_setting中是读取REST_FRAMEWORK的配置的,所以我们可以将其写在配置文件中。

为了以后利于维护,我们将全局调用类(这里就封装authentication函数用到的)写在一个文件夹下。

utils/auth.py如下:

from rest_framework import exceptions
from api import models


class Authtication(object):

    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('用户认证失败')
        return (token_obj.user, token_obj)

    def authenticate_header(self, request):
        pass


settings.py如下:
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": ['api.utils.auth.Authtication', ]
}

此时所有继承APIView的类都继承了Authtication方法,如果我们有的类不想继承,可以通过当前类的authentication_classes = []即可。

api/views.py如下:

from django.shortcuts import render, HttpResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from rest_framework.views import APIView
from django.http import JsonResponse
from rest_framework.authentication import BaseAuthentication
from api import models


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()


@method_decorator(csrf_exempt, name='dispatch')
class AuthView(APIView):
    """
    用户登录认证
    """
    authentication_classes = []

    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)
            ret['token'] = token

            # 存在就更新,不存在就创建
            models.UserToken.objects.update_or_create(defaults={'token': token}, user=obj)
        except Exception as e:
            ret['code'] = 1002
            ret['msg'] = '请求异常'

        return JsonResponse(ret)


class OrderView(APIView):
    """
    订单相关
    """

    def get(self, request, *args, **kwargs):
        ret = {'code': 1000, 'msg': None, 'data': None}

        try:
            ret['data'] = {
                'name': request.user.username,
                'context': '成功啦'
            }
        except Exception as e:
            pass

        return JsonResponse(ret)

测试一下:
在这里插入图片描述
可以发现AuthView并没有继承Authtication类的方法。

且在APIView的全局配置中,给没有返回token,user参数的默认值也可以在全局配置中修改。

settings.py如下:

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": ['api.utils.auth.Authtication', ],
    "UNAUTHENTICATED_USER":None,
    "UNAUTHENTICATED_TOKEN":None,
    
}

可以设置为none也同样可以设置其他参数(返回值需要是函数,可以使用lambda表达式:“其他参数”),当没有返回参数时,就会返回你修改的参数了。


4.check_permissions源码分析

本质上perform_authentication的源码和check_permissions源码也是类似的,不过check_permissions方法的作用是用于权限的管理,而perform_authentication方法是做登录认证的,话不多说,直接看check_permissions源码吧。
如下:

    def check_permissions(self, request):
     
        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)
                )

可以看到,check_permissions方法遍历了self.get_permissions函数,获取到permission之后,通过调用其has_permission方法来判断是否存在,存在则略(内部has_permission返回ture,则有权限访问),不存在则调用permission_denied方法(内部has_permission返回false,无权限访问)抛出异常

此时我们点开self.get_permissions函数如下:

    def get_permissions(self):
        return [permission() for permission in self.permission_classes]

可以看到也是通过遍历的方法,将其封装成一个函数,且该函数必须调用has_permission方法,所以我们在自定义的时候必须要加上该方法,和需要的两个request、view传参。

self.permission_denied函数如下:

    def permission_denied(self, request, message=None, code=None):
        if request.authenticators and not request.successful_authenticator:
            raise exceptions.NotAuthenticated()
        raise exceptions.PermissionDenied(detail=message, code=code)

该函数判断了前面的登录验证、当前认证是否存在(不为空),且缺一不可,根据不同情况抛出不同的异常。而这个抛出异常的message即为报错时的错误信息提示,所以可以根据自己的需求自行修改。



- permissions函数使用


api/views.py如下:

from django.shortcuts import render, HttpResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from rest_framework.views import APIView
from django.http import JsonResponse
from api import models



class MyPermission(object):

    def has_permission(self, request, view):
        if request.user.user_type != 3:
            return False
        return True


class OrderView(APIView):
    """
    订单相关
    """
    permission_classes = [MyPermission, ]

    def get(self, request, *args, **kwargs):
        ret = {'code': 1000, 'msg': None, 'data': None}

        try:
            ret['data'] = {
                'name': request.user.username,
                'context': '成功啦'
            }
        except Exception as e:
            pass

        return JsonResponse(ret)

测试一下:
在这里插入图片描述
此时可以发现,我们通过在表结构创建的枚举类对象,判断用户类型不等于3的用户不能访问的权限,通过permissions函数成功的实现了。


- permissions函数全局中使用

为了后期管理起来方便,我们也将其封装在一个文件中,并放入全局配置中(和authentication方法是一样的)。通过APIVIew中的全局配置中,我们得知了permissions函数的全局配置为DEFAULT_PARSER_CLASSES。


settings.py如下:

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": ['api.utils.auth.Authtication', ],
    "UNAUTHENTICATED_USER": None,
    "UNAUTHENTICATED_TOKEN": None,
    "DEFAULT_PERMISSION_CLASSES": ['api.utils.permissions.MyPermission', ],

}

utils/permissions.py如下:

class MyPermission(object):
    message = '必须是SVIP才可以访问哦~'

    def has_permission(self, request, view):
        if request.user.user_type != 3:
            return False
        return True

api/views.py如下:

from django.shortcuts import render, HttpResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from rest_framework.views import APIView
from django.http import JsonResponse
from rest_framework.authentication import BaseAuthentication
from api import models


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()


@method_decorator(csrf_exempt, name='dispatch')
class AuthView(APIView):
    """
    用户登录认证
    """
    authentication_classes = []
    permission_classes = []

    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)
            ret['token'] = token

            # 存在就更新,不存在就创建
            models.UserToken.objects.update_or_create(defaults={'token': token}, user=obj)
        except Exception as e:
            ret['code'] = 1002
            ret['msg'] = '请求异常'

        return JsonResponse(ret)


class OrderView(APIView):
    """
    订单相关
    """


    def get(self, request, *args, **kwargs):
        ret = {'code': 1000, 'msg': None, 'data': None}

        try:
            ret['data'] = {
                'name': request.user.username,
                'context': '成功啦'
            }
        except Exception as e:
            pass

        return JsonResponse(ret)


测试一下:
在这里插入图片描述
可以发现此时permissions函数也在全局中使用了,如果不想使用的话即可在继承APIView方法中使用permission_classes=[]即可。


5.check_throttles源码分析

此时访问check_throttles源码:

    def check_throttles(self, request):
        throttle_durations = []
        for throttle in self.get_throttles():
            if not throttle.allow_request(request, self):
                throttle_durations.append(throttle.wait())

        if throttle_durations:
            durations = [
                duration for duration in throttle_durations
                if duration is not None
            ]

            duration = max(durations, default=None)
            self.throttled(request, duration)

check_throttles的源码和check_permissions源码很类似,也是通过返回的值是Ture或Flase,来决定是否抛出异常或返回结果,不过check_throttles方法是用于限制请求次数的(节流),且在自定义函数时需写上两个函数,duration即为将遍历出来当前时间最大的那个值,赋值回去当剩余限制时间使用

self.get_throttles函数如下:

    def get_throttles(self):
        return [throttle() for throttle in self.throttle_classes]

可以发现check_throttles函数中通过self.get_throttles遍历,和前面类似,将其封装成了一个函数,且该函数必须要有allow_request、wait方法(返回的时间),且必须传入两个参数request、view


- throttles函数使用

api/views.py如下:

from django.shortcuts import render, HttpResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from rest_framework.views import APIView
from django.http import JsonResponse
from api import models
import time


VISIT_RECORD = {}


class VisitThrottle(object):
    """60s内只能访问3次"""

    def __init__(self):
        self.history = None

    def allow_request(self, request, view):
        # 1.获取用户ip
        remote_addr = request.META.get('REMOTE_ADDR')
        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

        # 判断histor是否有值,且最后一个值的时间是否小于60m,小于则可以移除
        while history and history[-1] < ctime - 60:
            history.pop()

        if len(history) < 3:
            VISIT_RECORD[remote_addr].insert(0, ctime)
            return True

    def wait(self):
        ctime = time.time()

        return 60 - (ctime - self.history[-1])


@method_decorator(csrf_exempt, name='dispatch')
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)
            ret['token'] = token
            # 存在就更新,不存在就创建
            models.UserToken.objects.update_or_create(defaults={'token': token}, user=obj)
        except Exception as e:
            ret['code'] = 1002
            ret['msg'] = '请求异常'

        return JsonResponse(ret)


此时访问http://127.0.0.1:8000/api/v1/auth/如下:
在这里插入图片描述
此时可以发现,我们通过remote_addr = request.META.get(‘REMOTE_ADDR’)获取ip地址,然后定义一个字典来存储该ip地址访问时间是否小于当前时间-60秒,如果小于则能访问并增加一个记录,大于三次则不能访问。

- throttles函数全局中使用


通过APIVIew中的全局配置中,我们得知了throttles函数的全局配置为DEFAULT_THROTTLE_CLASSES

utils/throttle.py如下:

import time

VISIT_RECORD = {}


class VisitThrottle(object):
    """60s内只能访问3次"""

    def __init__(self):
        self.history = None

    def allow_request(self, request, view):
        # 1.获取用户ip
        remote_addr = request.META.get('REMOTE_ADDR')
        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

        # 判断histor是否有值,且最后一个值的时间是否小于60m,小于则可以移除
        while history and history[-1] < ctime - 60:
            history.pop()

        if len(history) < 3:
            VISIT_RECORD[remote_addr].insert(0, ctime)
            return True

    def wait(self):
        ctime = time.time()

        return 60 - (ctime - self.history[-1])

settings.py如下:

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": ['api.utils.auth.Authtication', ],
    "UNAUTHENTICATED_USER": None,
    "UNAUTHENTICATED_TOKEN": None,
    "DEFAULT_PERMISSION_CLASSES": ['api.utils.permissions.MyPermission', ],
    "DEFAULT_THROTTLE_CLASSES": ['api.utils.throttle.VisitThrottle'],

}

三、drf框架内置认证类

1.内置BaseAuthentication类

DRF框架非常的灵活,在内部就有多种已经帮我们写好认证的类了,如果我们想要使用即可继承让我们全局的Authtication函数继承于BaseAuthentication类即可。

utils/auto.py如下:

from rest_framework import exceptions
from api import models
from rest_framework.authentication import BaseAuthentication


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('用户认证失败')
        return (token_obj.user, token_obj)

    def authenticate_header(self, request):
        return 'Basic realm="api"'

此时访问http://127.0.0.1:8000/api/v1/order/显示:
在这里插入图片描述
可以发现,当我们没有传入值的时候,BaseAuthentication类会让我们登录,失败的时候也会返回错误信息,成功也可以返回数据,所以可以得出,当我们在写全局类中都必须要继承于BaseAuthentication类的方法。

2.内置BasePermission类

为了让我们的代码符合DRF规则,我们在自定义permissions函数的时候都需要继承BasePermission类,该类也是内部就有多种已经帮我们写好认证的类。

utils/auto.py如下:

from rest_framework.permissions import BasePermission

class MyPermission(BasePermission):
    message = '必须是SVIP才可以访问哦~'

    def has_permission(self, request, view):
        if request.user.user_type != 3:
            return False
        return True

此时访问http://127.0.0.1:8000/api/v1/order/显示:
在这里插入图片描述

3.内置BaseThrottle类

为了遵循规范我们自定义的VisitThrottle也需要继承BaseThrottle类。

import time
from rest_framework.throttling import BaseThrottle

VISIT_RECORD = {}


class VisitThrottle(BaseThrottle):
    """60s内只能访问3次"""

    def __init__(self):
        self.history = None

    def allow_request(self, request, view):
        # 1.获取用户ip
        remote_addr = request.META.get('REMOTE_ADDR')
        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

        # 判断histor是否有值,且最后一个值的时间是否小于60m,小于则可以移除
        while history and history[-1] < ctime - 60:
            history.pop()

        if len(history) < 3:
            VISIT_RECORD[remote_addr].insert(0, ctime)
            return True

    def wait(self):
        ctime = time.time()

        return 60 - (ctime - self.history[-1])

- SimpleRateThrottle类

在DRF框架中,内置给了我们一个SimpleRateThrottle方法,该方法继承于BaseThrottle类,并且封装了很多的功能,所以为了能更加深刻了解,我们就读一下源码。

class SimpleRateThrottle(BaseThrottle):

    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)

我们先从SimpleRateThrottle类的构造方法来看,第一步先是通过反射去查找rate函数,当前也没有rate函数,所以会执行get_rate函数

    def get_rate(self):
        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)

此时get_rate函数又通过反射去查找scope函数,有scope函数(不过需要自己定义,默认none),所以往下走到self.THROTTLE_RATES[self.scope],该方法通过[self.scope为键返回了回去。不过此时我们发现THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES,所以THROTTLE_RATES是一个配置文件,而获取key的规则就是scope的值(键)

返回构造函数,执行self.num_requests, self.duration = self.parse_rate(self.rate),其目的是给self.num_requests, self.duration赋值,此时我们点击parse_rate函数。

    def parse_rate(self, rate):
        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)

此时我们可以发现,该方法是将传入的值以’/‘进行分割,num_requests表示请求多少次,而duration表示限制时间是多少,通过该规则,我们明白了返回参数是一个字典(通过scope当键,获取以’/'分割的数据规则)我们可以通过全局配置,进行使用。

SimpleRateThrottle类还帮我们封装了一个get_cache_key函数(会将保存得值保存在default_cache的缓存中),用于限制访问用的,返回值即为以什么作为唯一标识

utils/throttle.py如下:

import time
from rest_framework.throttling import BaseThrottle, SimpleRateThrottle


class VisitThrottle(SimpleRateThrottle):
    scope = "scope"

    def get_cache_key(self, request, view):
    	# 通过ip地址
        return self.get_ident(request)
	
class UserThrottle(SimpleRateThrottle):
    scope = "user"

    def get_cache_key(self, request, view):
    	# 用户名
        return request.user.username

settings.py如下:

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": ['api.utils.auth.Authtication', ],
    "UNAUTHENTICATED_USER": None,
    "UNAUTHENTICATED_TOKEN": None,
    "DEFAULT_PERMISSION_CLASSES": ['api.utils.permissions.MyPermission', ],
    "DEFAULT_THROTTLE_CLASSES": ['api.utils.throttle.UserThrottle'],
    "DEFAULT_THROTTLE_RATES": {
        "scope": '3/m',
        "user":'5/m',
    },
}

api/views.py如下:

from django.shortcuts import render, HttpResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from rest_framework.views import APIView
from django.http import JsonResponse
from api import models
import time
from api.utils.throttle import VisitThrottle


def md5(user):
    import hashlib
    # 生成随机字符串m
    ctime = str(time.time())
    m = hashlib.md5(bytes(user, encoding='utf-8'))
    m.update(bytes(ctime, encoding='utf-8'))
    return m.hexdigest()


@method_decorator(csrf_exempt, name='dispatch')
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)
            ret['token'] = token
            # 存在就更新,不存在就创建
            models.UserToken.objects.update_or_create(defaults={'token': token}, user=obj)
        except Exception as e:
            ret['code'] = 1002
            ret['msg'] = '请求异常'

        return JsonResponse(ret)


class OrderView(APIView):
    """
    订单相关
    """

    def get(self, request, *args, **kwargs):
        ret = {'code': 1000, 'msg': None, 'data': None}

        try:
            ret['data'] = {
                'name': request.user.username,
                'context': '成功啦'
            }
        except Exception as e:
            pass

        return JsonResponse(ret)

此时通过全局配置使得throttle_classes默认使用以用户作为标识,而未登录状态设置为以id为标识。


测试一下:
在这里插入图片描述

  • 25
    点赞
  • 71
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
好的,下面我会简单介绍如何使用 DjangoDjango REST framework(DRF)来实现 CRUD 操作。CRUD 是指 Create(创建)、Read(读取)、Update(更新)和 Delete(删除),是开发 Web 应用程序时最基本的操作。 首先,你需要安装 DjangoDRF。你可以使用以下命令来安装: ``` pip install django pip install djangorestframework ``` 接下来,创建一个 Django 项目,并在项目中创建一个应用程序。你可以使用以下命令来创建: ``` django-admin startproject projectname cd projectname python manage.py startapp appname ``` 在应用程序的 `models.py` 文件中定义一个模型。例如,我们创建一个简单的 `Book` 模型: ```python from django.db import models class Book(models.Model): title = models.CharField(max_length=200) author = models.CharField(max_length=200) pub_date = models.DateField() price = models.DecimalField(max_digits=5, decimal_places=2) ``` 然后,在应用程序的 `serializers.py` 文件中创建一个序列化器。序列化器用于将模型转换为 JSON 格式,以便在 API 中返回。以下是一个简单的 `BookSerializer`: ```python from rest_framework import serializers from .models import Book class BookSerializer(serializers.ModelSerializer): class Meta: model = Book fields = ['id', 'title', 'author', 'pub_date', 'price'] ``` 接下来,我们需要创建视图。视图定义了 API 的行为。在 `views.py` 文件中,创建一个继承自 DRF 的 `ViewSet` 类的视图。以下是一个简单的 `BookViewSet`: ```python from rest_framework import viewsets from .models import Book from .serializers import BookSerializer class BookViewSet(viewsets.ModelViewSet): queryset = Book.objects.all() serializer_class = BookSerializer ``` 最后,我们需要将视图添加到路由中。在 `urls.py` 文件中,创建一个路由器,并将 `BookViewSet` 添加到路由器中。以下是一个简单的 `urls.py`: ```python from django.urls import path, include from rest_framework.routers import DefaultRouter from .views import BookViewSet router = DefaultRouter() router.register(r'books', BookViewSet) urlpatterns = [ path('', include(router.urls)), ] ``` 现在,你可以运行 Django 项目并使用 API 进行 CRUD 操作了。在命令行中运行 `python manage.py runserver` 启动 Django 服务器。然后,使用浏览器或其他 HTTP 客户端工具(例如 Postman)访问 API。 例如,要创建一个新的书籍,你可以向 `http://localhost:8000/books/` 发送一个 POST 请求,包含书籍的详细信息。要获取所有书籍的列表,你可以向 `http://localhost:8000/books/` 发送一个 GET 请求。要更新或删除特定的书籍,你可以向 `http://localhost:8000/books/<book_id>/` 发送一个 PUT 或 DELETE 请求,其中 `<book_id>` 是书籍的 ID。 这是一个非常简单的示例,但是它可以帮助你了解如何使用 DjangoDRF 来创建一个 CRUD API。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值