Django RESTフレームワークでは、標準のModelSerializerにより、IDを整数としてPOSTすることにより、ForeignKeyモデルの関係を割り当てまたは変更できます。
ネストされたシリアライザーからこの動作を取得する最も単純な方法は何ですか?
注、私は既存のデータベースオブジェクトの割り当てについてのみ話しているnotネストされた作成。
シリアライザーの追加の 'id'フィールドとカスタムcreate
およびupdate
メソッドを使用して、過去にこれを回避しましたが、これは私にとっては一見シンプルで頻繁な問題です最善の方法を知りたいです。
class Child(models.Model):
name = CharField(max_length=20)
class Parent(models.Model):
name = CharField(max_length=20)
phone_number = models.ForeignKey(PhoneNumber)
child = models.ForeignKey(Child)
class ChildSerializer(ModelSerializer):
class Meta:
model = Child
class ParentSerializer(ModelSerializer):
# phone_number relation is automatic and will accept ID integers
children = ChildSerializer() # this one will not
class Meta:
model = Parent
ここでの最善の解決策は、読み取り用と書き込み用の2つの異なるフィールドを使用することです。少し重いリフティングを行わなければ、探しているものを単一のフィールドで取得することは困難です。
読み取り専用フィールドはネストされたシリアライザー(この場合はChildSerializer
)になり、期待するものと同じネストされた表現を取得できます。ほとんどの人はこれを単にchild
と定義します。なぜなら、彼らはすでにこの時点でフロントエンドを記述しており、それを変更すると問題が発生するからです。
書き込み専用フィールドは PrimaryKeyRelatedField
になります。これは、主キーに基づいてオブジェクトを割り当てるために通常使用するものです。これは書き込み専用である必要はありません。特に、受信したものと送信したものの間で対称性を保とうとする場合は、最適であると思われます。このフィールドには a source
が外部キーフィールド(この例ではchild
)に設定されている必要があるため、作成および更新時に適切に割り当てられます。
これはディスカッショングループで何度か取り上げられてきましたが、これが最善の解決策だと思います。 指摘してくれたSven Maurer に感謝します。
そのアプローチを取り、2つの別々のフィールドを使用したい場合、Kevinの答えが話しているものの例を次に示します。
Models.pyで...
class Child(models.Model):
name = CharField(max_length=20)
class Parent(models.Model):
name = CharField(max_length=20)
phone_number = models.ForeignKey(PhoneNumber)
child = models.ForeignKey(Child)
その後、serializers.py ...
class ChildSerializer(ModelSerializer):
class Meta:
model = Child
class ParentSerializer(ModelSerializer):
# if child is required
child = ChildSerializer(read_only=True)
# if child is a required field and you want write to child properties through parent
# child = ChildSerializer(required=False)
# otherwise the following should work (untested)
# child = ChildSerializer()
child_id = serializers.PrimaryKeyRelatedField(
queryset=Child.objects.all(), source='child', write_only=True)
class Meta:
model = Parent
設定source=child
はchild_id
デフォルトでは、子として機能し、オーバーライドされませんでした(望ましい動作)。 write_only=True
はchild_id
は書き込み可能ですが、ChildSerializer
にすでにidが表示されているため、応答に表示されないようにします。
2つの異なるフィールドを使用すると、ok( @ Kevin Brown および @ joslarsonとして 言及)、しかし、私はperfect(私にとって)ではないと思います。あるキー(child
)からデータを取得し、別のキー(_child_id
_)にデータを送信することは、front- end開発者。 (犯罪なし)
それで、ここで提案するのは、to_representation()
メソッドのParentSerializer
が仕事をします。
_def to_representation(self, instance):
response = super().to_representation(instance)
response['child'] = ChildSerializer(instance.child).data
return response
_
シリアライザーの完全な表現
_class ChildSerializer(ModelSerializer):
class Meta:
model = Child
fields = '__all__'
class ParentSerializer(ModelSerializer):
class Meta:
model = Parent
fields = '__all__'
def to_representation(self, instance):
response = super().to_representation(instance)
response['child'] = ChildSerializer(instance.child).data
return response
_
この方法の利点は?
この方法を使用することにより、作成と読み取りに2つの別個のフィールドは必要ありません。ここでは、作成と読み取りの両方をchild
キーを使用して実行できます。
サンプルペイロードを作成してparent
インスタンス
_{
"name": "TestPOSTMAN_name",
"phone_number": 1,
"child": 1
}
_
作成/更新操作でフィールドを置き換える方法があります。
class ChildSerializer(ModelSerializer):
class Meta:
model = Child
class ParentSerializer(ModelSerializer):
child = ChildSerializer()
# called on create/update operations
def to_internal_value(self, data):
self.fields['child'] = serializers.PrimaryKeyRelatedField(
queryset=Child.objects.all())
return super(ParentSerializer, self).to_internal_value(data)
class Meta:
model = Parent
ケビンによって概説されたアプローチはおそらく最良の解決策だと思いますが、私はそれを機能させることができませんでした。ネストされたシリアライザーと主キーフィールドセットの両方がある場合、DRFはエラーをスローし続けました。どちらかを削除しても機能しますが、明らかに必要な結果が得られませんでした。私が思いついたのは、読み取りと書き込み用に2つの異なるシリアライザーを作成することです。
serializers.py:
class ChildSerializer(serializers.ModelSerializer):
class Meta:
model = Child
class ParentSerializer(serializers.ModelSerializer):
class Meta:
abstract = True
model = Parent
fields = ('id', 'child', 'foo', 'bar', 'etc')
class ParentReadSerializer(ParentSerializer):
child = ChildSerializer()
views.py
class ParentViewSet(viewsets.ModelViewSet):
serializer_class = ParentSerializer
queryset = Parent.objects.all()
def get_serializer_class(self):
if self.request.method == 'GET':
return ParentReadSerializer
else:
return self.serializer_class
この問題をどのように解決したかを以下に示します。
serializers.py
class ChildSerializer(ModelSerializer):
def to_internal_value(self, data):
if data.get('id'):
return get_object_or_404(Child.objects.all(), pk=data.get('id'))
return super(ChildSerializer, self).to_internal_value(data)
ネストされた子シリアライザーを、シリアライザーから取得するのと同じように、つまり子をjson/dictionaryとして渡すだけです。 to_internal_value
子オブジェクトが有効なIDを持っている場合、DRFがオブジェクトをさらに処理できるように子オブジェクトをインスタンス化します。
ここの少数の人々は、1つのフィールドを保持する方法を配置しましたが、オブジェクトを取得するときに詳細を取得し、IDのみでそれを作成することができます。人々が興味を持っている場合、私はもう少し一般的な実装を行いました:
最初のテスト:
from rest_framework.relations import PrimaryKeyRelatedField
from Django.test import TestCase
from .serializers import ModelRepresentationPrimaryKeyRelatedField, ProductSerializer
from .factories import SomethingElseFactory
from .models import SomethingElse
class TestModelRepresentationPrimaryKeyRelatedField(TestCase):
def setUp(self):
self.serializer = ModelRepresentationPrimaryKeyRelatedField(
model_serializer_class=SomethingElseSerializer,
queryset=SomethingElse.objects.all(),
)
def test_inherits_from_primary_key_related_field(self):
assert issubclass(ModelRepresentationPrimaryKeyRelatedField, PrimaryKeyRelatedField)
def test_use_pk_only_optimization_returns_false(self):
self.assertFalse(self.serializer.use_pk_only_optimization())
def test_to_representation_returns_serialized_object(self):
obj = SomethingElseFactory()
ret = self.serializer.to_representation(obj)
self.assertEqual(ret, SomethingElseSerializer(instance=obj).data)
次に、クラス自体:
from rest_framework.relations import PrimaryKeyRelatedField
class ModelRepresentationPrimaryKeyRelatedField(PrimaryKeyRelatedField):
def __init__(self, **kwargs):
self.model_serializer_class = kwargs.pop('model_serializer_class')
super().__init__(**kwargs)
def use_pk_only_optimization(self):
return False
def to_representation(self, value):
return self.model_serializer_class(instance=value).data
どこかにシリアライザーがある場合、使用方法は次のようになります。
class YourSerializer(ModelSerializer):
something_else = ModelRepresentationPrimaryKeyRelatedField(queryset=SomethingElse.objects.all(), model_serializer_class=SomethingElseSerializer)
これにより、PKのみで外部キーを持つオブジェクトを作成できますが、作成したオブジェクトを取得するときに(または実際に)シリアル化された完全なネストモデルを返します。
そのためのパッケージがあります! Drf Extra FieldsパッケージのPresentablePrimaryKeyRelatedFieldを確認してください。