Django从另一个基于类的Mixin调用基于类的Mixin

时间:2017-10-06 07:30:21

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

我的代码有两个mixins, BasicAuthMixin JWTAuthMixin ,如下所述。假设 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)

基于需要身份验证的视图中使用了这些mixins。

实际问题从这里开始:我正在尝试创建另一个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)

一旦我在 / test 的任何视图中包含 AllAuthMixin ,它实际上会调用相应的Mixins但返回方法不允许(GET):/ test < /强>

如果我使用基本身份验证,我调试并发现方法不允许错误消息来自以下行:

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。

1 个答案:

答案 0 :(得分:2)

这里存在一个设计问题,两个mixin都实现了拦截dispatch方法并调用super。通过调用AllAuthMixin来实现dispatch的方式意味着您需要在MRO和&#34;技巧&#34; super选择一个不合适的合适的。{/ p>

实施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)

重用代码的一种更好方法是将身份验证分离到自己的类中,并制作使用它们的各个mixin。通过这种方式,您可以更好地分离关注点。

类似的东西:

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'})

- 原始回答 -

您在as_view中为每个mixin致电AllAuthMixin,致电as_view()(request, *args, *kwargs),您强迫mixin回复该请求,但由于它没有&#39 ; t有一个get方法返回405方法不允许如in the docs所述。

您应该致电dispatch并同时让AllAuthMixin继承两个子混音,以便将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'})