Django REST API:为某些权限级别

时间:2016-12-28 17:21:05

标签: python django django-rest-framework

如何将某些字段设为只读特定用户权限级别?

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

Serializer定义为:

class FooSerializer(serializers.ModelSerializer):
    ...
    class Meta:
        model = FooModel
        fields = ['foo', 'bar']

如何确保'bar'字段对于USER是只读的并且对ADMIN是可写的?

我会像使用smth一样:

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

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

3 个答案:

答案 0 :(得分:3)

您可以使用视图的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

答案 1 :(得分:1)

尽管Fian的回答似乎确实是最明显的documented方式,但是还有另一种方法可以借鉴其他文档化的代码,并且可以在实例化序列化程序时将参数传递给序列化程序。

第一个难题是dynamically modifying a serializer at the point of instantiation上的文档。该文档没有说明如何从视图集中调用此代码,也没有说明如何在初始化字段后修改字段的只读状态-但这并不难。

第二部分-get_serializer method也被记录了-(在“其他方法”下,距get_serializer_class的页面稍远处),因此应该可以安全地依赖(并且源很简单,希望能减少因修饰而引起的意外副作用的机会)。检查GenericAPIView下的源代码(ModelViewSet-以及所有其他似乎内置的视图集类-继承自定义了get_serializer的GenericAPIView。

将两者放在一起,您可以执行以下操作:

在序列化器文件中(对我来说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

然后,您可以在视图集中执行以下操作:

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)

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

也许值得一提的是,当然可以轻松地将DynamicFieldsModelSerializer修改为执行read_only_exceptions列表并将其用于白名单而非黑名单字段的工作(我倾向于这样做)。我还发现将字段设置为空元组(如果未通过),然后删除对None的检查非常有用,然后将继承的序列化程序上的字段定义设置为“ 全部”。这意味着实例化序列化程序时没有传递的字段不会偶然幸免,而且我也不必将序列化程序调用与继承的序列化程序类定义进行比较,以了解其中包含的内容...例如,在 init < / strong>的DynamicFieldsModelSerializer:

# ....
fields = kwargs.pop('fields', ())
# ...
allowed = set(fields)
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
# ....

NB 如果我只想将两到三个类映射到不同的用户类型,并且/或者我不希望任何特别动态的序列化程序行为,我可能会坚持使用Fian提到的方法。

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

答案 2 :(得分:1)

您可以在序列化程序类中扩展get_fields方法。在您的情况下,它看起来像这样:

class FooSerializer(serializers.ModelSerializer):
    ...
    class Meta:
        model = FooModel
        fields = ["foo", "bar"]
    
    def get_fields(self):
        fields = super().get_fields()  # Python 3 syntax
        request = self.context.get("request", None)
        if request and request.user and request.user.is_superuser is False:
            fields["bar"].read_only = True
        return fields