【问题标题】:Django Calling Class Based Mixin from Another Class Based MixinDjango 从另一个基于类的 Mixin 调用基于类的 Mixin
【发布时间】:2017-10-06 07:30:21
【问题描述】:

我的代码有两个 mixin,BasicAuthMixinJWTAuthMixin,如下所述。只需假设 self.authenticate 方法返回 True 并且不会引发任何异常:

from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import View

class BasicAuthMixin(View):

    """
    Add this mixin to the views where Basic Auth is required.
    """
    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        try:
            self.authenticate(request)
        except:
            return JsonResponse({'status': 403, 'message': 'Forbidden'}, status=403, content_type='application/json')
        return super(BasicAuthMixin, self).dispatch(request, *args, **kwargs)

class JWTAuthMixin(View):
    """
    Add this mixin to the views where JWT based authentication is required.
    """
    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        try:
            self.authenticate(request)
        except:
            return JsonResponse({'status': 403, 'message': 'Forbidden'}, status=403, content_type='application/json')
        return super(JWTAuthMixin, self).dispatch(request, *args, **kwargs)

根据需要的身份验证在视图中使用这些 mixin。

实际问题从这里开始:我正在尝试创建另一个 mixin AllAuthMixin,当包含在任何视图中时,它将自动确定需要根据哪些 mixin 调用提供的身份验证标头:

class AllAuthMixin(View):

    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        auth = request.META.get('HTTP_AUTHORIZATION') or ''
        if auth.startswith('Bearer'):
            return JWTAuthMixin.as_view()(request, *args, **kwargs)
        elif auth.startswith('Basic'):
            return BasicAuthMixin.as_view()(request, *args, **kwargs)
        raise Exception('Unauthorized Access to Saurav APIs', 403)

一旦我在任何视图中包含 AllAuthMixin/test 它实际上会调用适当的 Mixins 但返回 Method Not Allowed (GET): /test

我调试并发现如果我使用基本身份验证,Method Not Allowed 错误消息来自以下行:

return super(BasicAuthMixin, self).dispatch(request, *args, **kwargs)

以下说明了一个非常简单的示例,可以使用基本身份验证调用我的视图:

>>> import requests
>>> requests.get('http://127.0.0.1:8000/test', auth=('UserName', 'Password'))
<Response [405]>

我不确定我在这里做错了什么。谁能帮我解决这个问题或任何替代方法来实现这一目标。我想要的是重用已经声明的 mixins:BasicAuthMixn 和 JWTAuthMixin。

【问题讨论】:

    标签: django mixins django-class-based-views


    【解决方案1】:

    这里有一个设计问题,两个 mixin 都是拦截dispatch 方法并调用super。您通过调用dispatch 来实现AllAuthMixin 的方式意味着您需要在其MRO 和“技巧”super 中同时使用它们来选择合适的方法,这不是一个好主意。

    实现AllAuthMixin 的另一种方法是不调用dispatch,而是实例化并调用authenticate

    class AllAuthMixin(View):
        @method_decorator(csrf_exempt)
        def dispatch(self, request, *args, **kwargs):
            auth = request.META.get('HTTP_AUTHORIZATION') or ''
            try:
                if auth.startswith('Bearer'):
                    JWTAuthMixin().authenticate(request)  # raises on failed auth
                elif auth.startswith('Basic'):
                    BasicAuthMixin().authenticate(request)
            except:
                raise Exception('Unauthorized Access to Saurav APIs', 403)
    
            return super(AllAuthMixin, self).dispatch(request, *args, **kwargs)
    

    重用代码的更好方法是将身份验证分离到其自己的类中,并制作使用它们的单独混入。这样您就可以更好地分离关注点。

    类似:

    class BasicAuth(object):
        def authenticate(self, request):
            # raise if not authed
            print("Basic auth")
    
    class JWTAuth(object):
        def authenticate(self, request):
            # raise if not authed
            print("JWT auth")
    
    class AuthMixin(View):
        def authenticate(self, request):
            raise NotImplementedError('Implement in subclass')
    
        @method_decorator(csrf_exempt)
        def dispatch(self, request, *args, **kwargs):
            try:
                self.authenticate(request)
            except:
                return JsonResponse({'status': 403, 'message': 'Forbidden'}, status=403)
    
            return super(AuthMixin, self).dispatch(request, *args, **kwargs)
    
    class BasicAuthMixin(BasicAuth, AuthMixin):
        pass
    
    class JWTAuthMixin(JWTAuth, AuthMixin):
        pass
    
    class AllAuthMixin(AuthMixin):
        def authenticate(self, request):
            auth = request.META.get('HTTP_AUTHORIZATION') or ''
            try:
                if auth.startswith('Bearer'):
                    return JWTAuth().authenticate(request)
                elif auth.startswith('Basic'):
                    return BasicAuth().authenticate(request)
            except:
                return JsonResponse({'status': 403, 'message': 'Other'}, status=403)
    
    class SomeView(AllAuthMixin, View):
        def get(self, request):
            return JsonResponse({'status': 200, 'message': 'OK'})
    

    -- 原答案 --

    您正在为AllAuthMixin 中的每个mixin 调用as_view,通过调用as_view()(request, *args, *kwargs) 您正在强制mixin 响应请求,但由于它没有get 方法,它返回405 方法不允许如in the docs 所述。

    您应该调用dispatch 并让AllAuthMixin 从两个子mixin 继承以正确地将self 传递给dispatch。像这样:

    class AllAuthMixin(JWTAuthMixin, BasicAuthMixin):
        @method_decorator(csrf_exempt)
        def dispatch(self, request, *args, **kwargs):
            auth = request.META.get('HTTP_AUTHORIZATION') or ''
            if auth.startswith('Bearer'):
                return JWTAuthMixin.dispatch(self, request, *args, **kwargs)
            elif auth.startswith('Basic'):
                return BasicAuthMixin.dispatch(self, request, *args, **kwargs)
            raise Exception('Unauthorized Access to Saurav APIs', 403)
    
    class SomeView(AllAuthMixin, View):
        def get(self, request):
            return JsonResponse({'status': 200, 'message': 'OK'})
    

    【讨论】:

    • dispatch 每个 mixins 的方法都做了很多我在这里没有提到的事情;像验证、引发异常、使用正确的响应代码返回适当的 json 响应。不调用调度方法导致我在 AllAuthMixin 中编写重复代码。但这很好! 你最近的建议奏效了,至少我没有打扰现有的代码。非常感谢您宝贵的时间。非常感谢:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-02-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多