Django Rest Framework: Disable field update after

2019-01-21 10:30发布

I'm trying to make my User model RESTful via Django Rest Framework API calls, so that I can create users as well as update their profiles.

However, as I go through a particular verification process with my users, I do not want the users to have the ability to update the username after their account is created. I attempted to use read_only_fields, but that seemed to disable that field in POST operations, so I was unable to specify a username when creating the user object.

How can I go about implementing this? Relevant code for the API as it exists now is below.

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()]

Thanks!

11条回答
再贱就再见
2楼-- · 2019-01-21 10:48
class UserUpdateSerializer(UserSerializer):
    class Meta(UserSerializer.Meta):
        fields = ('username', 'email')

class UserViewSet(viewsets.ModelViewSet):
    def get_serializer_class(self):
        return UserUpdateSerializer if self.action == 'update' else super().get_serializer_class()

djangorestframework==3.8.2

查看更多
兄弟一词,经得起流年.
3楼-- · 2019-01-21 10:49

More universal way to "Disable field update after object is created" - adjust read_only_fields per View.action

1) add method to Serializer (better to use your own base 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) Add to Meta of serializer dict named actions_readonly_fields

class Meta:
    model = YourModel
    fields = '__all__'
    actions_readonly_fields = {
        ('update', 'partial_update'): ('client', )
    }

In the example above client field will become read-only for actions: 'update', 'partial_update' (ie for PUT, PATCH methods)

查看更多
Deceive 欺骗
4楼-- · 2019-01-21 10:52

I used this approach:

def get_serializer_class(self):
    if getattr(self, 'object', None) is None:
        return super(UserViewSet, self).get_serializer_class()
    else:
        return SerializerWithoutUsernameField
查看更多
疯言疯语
5楼-- · 2019-01-21 10:57

Another solution (apart from creating a separate serializer) would be to pop the username from attrs in the restore_object method if the instance is set (which means it's a PATCH / PUT method):

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楼-- · 2019-01-21 10:59

Another option (DRF3 only)

class MySerializer(serializers.ModelSerializer):
    ...
    def get_extra_kwargs(self):
        extra_kwargs = super(MySerializer, self).get_extra_kwargs()
        action = self.context['view'].action

        if action in ['create']:
            kwargs = extra_kwargs.get('ro_oncreate_field', {})
            kwargs['read_only'] = True
            extra_kwargs['ro_oncreate_field'] = kwargs

        elif action in ['update', 'partial_update']:
            kwargs = extra_kwargs.get('ro_onupdate_field', {})
            kwargs['read_only'] = True
            extra_kwargs['ro_onupdate_field'] = kwargs

        return extra_kwargs
查看更多
男人必须洒脱
7楼-- · 2019-01-21 11:00

It seems that you need different serializers for POST and PUT methods. In the serializer for PUT method you are able to just except the username field (or set the username field as read only).

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()]

Check this question django-rest-framework: independent GET and PUT in same URL but different generics view

查看更多
登录 后发表回答