Django REST API:将字段设为特定权限级别的只读

0le*_*leg 6 python django django-rest-framework

如何使某些字段对于特定用户权限级别为只读?

有一个 Django REST API 项目。有一个Foo带有 2 个字段的序列化程序 -foobar. 有 2 个权限 -USERADMIN.

序列化器定义为:

class FooSerializer(serializers.ModelSerializer):
    ...
    class Meta:
        model = FooModel
        fields = ['foo', 'bar']
Run Code Online (Sandbox Code Playgroud)

如何确保 'bar' 字段是只读的USER和可写的ADMIN

我会使用 smth 像:

class FooSerializer(serializers.ModelSerializer):
    ...
    class Meta:
        model = FooModel
        fields = ['foo', 'bar']
        read_only_fields = ['bar']
Run Code Online (Sandbox Code Playgroud)

但是如何使其成为有条件的(取决于许可)?

小智 9

您可以使用视图的 get_serializer_class() 方法为不同的用户使用不同的序列化程序:

class ForUserSerializer(serializers.ModelSerializer):
    class Meta:
        model = ExampleModel
        fields = ('id', 'name', 'bar')
        read_only_fields =  ('bar',)

class ForAdminSerializer(serializers.ModelSerializer):
    class Meta:
        model = ExampleModel
        fields = ('id', 'name', 'bar', 'for_admin_only_field')

class ExampleView(viewsets.ModelViewSet):    
    ...
    def get_serializer_class(self):
        if self.request.user.is_admin:
            return ForAdminSerializer
        return ForUserSerializer
Run Code Online (Sandbox Code Playgroud)


use*_*977 6

尽管 Fian 的答案似乎确实是最明显的记录方式,但还有一种替代方案可以利用其他记录的代码,并且可以在实例化序列化器时将参数传递给序列化器。

第一个难题是有关在实例化时动态修改序列化器的文档。该文档没有解释如何从视图集中调用此代码或如何在启动字段后修改字段的只读状态 - 但这并不难。

第二部分 - get_serializer 方法也被记录下来 - (就在“其他方法”下的 get_serializer_class 页面的更下方),所以它应该是安全的依赖(并且来源非常简单,这希望意味着更少的意外机会修改产生的副作用)。检查 GenericAPIView 下的源代码(ModelViewSet - 以及所有其他内置视图集类似乎 - 继承自 GenericAPIView,它定义了 get_serializer。

将两者放在一起你可以做这样的事情:

在序列化程序文件中(对我来说是base_serializers.py):

class DynamicFieldsModelSerializer(serializers.ModelSerializer):
"""
A ModelSerializer that takes an additional `fields` argument that
controls which fields should be displayed.
"""

def __init__(self, *args, **kwargs):
    # Don't pass the 'fields' arg up to the superclass
    fields = kwargs.pop('fields', None)

    # Adding this next line to the documented example
    read_only_fields = kwargs.pop('read_only_fields', None)

    # Instantiate the superclass normally
    super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)

    if fields is not None:
        # Drop any fields that are not specified in the `fields` argument.
        allowed = set(fields)
        existing = set(self.fields)
        for field_name in existing - allowed:
            self.fields.pop(field_name)

    # another bit we're adding to documented example, to take care of readonly fields 
    if read_only_fields is not None:
        for f in read_only_fields:
            try:
                self.fields[f].read_only = True
            exceptKeyError:
                #not in fields anyway
                pass
Run Code Online (Sandbox Code Playgroud)

然后在你的视图集中你可能会做这样的事情:

class MyUserViewSet(viewsets.ModelViewSet):
    # ...permissions and all that stuff

    def get_serializer(self, *args, **kwargs):

        # the next line is taken from the source
        kwargs['context'] = self.get_serializer_context()

        # ... then whatever logic you want for this class e.g:
        if self.request.user.is_staff and self.action == "list":
            rofs = ('field_a', 'field_b')
            fs = ('field_a', 'field_c')
        #  add all your further elses, elifs, drawing on info re the actions, 
        # the user, the instance, anything passed to the method to define your read only fields and fields ...
        #  and finally instantiate the specific class you want (or you could just
        # use get_serializer_class if you've defined it).  
        # Either way the class you're instantiating should inherit from your DynamicFieldsModelSerializer
        kwargs['read_only_fields'] = rofs
        kwargs['fields'] = fs
        return MyUserSerializer(*args, **kwargs)
Run Code Online (Sandbox Code Playgroud)

应该就是这样!使用 MyUserViewSet 现在应该使用您想要的参数实例化您的 UserSerializer - 并假设您的用户序列化器继承自 DynamicFieldsModelSerializer,它应该知道要做什么。

也许值得一提的是,DynamicFieldsModelSerializer 当然可以轻松地适应做一些事情,比如接受 read_only_exceptions 列表并将其用于白名单而不是黑名单字段(我倾向于这样做)。我还发现,如果未通过字段,则将字段设置为空元组,然后删除对 None 的检查...并且我将继承序列化器上的字段定义设置为“ all ”。这意味着实例化序列化器时未传递的字段不会意外存在,而且我也不必将序列化器调用与继承序列化器类定义进行比较来了解包含的内容...例如在DynamicFieldsModelSerializer 的init中:

# ....
fields = kwargs.pop('fields', ())
# ...
allowed = set(fields)
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
# ....
Run Code Online (Sandbox Code Playgroud)

注意如果我只想要两个或三个映射到不同用户类型的类和/或我不想要任何特殊的动态序列化器行为,我很可能会坚持使用 Fian 提到的方法。

然而,就我而言,我想根据发出请求的用户的操作和管理级别来调整字段,这导致了许多又长又烦人的序列化器类名。仅仅为了调整字段和只读字段列表而创建许多序列化器类开始感觉很丑陋。这种方法还意味着字段列表与视图中的相关业务逻辑是分离的。这是否是一件好事可能值得商榷,但是当逻辑变得更加复杂时,我认为这会使代码变得更不易于维护,而不是更易于维护。当然,如果您还想在序列化器启动时执行其他“动态”操作,那么使用我上面概述的方法更有意义。