[DRF]結合テーブルの更新【create】(part1)


Django-rest-frameworkでAPIを作成します

前回までの記事一覧です。

基本編
[DRF]apiの作成(patch)
[DRF]apiの作成(post)
[DRF]apiの作成(get)part2
[DRF]apiの作成(get)part1
応用編
[DRF]結合テーブルの取得

この記事では、OneToOneフィールドで結合したテーブルを更新します。

model

modelは以下のとおりです。

models.py
from django.db import models

DISTRICT_CATEGORIES = [
    ("1", "地区1"),
    ("2", "地区2"),
    ("3", "地区3"),
    ("4", "地区4"),
]


class Student(models.Model):
    """生徒情報"""

    # 生徒ID
    student_id = models.CharField(max_length=4, primary_key=True)

    # クラス
    class_no = models.CharField(max_length=1)

    # 出席番号
    attendance_no = models.IntegerField()

    # 名前
    name = models.CharField(max_length=20)

    # 地区番号
    district_no = models.CharField(max_length=1, choices=DISTRICT_CATEGORIES)

    # フリーコメント
    comment = models.CharField(max_length=200,blank=True)

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=["class_no", "attendance_no"],
                name="class_attendance_unique"
            ),
        ]


class Exam(models.Model):
    """試験情報"""

    # 国語
    japanese_score = models.IntegerField(null=True, blank=True)

    # 数学
    math_score = models.IntegerField(null=True, blank=True)

    # 英語
    english_score = models.IntegerField(null=True, blank=True)

    # 生徒ID
    student = models.OneToOneField(Student, on_delete=models.CASCADE)

Student, ExamというOneToOneフィールドで結合しているテーブルに付いて考えましょう。

create

まずは新規レコード作成から考えてみます。

serializer(未完成)

前回と同じようなserializerです。これを出力でなく入力に使います。

serializers.py
from rest_framework import serializers
from ..models import Student, Exam


class ExamSerializer(serializers.ModelSerializer):
    """Examクラスのシリアライザ"""

    class Meta:
        model = Exam
        fields=[
            'japanese_score',
            'math_score',
            'english_score'
        ]


class StudentSerializer(serializers.ModelSerializer):
    """Studentクラスのシリアライザ"""

    exam = ExamSerializer()

    class Meta:
        model = Student
        fields = [
            'student_id',
            'class_no',
            'attendance_no',
            'name',
            'district_no',
            'comment',
            'exam'
        ]

view

とりあえず新規作成のviewです。
あまり結合テーブルということは意識していませんね。

views.py
from rest_framework.views import APIView
from rest_framework.response import Response

from ..serializers import StudentSerializer


class StudentExamCreateAPIView(APIView):
    """生徒情報、試験情報を登録"""

    def post(self, request, *args, **kwargs):

        # serializer作成
        serializer = StudentSerializer(data=request.data)

        # validate
        try:
            serializer.is_valid(raise_exception=True)
        except Exception:
            raise Exception

        serializer.save()

        # Response
        return Response({'result':True})

実行

request.py
import requests
import json

student_id='0011'
class_no='3'
attendance_no=10
name="茶渡泰虎"
district_no='2'
comment='チャドの霊圧が・・・消えた!?'

japanese_score = 90
math_score = 60
english_score = 50
exam = {
    'japanese_score': japanese_score,
    'math_score': math_score,
    'english_score': english_score,
}
body = {
    'student_id':student_id,
    'class_no':class_no,
    'attendance_no':attendance_no,
    'name':name,
    'district_no':district_no,
    'comment':comment,
    'exam': exam,
}

headers = {"Content-Type" : "application/json"}
response = requests.post('http://127.0.0.1:8000/student/student_exam_create/', data=json.dumps(body), headers=headers)

print(response.text)

怒られました。

AssertionError: The .create() method does not support writable nested fields by default.
Write an explicit .create() method for serializer students.serializers.student_exam_create.StudentSerializer, or set read_only=True on nested serializer fields.

save()を呼んだときに呼ばれるcreate()でエラったようですね。ネストされた項目の更新はデフォルトで出来ないから、readonly=Trueをつけるか、自分でcreate()をオーバーライドしてね。と言われてます。

公式のドキュメント(writable-nested-serializers)を参照してみました。
同じように実装してみましょう。

serializer(修正版)

serializer
from rest_framework import serializers
from ..models import Student, Exam


class ExamSerializer(serializers.ModelSerializer):
    """Examクラスのシリアライザ"""

    class Meta:
        model = Exam
        fields=[
            'japanese_score',
            'math_score',
            'english_score'
        ]


class StudentSerializer(serializers.ModelSerializer):
    """Studentクラスのシリアライザ"""

    exam = ExamSerializer()

    class Meta:
        model = Student
        fields = [
            'student_id',
            'class_no',
            'attendance_no',
            'name',
            'district_no',
            'comment',
            'exam'
        ]

+   def create(self, validated_data):
+       exam_data = validated_data.pop('exam')
+       student = Student.objects.create(**validated_data)
+       Exam.objects.create(student=student, **exam_data)
+
+       return student

はい。createメソッドを追加しました。
validated_dataからexamデータをpopして、ネストされた構造をネストされていない状態にすることが出来ました。そしてそれぞれのmodelについてcreateメソッドを実行することによりデータの作成ができています。

実行

{"result":true}

STUDENT ID CLASS NO ATTENDANCE NO NAME DISTRICT NO COMMENT
0011 3 10 茶渡泰虎 地区2 チャドの霊圧が・・・消えた!?
STUDENT ID JAPANESE SCORE MATH SCORE ENGLISH SCORE
0011 90 60 50

無事createが出来ました。

createをオーバーライドしてくれなくてもやってくれたらいいのになーちょっと思ったりもしますが、まぁ想定されていないようなやり方なのでしょうか。

今回は新規作成をやりましたが、更新だったり、あと他にもやり方があると思うのでpart2などで勉強していくこととしましょう。