[DRF]結合テーブルの更新（part3）【Update】

# Django-rest-frameworkでAPIを作成します
前回までの記事一覧です。

|基本編|
|---|
|[[DRF]apiの作成（patch）](https://qiita.com/aja_min/items/a84b00c74225e41e7da5)|
|[[DRF]apiの作成（post）](https://qiita.com/aja_min/items/cf297013c2c75e07d443)|
|[[DRF]apiの作成（get）part2](https://qiita.com/aja_min/items/ca360960a6f6c45cddcc)|
|[[DRF]apiの作成（get）part1](https://qiita.com/aja_min/items/eea4d49928a55946afbe)|

|応用編|
|---|
|[[DRF]結合テーブルの更新（part2）](https://qiita.com/aja_min/items/c8629fe341fd8e937f3a)|
|[[DRF]結合テーブルの更新（part1）](https://qiita.com/aja_min/items/af1b0b9785f380b8cd9c)|
|[[DRF]結合テーブルの取得](https://qiita.com/aja_min/items/9d6d6cf2ac9495836f5f)|

[前回の記事](https://qiita.com/aja_min/items/c8629fe341fd8e937f3a)では、drf-writable-nestedを使用して、結合テーブルを新規作成しました。今回は`update()`メソッドのオーバーライドにより更新をしてみようと思います。

## model
modelは以下のとおりです。

``` python: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が1対1対応しています。

## update

今回は既存レコードを更新します。

### view
viewは前回（create）と変わっていません。
とりあえず何も考えず、既存の普通のレコードの更新のように書いています。
`partial=True`もついてます。

```python:views.py
from django.shortcuts import get_object_or_404

from rest_framework.views import APIView
from rest_framework.response import Response
from ..models import Student
from ..serializers import StudentExamUpdateSerializer


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

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

        instance = get_object_or_404(Student, pk=pk)

        # serializer作成
        serializer = StudentExamUpdateSerializer(instance=instance, data=request.data, partial=True)

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

        serializer.save()

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


### serializer（未完成）

クラスの名前だけ変えました。

```python:serizlizers.py
from rest_framework import serializers
from ..models import Student, Exam



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

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


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

    exam = ExamOfStudentExamUpdateSerializer()

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

```

### 実行

実行ファイルは以下のとおりです。
urls.pyは貼りませんが、パスパラメータでpkをもらってそのpkに対して更新しにいきます。

名前と数学の点数を修正しにいきます。

```python
import requests
import json

student_id='0001'
name="更木剣八"

math_score = 30
exam = {
    'math_score': math_score,
}
body = {
    'name':name,
}

headers = {"Content-Type" : "application/json;charset=UTF-8"}
response = requests.post('http://127.0.0.1:8000/student/{}/student_exam_update/'.format(student_id), data=json.dumps(body), headers=headers)

print(response.text)
```

> AssertionError: The `.update()` method does not support writable nested fields by default.
Write an explicit `.update()` method for serializer `students.serializers.student_exam_update.StudentExamUpdateSerializer`, or set `read_only=True` on nested serializer fields.

やっぱり怒られました。しょうがない、updateをオーバーライドしてみましょうか。

### serializer（完成）

updateを追加しています。

``` diff_python:serizlizers.py
from rest_framework import serializers
from ..models import Student, Exam



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

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


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

    exam = ExamOfStudentExamUpdateSerializer()

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

+   def update(self, instance, validated_data):
+   
+       exam_data = validated_data.pop('exam')
+       instance = super().update(instance, validated_data)
+   
+       exam_serializer = ExamOfStudentExamUpdateSerializer(instance=instance.exam, data=exam_data)
+       exam_serializer.is_valid()
+       exam_serializer.save()
+   
+       return instance

```

### 再実行

> {"result":true}

出来ました。

serializerがあれば、これを使用してupdateとすることができそうです。
[こちら](https://dev.to/a1k89/django-rest-framework-writable-nested-serializer-515f)を参考にしました。
個人的にとっても迷う実装だったのですが、参考リンクのおかげでなんとなく腹落ちしました。

次回は`WritableNestedModelSerializer`でupdateします。