web-dev-qa-db-ja.com

Django REST Framework POSTネストされたオブジェクト

現在、Django Rest Frameworkで小さな問題に直面しています。ネストされたオブジェクトを含むオブジェクトを投稿しようとしています。

ここに私の_serializers.py_があります:

_class ClassSerializer(serializers.ModelSerializer):
    class Meta:
        model = Class
        fields = ('number', 'letter')


class SubjectSerializer(serializers.ModelSerializer):
    class Meta:
        model = Subject
        fields = ('title',)


class ExamSerializer(serializers.ModelSerializer):
    subject = SubjectSerializer()
    clazz = ClassSerializer()

    class Meta:
        model = Exam
        fields = ('id', 'subject', 'clazz', 'topic', 'date', 'details')
        depth = 1

    def create(self, validated_data):
        return Exam.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.__dict__.update(**validated_data)
        instance.save()

        return instance
_

そして_views.py_からのcreate()

_def create(self, request):
    serializer = self.serializer_class(data=request.data)
    serializer.is_valid(raise_exception=True)
    self.perform_create(serializer)

    return Response(serializer.validated_data, status=status.HTTP_201_CREATED)
_

そして、これはPostmanからの応答です: Postman response

私はこの問題に関するいくつかの投稿をここで読みましたが、私はまだそれで立ち往生しています。いくつかの方法で修正しようとしましたが、まだ_"This field is required."_を返しています。

20
wencakisa

あなたはネストされたシリアル化の問題に対処しています。先に進む前に、リンクされたドキュメントをお読みください。

あなたの質問はDRFの複雑な問題に関連しているため、シリアライザーとビューセットがどのように機能するかを理解するには説明と議論が必要です。

さまざまなHTTPメソッドのデータの異なる表現を使用して、同じエンドポイントを介してSubjectおよびClassデータを表す問題を説明します。これは、人々がデータをネストされた形式。彼らは、クリーンな使用に十分な情報をユーザーインターフェイスに提供したいと考えています。ドロップダウンセレクターを使用します。

