developer tip

Django Rest Framework : 개체 생성 후 필드 업데이트 비활성화

optionbox 2020. 12. 25. 09:38
반응형

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

감사!


POST 및 PUT 메서드에 대해 다른 serializer가 필요한 것 같습니다. PUT 메서드 용 serializer에서 사용자 이름 필드를 제외하거나 사용자 이름 필드를 읽기 전용으로 설정할 수 있습니다.

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 : 동일한 URL에서 독립적 인 GET 및 PUT이지만 다른 일반보기


다른 옵션 (DRF3 만 해당)

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

내 접근 방식은 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()

최신 정보:

Rest Framework는 이미이 기능을 갖추고 있습니다. "만들기 전용"필드를 갖는 올바른 방법은 CreateOnlyDefault()옵션 을 사용하는 것입니다.

남은 말은 Read the Docs !!! http://www.django-rest-framework.org/api-guide/validators/#createonlydefault

이전 답변 :

파티에 꽤 늦은 것 같지만 어쨌든 여기에 내 2 센트가 있습니다.

필자는 필드가 업데이트되는 것을 방지하기 위해 두 개의 서로 다른 serializer를 사용하는 것은 의미가 없습니다. 나는 이와 똑같은 문제가 있었고 내가 사용한 접근 방식 validate은 Serializer 클래스에서 내 자신의 메서드 를 구현하는 것이 었습니다 . 제 경우에는 업데이트하고 싶지 않은 필드를이라고 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누군가가 잘못된 작업을 시도했다는 사실을 숨기고 싶지 않기 때문에 a를 제기 합니다. 이 작업을 원하지 않고 대신 필드를 업데이트하지 않고 작업을 완료하려면 다음을 수행하십시오.

줄을 제거하십시오.

raise ValidationError('Cannot update owner')

다음으로 대체하십시오.

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

도움이 되었기를 바랍니다!


이 접근 방식을 사용했습니다.

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

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

If you don't want to create another serializer, you may want to try customizing get_serializer_class() inside MyViewSet. This has been useful to me for simple projects.

# 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)

This is the counterpart of getattr(). The arguments are an object, a string and an arbitrary value. The string may name an existing attribute or a new attribute. The function assigns the value to the attribute, provided the object allows it. For example, setattr(x, 'foobar', 123) is equivalent to x.foobar = 123.


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)


This post mentions four different ways to achieve this goal.

This was the cleanest way I think: [collection must not be edited]

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)

Another method would be to add a validation method, but throw a validation error if the instance already exists and the value has changed:

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

In my case, I wanted a foreign key to never be updated:

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         

See also: Level-field validation in django rest framework 3.1 - access to the old value


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

ReferenceURL : https://stackoverflow.com/questions/22124555/django-rest-framework-disable-field-update-after-object-is-created

반응형