DRF | 針對指定的接口設置權限

描述

針對同一個 view,對不一樣的接口設置不一樣的權限python

看了 DRF views 源碼:app

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)

經過源碼能夠發現,每次請求進來,都要作認證,權限驗證和限流驗證。若是全部接口都須要權限,這直接在視圖類中直接設置 permission_classes 便可;若是針對業務中部分接口須要權限,其餘不須要權限的場景,這樣一刀切的方式是行不通的,由於進來的請求會被權限打回去,針對部分接口須要權限的場景,可能須要變通一下。ide

1 場景以下:例如我的博客

例若有個分類模型url

class Category(models.Model):
    name = models.CharField('名稱', max_length=30)
    created = models.DateTimeField('生成時間', auto_now_add=True)

    def __str__(self):
        return self.name

若是使用 DRF 的 viewsetrest

class CategoryViewSet(viewsets.ModelViewSet):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer

DRF 會自動生成六個方法,具體就不說了,對於我的博客來講,獲取列表以及詳情是不須要設置權限的,可是對於更新,建立,刪除時須要作權限驗證的,那麼問題來了,我該怎麼作好權限驗證,這裏我假使獲取詳情須要管理員權限,其餘方法都不須要權限驗證code

2 變通方式

方法 1:自定義裝飾器

寫包裝權限的裝飾器orm

from functools import update_wrapper

def wrap_permission(*permissions, validate_permission=True):
    """custom permissions for special route"""
    def decorator(func):
        def wrapper(self, request, *args, **kwargs):
            self.permission_classes = permissions
            if validate_permission:
                self.check_permissions(request)
            return func(self, request, *args, **kwargs)
        return update_wrapper(wrapper, func)
    return decorator

自定義權限類接口

from rest_framework.permissions import IsAdminUser

class IsVbAdminUser(IsAdminUser):
    """
    Allows access only to admin users.
    """
    def has_object_permission(self, request, view, obj):
        """
        Return `True` if permission is granted, `False` otherwise.
        """
        return self.has_permission(request, view)

而後在 viewset 中,例如獲取詳情接口,這樣用,ci

@wrap_permission(IsVbAdminUser)
def retrieve(self, request, *args, **kwargs):

若是直接請求,就會下面錯誤路由

"error_message": "Authentication credentials were not provided."

後面在 DRF 的裝飾器中找到 permission_classes

def permission_classes(permission_classes):
    def decorator(func):
        func.permission_classes = permission_classes
        return func
    return decorator

這個默認的業務還須要定製,根據本身業務須要了

方法2:使用 detail_route 或者 list_route

@detail_route(
    url_path='test',
    permission_classes=(IsVbAdminUser, ),
)
def test(self, request, *args, **kwargs):

這樣耍的緣由,在看了 DRF route 源代碼,發現

method_kwargs = getattr(viewset, methodname).kwargs
initkwargs = route.initkwargs.copy()
initkwargs.update(method_kwargs)

是否是很好玩

對於自定義路由,使用這種方法,仍是蠻方便的。

方法 3:從新定義 initial 方法

重寫 initial 方法,根據方法定義不一樣的權限類

添加 permission_classes_map 類屬性

permission_classes_map 定義接口和權限的映射,用法以下:

permission_classes_map = {
    方法名: 權限類列表
}

此爲特定接口的權限檢測,例如若是視圖中包含 create 方法,同時在又在視圖中設置了全局性的 permission_classes,
可是想爲 create 定義不一樣於全局的權限,因此這裏能夠這樣設置,示例以下:

permission_classes_map = {
    'create': [CustomPermission]
}
permission_classes_map = {}

def initial(self, request, *args, **kwargs):
    """從新定義此方法,添加靈活配置權限映射"""
    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

    if hasattr(handler, '__name__'):
        handler_name = handler.__name__
    elif hasattr(handler, '__func__'):
        handler_name = handler.__func__.__name__
    else:
        handler_name = None

    if handler_name and handler_name in self.permission_classes_map:
        if isinstance(self.permission_classes_map.get(handler_name), (tuple, list)):
            self.permission_classes = self.permission_classes_map.get(handler_name)
    return super(CommonWithSignViewSet, self).initial(request, *args, **kwargs)

方法 4:使用不一樣的視圖

把須要權限驗證的和不須要權限驗證的視圖分開,寫兩個視圖,可是這樣作會冗餘部分代碼

3 小結

我在以往的經歷中,若是是針對默認的路由須要加權限驗證,我會使用方法 1,對於自定義路由,我會使用方法 2,固然也能夠反過來思考,設置全局的權限驗證,若是那個方法不須要權限驗證,使用裝飾器把權限設置爲空便可,隨便怎麼折騰

前兩種方法儘可能不要再視圖屬性中設置 permission_classes,這樣的處理有點奇葩,我的能夠針對 DRF 處理流程,進行包裝,看業務須要。

相關文章
相關標籤/搜索