web-dev-qa-db-ja.com

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

ありがとう!

56
Brad Reardon

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:同じURLの独立したGETとPUTが異なるジェネリックビュー

52

別のオプション(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
22
VoSi

私のアプローチは、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
Gooshan

UPDATE:

Rest Frameworkには既にこの機能が搭載されています。 「作成専用」フィールドを持つ正しい方法は、CreateOnlyDefault()オプションを使用することです。

あとは、ドキュメントを読むだけです! http://www.Django-rest-framework.org/api-guide/validators/#createonlydefault

古い回答:

私はパーティーにかなり遅れているように見えますが、とにかくここに私の2セントがあります。

私には、フィールドが更新されないようにしたいという理由だけで、2つの異なるシリアライザーを使用することは意味がありません。これとまったく同じ問題があり、使用したアプローチは、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
LuisCien

私はこのアプローチを使用しました:

def get_serializer_class(self):
    if getattr(self, 'object', None) is None:
        return super(UserViewSet, self).get_serializer_class()
    else:
        return SerializerWithoutUsernameField
2
Alex Rothberg
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

1
gzerone

別の方法は、検証メソッドを追加することですが、インスタンスが既に存在し、値が変更されている場合は検証エラーをスローします。

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         

参照: Django rest framework 3.1-古い値へのアクセス)でのレベルフィールド検証

1
rrauenza

別の解決策(別のシリアライザーの作成とは別に)は、インスタンスが設定されている場合に、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
1
Pawel Kozela

別のシリアライザーを作成したくない場合は、MyViewSet内でget_serializer_class()をカスタマイズしてみてください。これは単純なプロジェクトの場合に役立ちました。

# 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(オブジェクト、名前、値)

これはgetattr()の対応物です。引数は、オブジェクト、文字列、および任意の値です。文字列は、既存の属性または新しい属性を指定できます。この関数は、オブジェクトで許可されている場合、値を属性に割り当てます。たとえば、setattr(x、 'foobar'、123)はx.foobar = 123と同等です。

1
Nogurenn

これ 投稿では、この目標を達成するための4つの異なる方法について言及しています。

これは私が考える最もクリーンな方法でした:[コレクションを編集してはいけません]

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)
1
Hojat Modaresi

詳細ユニバーサル方法「オブジェクト作成後のフィールド更新を無効にする」-調整read_only_fields per View.action

1)シリアライザーにメソッドを追加します(独自のベース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という名前のシリアライザーdictのメタに追加

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

上記の例では、clientフィールドはアクションに対して読み取り専用になります: 'update'、 'partial_update'(PUT、PATCHメソッドの場合)

1
pymen