温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Django REST_framework框架 02

发布时间:2020-07-15 18:57:05 来源:网络 阅读:532 作者:jedi911 栏目:编程语言

mixin类编写视图

将上一篇文章中的写法进一步封装简化

urls

from app01 import views
urlpatterns = [
    ......
    url(r'^authors/$', views.AuthorView.as_view(), name="author"),
    url(r'^authors/(?P<pk>\d+)/$', views.AuthorDetailView.as_view(), name="detail_author"),
]

还要写一个ModelSerializer,方法与上一篇博文中相同
views

from rest_framework import mixins
from rest_framework import generics

#GenericAPIView继承了APIView
class AuthorView(mixins.ListModelMixin,    #查看所有
                 mixins.CreateModelMixin,  #添加
                 generics.GenericAPIView):
    queryset = Author.objects.all()
    serializer_class = AuthorModelSerializers

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

class AuthorDetailView(mixins.DestroyModelMixin,   #删除
                       mixins.RetrieveModelMixin,  #查看单条
                       mixins.UpdateModelMixin,    #更新
                       generics.GenericAPIView):
    queryset = Author.objects.all()
    serializer_class = AuthorModelSerializers

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

viewsets.ModelViewSet

这是最终封装版本,关键在于让两条不同的url(带pk值和不带pk值)都汇聚到同一个视图类中

urls.py:

url(r'^authors/$', views.AuthorView.as_view({"get":"list","post":"create"}),name="author"),
url(r'^authors/(?P<pk>\d+)$', views.AuthorView.as_view({
            'get': 'retrieve',
            'put': 'update',
            'patch': 'partial_update',
            'delete': 'destroy'
        }),name="detail_author"),

views.py:

from rest_framework import viewsets

class AuthorView(viewsets.ModelViewSet):
    queryset = Author.objects.all()
    serializer_class = AuthorModelSerializers

源码解析

以下面这个url为例子,我们可以看到这条url最大的变化就是as_view后面传值了,因此要看看是如何处理的

url(r'^authors/$', views.AuthorView.as_view({"get": "list", "post": "create"}), name="author")

我们需要看看此时的as_view是如何用一个视图类处理两条url的,首先寻找这个as_view方法在哪里,事实上它已经不是原来的as_view方法了
AuthorView类-ModelViewSet类-GenericViewSet类-ViewSetMixin类

在ViewSetMixin类中找到as_view方法

def as_view(cls, actions=None, **initkwargs):
    ......
    return csrf_exempt(view)

找到同在ViewSetMixin类中的view:

def as_view(cls, actions=None, **initkwargs):
    ......
    def view(request, *args, **kwargs):
        ......
        for method, action in actions.items(): #循环actions{"get": "list", "post": "create"}
            handler = getattr(self, action)    #handler = self.list或handler = self.create
            setattr(self, method, handler)     #self.get = self.list或self.post = self.create
            ......
        return self.dispatch(request, *args, **kwargs)

Django启动后的url就等同于下面的情况,等待用户访问

url(r'^authors/$', ViewSetMixin.view({"get": "list", "post": "create"}), name="author")


用户访问开始后:
在views.APIView中找到self.dispatch:

class APIView(View):
    def dispatch(self, request, *args, **kwargs):
        try:
            ......
            # Get the appropriate handler method
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(), #此处的request.method.lower()是字符串,get或post
                                  self.http_method_not_allowed)
                #因为上面已经通过反射绑定self.get = self.list或self.post = self.create,
                #因此这里:
                #handler = self.list或self.create
            else:
                handler = self.http_method_not_allowed

            response = handler(request, *args, **kwargs) #这里去找self.list或self.create,将执行的结果返回给response

        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response #将ListModelMixin处理后的结果返回给请求者

self.list或self.create在ModelViewSet类的父类mixins.ListModelMixin或mixins.CreateModelMixin中

class ModelViewSet(mixins.CreateModelMixin,
                   mixins.RetrieveModelMixin,
                   mixins.UpdateModelMixin,
                   mixins.DestroyModelMixin,
                   mixins.ListModelMixin,
                   GenericViewSet):
    """
    A viewset that provides default `create()`, `retrieve()`, `update()`,
    `partial_update()`, `destroy()` and `list()` actions.
    """
    pass

ListModelMixin类将数据处理并序列化后返回给APIView下的dispatch

class ListModelMixin(object):
    """
    List a queryset.
    """
    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

思考:viewsets.ModelViewSet通过覆盖APIView中同名的as_view来实现了新功能,如果有需求的话我们也可以通过覆盖同名方法来实现新的功能,例如我们可以自己写一个list方法来实现不同的需求

认证组件

#认证组件
self.perform_authentication(request)
#权限组件
self.check_permissions(request)
#频率组件
self.check_throttles(request)

局部视图认证

在app01.service.auth.py:

