web-dev-qa-db-ja.com

DRF:ネストされたシリアライザーを使用した単純な外部キーの割り当て?

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
49
John Rork

ここでの最善の解決策は、読み取り用と書き込み用の2つの異なるフィールドを使用することです。少し重いリフティングを行わなければ、探しているものを単一のフィールドで取得することは困難です

読み取り専用フィールドはネストされたシリアライザー(この場合はChildSerializer)になり、期待するものと同じネストされた表現を取得できます。ほとんどの人はこれを単にchildと定義します。なぜなら、彼らはすでにこの時点でフロントエンドを記述しており、それを変更すると問題が発生するからです。

書き込み専用フィールドは PrimaryKeyRelatedField になります。これは、主キーに基づいてオブジェクトを割り当てるために通常使用するものです。これは書き込み専用である必要はありません。特に、受信したものと送信したものの間で対称性を保とうとする場合は、最適であると思われます。このフィールドには a source が外部キーフィールド(この例ではchild)に設定されている必要があるため、作成および更新時に適切に割り当てられます。


これはディスカッショングループで何度か取り上げられてきましたが、これが最善の解決策だと思います。 指摘してくれたSven Maurer に感謝します。

37
Kevin Brown

そのアプローチを取り、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=childchild_idデフォルトでは、子として機能し、オーバーライドされませんでした(望ましい動作)。 write_only=Truechild_idは書き込み可能ですが、ChildSerializerにすでにidが表示されているため、応答に表示されないようにします。

36
joslarson

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
    }
_



Screenshot
POSTMAN screenshot

28
JPG

作成/更新操作でフィールドを置き換える方法があります。

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
3

ケビンによって概説されたアプローチはおそらく最良の解決策だと思いますが、私はそれを機能させることができませんでした。ネストされたシリアライザーと主キーフィールドセットの両方がある場合、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
2
jayarnielsen

この問題をどのように解決したかを以下に示します。

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がオブジェクトをさらに処理できるように子オブジェクトをインスタンス化します。

2
Gaurav Butola

ここの少数の人々は、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のみで外部キーを持つオブジェクトを作成できますが、作成したオブジェクトを取得するときに(または実際に)シリアル化された完全なネストモデルを返します。

2
Bono

そのためのパッケージがあります! Drf Extra FieldsパッケージのPresentablePrimaryKeyRelatedFieldを確認してください。

https://github.com/Hipo/drf-extra-fields

0
Yiğit Güler