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メソッドに異なるシリアライザーが必要なようです。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が異なるジェネリックビュー
別のオプション(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()
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})
お役に立てれば!
私はこのアプローチを使用しました:
def get_serializer_class(self):
if getattr(self, 'object', None) is None:
return super(UserViewSet, self).get_serializer_class()
else:
return SerializerWithoutUsernameField
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
別の方法は、検証メソッドを追加することですが、インスタンスが既に存在し、値が変更されている場合は検証エラーをスローします。
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
別の解決策(別のシリアライザーの作成とは別に)は、インスタンスが設定されている場合に、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
別のシリアライザーを作成したくない場合は、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と同等です。
これ 投稿では、この目標を達成するための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)
詳細ユニバーサル方法「オブジェクト作成後のフィールド更新を無効にする」-調整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メソッドの場合)