Django 从另一个基于 Class 的混合调用基于 Class 的混合

Django Calling Class Based Mixin from Another Class Based Mixin

我的代码有两个 mixin,BasicAuthMixinJWTAuthMixin,如下所述。假设 self.authenticate 方法 returns 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,当包含在任何视图中时,它会自动确定需要根据提供的身份验证 Header 调用哪些 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 但 returns 不允许的方法(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]>

我不确定自己做错了什么。任何人都可以帮我解决问题或任何替代方法来实现这一目标。我想要的是 re-use 已经声明的 mixins:BasicAuthMixn 和 JWTAuthMixin。

这里有一个设计问题,两个 mixin 都是通过拦截 dispatch 方法并调用 super 来实现的。您通过还调用 dispatch 来实现 AllAuthMixin 的方式意味着您需要将它们都放在其 MRO 和 "trick" 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 中,并制作使用它们的单独混合。这样你就可以更好地分离关注点。

类似于:

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 中的每个混音调用 as_view,通过调用 as_view()(request, *args, *kwargs) 您是在强制混音响应请求,但由于它没有get method it returns 405 Method not allowed as described in the docs.

您应该调用 dispatch 并使 AllAuthMixin 从两个子 mixins 继承以正确地将 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'})