from rest_framework import exceptions
from rest_framework.authentication import BaseAuthentication

class TokenAuth(BaseAuthentication):
    def authenticate(self, request):
        token = request.GET.get("token")
        token_obj = Token.objects.filter(token=token).first()
        if not token_obj: #认证失败抛错,被源码中的try捕获
            raise exceptions.AuthenticationFailed("验证失败!")
        return token_obj.user, token_obj.token #需要返回一个元组

在views.py:

def get_random_str(user):
    import hashlib, time
    ctime = str(time.time())

    md5 = hashlib.md5(bytes(user, encoding="utf8"))
    md5.update(bytes(ctime, encoding="utf8"))

    return md5.hexdigest()

from django.http import JsonResponse

class loginView(APIView):
    authentication_classes = [TokenAuth]

    def post(self, request):
        res = {"code": 1000, "msg": None}

        user = request.data.get("user")
        pwd = request.data.get("pwd")

        user_obj = User.objects.filter(name=user, pwd=pwd).first()
        if not user_obj:
            res["code"] = 1001
            res["msg"] = "用户名或密码错误"

        else:
            token = get_random_str(user)
            Token.objects.update_or_create(user=user_obj, defaults={"token": token})
            res["token"] = token

        return JsonResponse(res, json_dumps_params={"ensure_ascii": False})

源码解析

我们知道在APIView类中可以找到as_view,而此时的as_view又指向了父类View中的as_view,此时父类as_view又会return dispatch,因此我们在APIView类中找到dispatch方法,从这里开始看源码的执行过程。

class APIView(View):
    def dispatch(self, request, *args, **kwargs):
        self.initial(request, *args, **kwargs) #这一步就是在处理认证、权限、频率

class APIView(View):        
    def initial(self, request, *args, **kwargs):
        self.perform_authentication(request) #认证组件
        self.check_permissions(request)      #权限组件
        self.check_throttles(request)        #访问频率组件

class APIView(View):
    def perform_authentication(self, request):
        request.user

这个request是Request类的实例化对象,因此我们要去Request下面去找user方法

class Request(object):
    @property
    def user(self):

        if not hasattr(self, '_user'):
            with wrap_attributeerrors():
                self._authenticate() #调用user过程其实就是在执行这个方法
        return self._user

查看self._authenticate

class Request(object):
    def _authenticate(self):
        for authenticator in self.authenticators: #循环包含着一个个认证类实例的列表,此时就是一个[TokenAuth(),]
            try:
                user_auth_tuple = authenticator.authenticate(self) #将视图中的authenticate返回结果赋值给user_auth_tuple,此时传进去的self是Request类的实例化对象
            except exceptions.APIException: #验证失败抛错
                self._not_authenticated()
                raise

            if user_auth_tuple is not None: #如果不为空
                self._authenticator = authenticator
                self.user, self.auth = user_auth_tuple #user_auth_tuple是个元祖,分成了两个变量,这两个变量可以为下面的权限组件所利用
                return #认证成功后返回

self.authenticators是什么?
往上走,发现构建request时传进来的参数

class APIView(View):
    def initialize_request(self, request, *args, **kwargs):
        ......

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

点进去看看,发现就是self.authentication_classes循环的结果

    def get_authenticators(self):
        """
        Instantiates and returns the list of authenticators that this view can use.
        """
        return [auth() for auth in self.authentication_classes] #列表解析式,循环的是我们自己在视图中写的一个个认证类

因此可见,self.authenticators就是包含着一个个认证类实例对象的列表

authenticator.authenticate(self)是什么意思?
我们再回到_authenticate方法中看看这句话

authenticator.authenticate(self)

实例化对象调自己的方法是不需要传self的,因此这是个形参,我要知道这个self是谁

那么这个self是谁?
要往上一级一级找,上一级是_authenticate(self),谁调用的?
找到user(self),谁调用的user(self)?

class APIView(View):
    def perform_authentication(self, request):
        request.user

request.user调的user,因此self就是这个新构建的request,这个request是Request类的实例化对象

GET访问时加上数据库中已有的一个token就能通过验证
http://127.0.0.1:8000/books/?token=1a54a64ee1111738c5d8b7b5487e801b

全局视图认证组件

如果我们自己不设authentication_classes,那么就会去父类APIView中找,里面有这么一段代码

authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES

api_settings是APISettings类的一个实例化对象

api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)

api_settings.DEFAULT_AUTHENTICATION_CLASSES会去找settings.py中的REST_FRAMEWORK

因此我们自己在settings.py设置这个REST_FRAMEWORK就可以

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.TokenAuth"]
}

["app01.utils.TokenAuth"]这个值是具体路径,也可以是元祖

如果某个视图(比如Login)不希望它经过全局认证,那么可以在视图类中添加一个
authentication_classes = []即可

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI