Django Rest Framework:在创建对象后禁用字段更新

时间:2014-03-02 06:19:47

标签: django rest django-rest-framework

我正在尝试通过Django Rest Framework API调用使我的用户模型RESTful,以便我可以创建用户以及更新他们的个人资料。

但是,当我与用户进行特定的验证过程时,我不希望用户在创建帐户后能够更新用户名。我试图使用read_only_fields,但这似乎在POST操作中禁用了该字段,因此在创建用户对象时我无法指定用户名。

我该如何实施?现在存在的API的相关代码如下所示。

class UserSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = User
        fields = ('url', 'username', 'password', 'email')
        write_only_fields = ('password',)

    def restore_object(self, attrs, instance=None):
        user = super(UserSerializer, self).restore_object(attrs, instance)
        user.set_password(attrs['password'])
        return user


class UserViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows users to be viewed or edited.
    """
    serializer_class = UserSerializer
    model = User

    def get_permissions(self):
        if self.request.method == 'DELETE':
            return [IsAdminUser()]
        elif self.request.method == 'POST':
            return [AllowAny()]
        else:
            return [IsStaffOrTargetUser()]

谢谢!

12 个答案:

答案 0 :(得分:52)

对于POST和PUT方法,您似乎需要不同的序列化程序。在PUT方法的序列化器中,您只能使用用户名字段(或将用户名字段设置为只读)。

class UserViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows users to be viewed or edited.
    """
    serializer_class = UserSerializer
    model = User

    def get_serializer_class(self):
        serializer_class = self.serializer_class

        if self.request.method == 'PUT':
            serializer_class = SerializerWithoutUsernameField

        return serializer_class

    def get_permissions(self):
        if self.request.method == 'DELETE':
            return [IsAdminUser()]
        elif self.request.method == 'POST':
            return [AllowAny()]
        else:
            return [IsStaffOrTargetUser()]

检查此问题django-rest-framework: independent GET and PUT in same URL but different generics view

答案 1 :(得分:18)

另一种选择(仅限DRF3)

import owl

答案 2 :(得分:4)

<强>更新

原来Rest Framework已经配备了这个功能。拥有&#34;仅限创建&#34;的正确方法字段是使用CreateOnlyDefault()选项。

我想唯一要说的就是阅读文档! http://www.django-rest-framework.org/api-guide/validators/#createonlydefault

旧答案:

看起来我参加派对已经很晚了,但无论如何这里是我的两分钱。

对我来说,仅仅因为想要阻止字段更新而拥有两个不同的序列化器是没有意义的。我有完全相同的问题,我使用的方法是在Serializer类中实现我自己的validate方法。就我而言,我不想更新的字段称为owner。以下是相关代码:

class BusinessSerializer(serializers.ModelSerializer):

    class Meta:
        model = Business
        pass

    def validate(self, data):
        instance = self.instance

        # this means it's an update
        # see also: http://www.django-rest-framework.org/api-guide/serializers/#accessing-the-initial-data-and-instance
        if instance is not None: 
            originalOwner = instance.owner

            # if 'dataOwner' is not None it means they're trying to update the owner field
            dataOwner = data.get('owner') 
            if dataOwner is not None and (originalOwner != dataOwner):
                raise ValidationError('Cannot update owner')
        return data
    pass
pass

这是一个验证它的单元测试:

def test_owner_cant_be_updated(self):
    harry = User.objects.get(username='harry')
    jack = User.objects.get(username='jack')

    # create object
    serializer = BusinessSerializer(data={'name': 'My Company', 'owner': harry.id})
    self.assertTrue(serializer.is_valid())
    serializer.save()

    # retrieve object
    business = Business.objects.get(name='My Company')
    self.assertIsNotNone(business)

    # update object
    serializer = BusinessSerializer(business, data={'owner': jack.id}, partial=True)

    # this will be False! owners cannot be updated!
    self.assertFalse(serializer.is_valid())
    pass

我举起ValidationError,因为我不想隐瞒有人试图执行无效操作的事实。如果您不想这样做,并且希望在不更新字段的情况下完成操作,请执行以下操作:

删除该行:

raise ValidationError('Cannot update owner')

并将其替换为:

data.update({'owner': originalOwner})

希望这有帮助!

答案 3 :(得分:3)

我的方法是在使用泛型视图类时修改perform_update方法。我在执行更新时删除了该字段。

class UpdateView(generics.UpdateAPIView):
    ...
    def perform_update(self, serializer):
        #remove some field
        rem_field = serializer.validated_data.pop('some_field', None)
        serializer.save()

答案 4 :(得分:2)

我使用了这种方法:

def get_serializer_class(self):
    if getattr(self, 'object', None) is None:
        return super(UserViewSet, self).get_serializer_class()
    else:
        return SerializerWithoutUsernameField

答案 5 :(得分:1)

另一个解决方案(除了创建单独的序列化程序之外)将在restore_object方法中从attrs弹出用户名(如果设置了实例)(这意味着它是PATCH / PUT方法):

def restore_object(self, attrs, instance=None):
    if instance is not None:
        attrs.pop('username', None)
    user = super(UserSerializer, self).restore_object(attrs, instance)
    user.set_password(attrs['password'])
    return user

答案 6 :(得分:1)

如果您不想创建其他序列化程序,可以尝试在get_serializer_class()内自定义MyViewSet。这对我来说对简单项目很有用。

# Your clean serializer
class MySerializer(serializers.ModelSerializer):
    class Meta:
        model = MyModel
        fields = '__all__'

# Your hardworking viewset
class MyViewSet(MyParentViewSet):
    serializer_class = MySerializer
    model = MyModel

    def get_serializer_class(self):
        serializer_class = self.serializer_class
        if self.request.method in ['PUT', 'PATCH']:
            # setting `exclude` while having `fields` raises an error
            # so set `read_only_fields` if request is PUT/PATCH
            setattr(serializer_class.Meta, 'read_only_fields', ('non_updatable_field',))
            # set serializer_class here instead if you have another serializer for finer control
        return serializer_class
  

setattr(object, name, value)

     

这是getattr()的对应物。该   arguments是一个对象,一个字符串和一个任意值。字符串   可以命名现有属性或新属性。功能   如果对象允许,则将值赋给属性。对于   例如,setattr(x,&#39; foobar&#39;,123)相当于x.foobar = 123。

答案 7 :(得分:1)

This帖子提到了实现这一目标的四种不同方式。

这是我认为最干净的方式:[不得编辑集合]

class DocumentSerializer(serializers.ModelSerializer):

    def update(self, instance, validated_data):
        if 'collection' in validated_data:
            raise serializers.ValidationError({
                'collection': 'You must not change this field.',
            })

        return super().update(instance, validated_data)

答案 8 :(得分:1)

另一种方法是添加验证方法,但如果实例已存在且值已更改,则抛出验证错误:

def validate_foo(self, value):                                     
    if self.instance and value != self.instance.foo:
        raise serializers.ValidationError("foo is immutable once set.")
    return value         

就我而言,我希望永远不会更新外键:

def validate_foo_id(self, value):                                     
    if self.instance and value.id != self.instance.foo_id:            
        raise serializers.ValidationError("foo_id is immutable once set.")
    return value         

另请参阅:Level-field validation in django rest framework 3.1 - access to the old value

答案 9 :(得分:1)

apache/conf/catalina/localhost/sampleProject.xml

djangorestframework == 3.8.2

答案 10 :(得分:0)

更多通用方式到“在创建对象后禁用字段更新” - 根据 View.action

调整 read_only_fields

1)向Serializer添加方法(最好使用你自己的基础cls)

def get_extra_kwargs(self):
    extra_kwargs = super(BasePerTeamSerializer, self).get_extra_kwargs()
    action = self.context['view'].action
    actions_readonly_fields = getattr(self.Meta, 'actions_readonly_fields', None)
    if actions_readonly_fields:
        for actions, fields in actions_readonly_fields.items():
            if action in actions:
                for field in fields:
                    if extra_kwargs.get(field):
                        extra_kwargs[field]['read_only'] = True
                    else:
                        extra_kwargs[field] = {'read_only': True}
    return extra_kwargs

2)添加名为 actions_readonly_fields

的序列化程序字典的Meta
class Meta:
    model = YourModel
    fields = '__all__'
    actions_readonly_fields = {
        ('update', 'partial_update'): ('client', )
    }

在上面的示例中,client字段对于操作将变为只读:'update','partial_update'(即对于PUT,PATCH方法)

答案 11 :(得分:0)

我建议您同时查看Django pgtrigger

这允许您安装触发器进行验证。我开始使用它,并对其简单性感到非常满意:

以下是他们的示例之一,可防止已发布的帖子被更新:

import pgtrigger
from django.db import models


@pgtrigger.register(
    pgtrigger.Protect(
        operation=pgtrigger.Update,
        condition=pgtrigger.Q(old__status='published')
    )
)
class Post(models.Model):
    status = models.CharField(default='unpublished')
    content = models.TextField()

此方法的优点是它还可以保护您免受绕过.update()的{​​{1}}个呼叫