web-dev-qa-db-ja.com

Django REST Framework ModelSerializer in a request type?

BookモデルとAuthorモデルがあるこのケースを考えてみます。

serializers.py

class AuthorSerializer(serializers.ModelSerializer):

    class Meta:
        model = models.Author
        fields = ('id', 'name')

class BookSerializer(serializers.ModelSerializer):
    author = AuthorSerializer(read_only=True)

    class Meta:
        model = models.Book
        fields = ('id', 'title', 'author')

viewsets.py

class BookViewSet(viewsets.ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

これは、本のGETリクエストを送信するとうまくいきます。本の詳細と入れ子になった著者の詳細を含む入れ子になったシリアライザーで出力を取得します。

ただし、本を作成または更新する場合は、POST/PUT/PATCHに、著者のIDだけでなく、ネストされた詳細を送信する必要があります。著者オブジェクト全体ではなく、著者IDを指定して本オブジェクトを作成/更新できるようにしたい。

だから、私のシリアライザがGETリクエストに対してこのように見える場所

class BookSerializer(serializers.ModelSerializer):
    author = AuthorSerializer(read_only=True)

    class Meta:
        model = models.Book
        fields = ('id', 'title', 'author')

私のシリアライザは、POSTPUTPATCHリクエストでは次のようになります

class BookSerializer(serializers.ModelSerializer):
    author = PrimaryKeyRelatedField(queryset=Author.objects.all())

    class Meta:
        model = models.Book
        fields = ('id', 'title', 'author')

また、要求のタイプごとに2つの完全に別個のシリアライザーを作成したくありません。 authorBookSerializerフィールドを変更するだけです。

最後に、この全体を行うためのより良い方法はありますか?

14
him229

私見、複数のシリアライザーはますます混乱を生むだけです。

むしろ私は以下の解決策を好みます:

  1. ビューセットを変更しないでください(デフォルトのままにします)
  2. シリアライザに.validate()メソッドを追加します。他の必要な.createまたは.update()などと一緒に。ここでは、実際のロジックはvalidate()メソッドに入ります。リクエストタイプに基づいて、シリアライザの必要に応じて、validated_data辞書を作成します。

これは最もクリーンなアプローチだと思います。

DRF:GETリクエストのすべてのフィールドを許可するが、POSTを1つのフィールドのみに制限する で私の同様の問題と解決策を参照してください

7
Jadav Bheda

シリアライザのフィールドを動的に変更できるDRFの機能があります http://www.Django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields

私の使用例:GETでslugフィールドを使用して関係のNice repを表示できるようにしますが、POST/PUTでクラシックな主キーの更新に切り替えます。シリアライザを次のように調整します。

class FooSerializer(serializers.ModelSerializer):
    bar = serializers.SlugRelatedField(slug_field='baz', queryset=models.Bar.objects.all())

    class Meta:
        model = models.Foo
        fields = '__all__'

    def __init__(self, *args, **kwargs):
        super(FooSerializer, self).__init__(*args, **kwargs)

        try:
            if self.context['request'].method in ['POST', 'PUT']:
                self.fields['bar'] = serializers.PrimaryKeyRelatedField(queryset=models.Bar.objects.all())
        except KeyError:
            pass

KeyErrorは、リクエストなしで、場合によってはユニットテストでコードの初期化時にスローされます。

責任を持って楽しんで使用してください。

7
jmoz

ViewSetget_serializer_classメソッドを探しています。これにより、使用するシリアライザの要求タイプをオンに切り替えることができます。

from rest_framework import viewsets

class MyModelViewSet(viewsets.ModelViewSet):

    model = MyModel
    queryset = MyModel.objects.all()

    def get_serializer_class(self):
        if self.action in ('create', 'update', 'partial_update'):
            return MySerializerWithPrimaryKeysForCreatingOrUpdating
        else:
            return MySerializerWithNestedData
4
Aaron Lelevier

私がこの問題に対処することになった方法は、関連フィールドであるときに別のシリアライザーを用意することでした。

class HumanSerializer(PersonSerializer):

    class Meta:
        model = Human
        fields = PersonSerializer.Meta.fields + (
            'firstname',
            'middlename',
            'lastname',
            'sex',
            'date_of_birth',
            'balance'
        )
        read_only_fields = ('name',)


class HumanRelatedSerializer(HumanSerializer):
    def to_internal_value(self, data):
        return self.Meta.model.objects.get(id=data['id'])


class PhoneNumberSerializer(serializers.ModelSerializer):
    contact = HumanRelatedSerializer()

    class Meta:
        model = PhoneNumber
        fields = (
            'id',
            'contact',
            'phone',
            'extension'
        )

あなたはこのようなことをすることができますが、RelatedSerializerのために:

 def to_internal_value(self, data):
     return self.Meta.model.objects.get(id=data)

したがって、シリアライズする場合は関連オブジェクトをシリアライズし、デシリアライズする場合は関連オブジェクトを取得するために必要なのはidだけです。

0
miyamoto

私はそれが少し遅れていることを知っていますが、誰かがそれを必要とする場合に備えて。 drfのサードパーティパッケージには、リクエストクエリパラメータを介して動的シリアライズフィールドの設定を許可するものがあります(公式ドキュメントにリストされています: https:// www.Django-rest-framework.org/api-guide/serializers/#third-party-packages )。

IMOの最も完全なものは次のとおりです。

  1. https://github.com/AltSchool/dynamic-rest
  2. https://github.com/rsinger86/drf-flex-fields

ここで、(1)には(2)よりも多くの機能があります(何をしたいかによっては多すぎる可能性があります)。

(2)を使用すると、次のようなことができます(リポジトリのreadmeから抽出)。

class CountrySerializer(FlexFieldsModelSerializer):
    class Meta:
        model = Country
        fields = ['name', 'population']


class PersonSerializer(FlexFieldsModelSerializer):
    country = serializers.PrimaryKeyRelatedField(read_only=True)

    class Meta:
        model = Person
        fields = ['id', 'name', 'country', 'occupation']

    expandable_fields = {
        'country': (CountrySerializer, {'source': 'country', 'fields': ['name']})
    }

デフォルトの応答:

{
  "id" : 13322,
  "name" : "John Doe",
  "country" : 12,
  "occupation" : "Programmer"
}

GET/person/13322?expand = countryを実行すると、応答は次のように変わります:

{
  "id" : 13322,
  "name" : "John Doe",
  "country" : {
    "name" : "United States"
  },
  "occupation" : "Programmer",
}

入れ子の国オブジェクトから人口が省略されていることに注意してください。これは、埋め込まれたCountrySerializerに渡されるときにフィールドが['name']に設定されたためです。

このようにして、IDだけを含むPOSTリクエストを保持し、詳細を含めるためにGET応答を「展開」できます。

0
aleclara95