デフォルトではDjangoおよびDjango REST Framework(DRF)は関連オブジェクトを参照します(SubjectおよびClassprimary keys。これらは、デフォルトでは、Djangoで整数キーを自動インクリメントします。他の方法で参照したい場合は、いくつかの異なるオプションがあります。

  1. 最初のオプションは、作成および更新ロジックを特化することです:他のいくつかの属性を介してクラスを参照し、自分で作成するためのルックアップを手動で記述する、または参照するキーをクラスの主キーとして設定します。一意である限り、クラスの名前、UUID、またはその他の属性をプライマリデータベースキーとして設定できます単一フィールド(これについて言及している理由は、現時点では、複合(数字、文字)検索用語で構成される複合検索でClassモデルを検索しているからです。たとえば、createビューメソッド(POSTの場合)で関連オブジェクトのルックアップをオーバーライドできますが、updateビューメソッド(PUTおよびPATCHの場合)でも同様のルックアップを処理する必要があります。
  2. 第二に、私の意見では、望ましいオプションは、オブジェクト表現を特化することです:通常、主キーとを介してクラスを参照しますオブジェクトを読み取るためのシリアライザーを1つ作成し、オブジェクトを作成および更新するために1つ作成します。これは、シリアライザークラスの継承と表現のオーバーライドによって簡単に実現できます。 POST、PUT、PATCHなどのリクエストで主キーを使用して、クラス参照と外部キーを更新します。

オプション1:作成および更新で任意の属性を使用してクラスとサブジェクトを検索:

ネストされたクラスシリアライザーを読み取り専用として設定します。

class ExamSerializer(serializers.ModelSerializer):
    subject = SubjectSerializer(read_only=True)
    clazz = ClassSerializer(read_only=True)

ビューの作成をオーバーライドして、自由形式の属性の関連クラスを検索します。また、DRFがこれをmixinでどのように実装するかも確認してください。また、これらを正しく処理するにはupdateメソッドをオーバーライドし、このルートを取る場合はPATCH(更新)に加えてPUT(部分更新)サポートを考慮する必要があります。

def create(self, request):
    # Look up objects by arbitrary attributes.
    # You can check here if your students are participating
    # the classes and have taken the subjects they sign up for.
    subject = get_object_or_404(Subject, title=request.data.get('subject'))
    clazz = get_object_or_404(
        Class, 
        number=request.data.get('clazz_number')
        letter=request.data.get('clazz_letter')
    )

    serializer = self.get_serializer(data=request.data)
    serializer.is_valid(raise_exception=True)
    serializer.save(clazz=clazz, subject=subject)
    headers = self.get_success_headers(serializer.data)

    return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

オプション2:シリアライザを読み書き用に特化し、主キーを使用します。これは慣用的なアプローチです:

最初に、通常の操作(POST、PUT、PATCH)に使用するデフォルトのModelSerializerを定義します。

class ExamSerializer(serializers.ModelSerializer)
    class Meta:
        model = Exam
        fields = ('id', 'subject', 'clazz', 'topic', 'date', 'details')

次に、必要なフィールドを、データの読み取り用に提供する表現の種類でオーバーライドします(GET):

class ExamReadSerializer(ExamSerializer):
     subject = SubjectSerializer(read_only=True)
     clazz = ClassSerializer(read_only=True)

次に、異なる操作に使用するシリアライザを指定しますViewSetに対して。ここでは、ネストされたSubjectおよびClassデータを読み取り操作に返しますが、更新操作には主キーのみを使用します(はるかに簡単です)。

class ExamViewSet(viewsets.ModelViewSet):
     queryset = Exam.objects.all()

     def get_serializer_class(self):
         # Define your HTTP method-to-serializer mapping freely.
         # This also works with CoreAPI and Swagger documentation,
         # which produces clean and readable API documentation,
         # so I have chosen to believe this is the way the
         # Django REST Framework author intended things to work:
         if self.request.method in ['GET']:
             # Since the ReadSerializer does nested lookups
             # in multiple tables, only use it when necessary
             return ExamReadSerializer
         return ExamSerializer

ご覧のとおり、オプション2はDRF(get_serializer_class実装)の上に3行の手書きコードのみを含む、それほど複雑ではなく、エラーが発生しやすいようです。フレームワークのロジックに、オブジェクトの表現と作成および更新を理解させてください。

私は他にも多くのアプローチを見てきましたが、これまでのところ、これらは私にとって維持し、クリーンな方法でDRFの設計を利用するための最小のコードを生成したものです。

42
Aleksi Häkli

追加のクラスを実行せずに簡単な方法は、自分でシリアル化することです。

class ExamSerializer(serializers.ModelSerializer):
    class Meta:
        model = Exam
        fields = ('id', 'subject', 'clazz', 'topic', 'date', 'details')

    def to_representation(self, instance):
        data = super().to_representation(instance)
        data['subject'] = SubjectSerializer(
            Subject.objects.get(pk=data['subject'])).data
        data['clazz'] = ClassSerializer(
            Class.objects.get(pk=data['clazz'])).data
        return data
4
validname

ネストされたJSONオブジェクトをDRF(Django Rest Framework)に投稿しようとすると、同じ問題が発生しました。

ネストされたシリアライザーの書き込みを適切にセットアップしたら( writable nested serializers のドキュメントを参照)、 browsable API を使用してデータを投稿/入力することにより、それが機能することをテストできますそこ。それが機能し、JSONオブジェクトをポスト/プットするときにネストされたモデルで「このフィールドは必須です」エラーが引き続き発生する場合は、リクエストのコンテンツタイプを設定します。

この回答 必要な解決策を提供しました。以下に要約します。

$.ajax ({
  // Other parameters e.g. url, type
  data: JSON.stringify(data),
  dataType: "json",
  contentType: "application/json; charset=utf-8",
});

Jsオブジェクトを「contentType」と「stringify」に設定する必要がありました。

0
Keoni Mahelona

問題を解決するには、このパッケージを使用できます drf-rw-serializers

必要なのは、2つのシリアライザーを使用することです(1つは読み取り用、もう1つは書き込み用)。

serializers.py

class ClassSerializer(serializers.ModelSerializer):
    class Meta:
        model = Class
        fields = ('number', 'letter')


class SubjectSerializer(serializers.ModelSerializer):
    class Meta:
        model = Subject
        fields = ('title',)


class ExamSerializer(serializers.ModelSerializer):
    subject = SubjectSerializer()
    clazz = ClassSerializer()

    class Meta:
        model = Exam
        fields = ('id', 'subject', 'clazz', 'topic', 'date', 'details')

class WriteExamSerializer(serializers.ModelSerializer):
    subject = SubjectSerializer()
    clazz = ClassSerializer()

    class Meta:
        model = Exam
        fields = ('id', 'subject', 'clazz', 'topic', 'date', 'details')

    def create(self, validated_data):
        subject = validated_data.pop('subject', None)
        # logic to update subject
        clazz = validated_data.pop('clazz', None)
        # logic to update clazz
        return super().create(validated_data)

    def update(self, validated_data):
        subject = validated_data.pop('subject', None)
        # logic to update subject
        clazz = validated_data.pop('clazz', None)
        # logic to update clazz
        return super().update(validated_data)

api_views.py

from drf_rw_serializers import generics

from .models import Exam
from .serializers import WriteExamSerializer, ExamSerializer


class ExamListCreateView(generics.ListCreateAPIView):
    queryset = Exam.objects.all()
    write_serializer_class = WriteExamSerializer
    read_serializer_class = ReadExamSerializer
0
Greg Eremeev