[DRF]apiの作成（patch）【partial=True】

# Django-rest-framework勉強中です
前回までに書いた記事はこちらのとおりです。

|記事|
|---|
|[[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)|

# 準備
前回までと同じですが、一応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"
            ),
        ]
```


|STUDENT ID|CLASS NO|ATTENDANCE NO|NAME|DISTRICT NO|COMMENT|
|---|---|---|---|---|---|
|0005|2|2|fugafuga|地区1|２組２番|
|0004|2|1|hogehoge|地区2|２組１番|
|0003|1|3|山田３番|地区4|１組３番|
|0002|1|2|山田花子|地区2|１組２番|
|0001|1|1|山田太郎|地区1|１組１番|

## リソースの一部更新

特定のカラムのupdateのapiを作りたいです。restではupdateはpatchとputがあります。putはどちらかというと"置換"を表すようなので、今回はpatchが正しそうですね。patchメソッドを用いたapiを作成していきましょう。

### serializer
地区番号だけもらって、更新をする引っ越しAPIとしましょう。

``` python:serializer.py
from rest_framework import serializers
from ..models import Student


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

    class Meta:
        model = Student
        fields = [
            'district_no'
        ]
```

### view
とりあえずAPIViewからいきますよ。
`is_valid()`とかやってるところは同じなのですが、シリアライザの作成の仕方が今までと違いますね。今まではシリアライザには`request.data`を引数として渡していましたが、違うものがくっついています。
ここです。
`serializer = StudentSerializer(instance=instance, data=request.data, partial=True)`

第一引数の　instanceなのですが、ここをつけておくと後続の`save()`の挙動が変わります。
第一引数がない→create
第一引数がある→update
です。

さらに、`partial=True`をつけておくと`request.data`の値のみで更新をしてくれるみたいです。（request.dataの値は一部のみで良い）


```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 StudentSerializer


class StudentDistrictUpdateAPIView(APIView):

    def patch(self, request, pk, *args, **kwargs):
        """
        パスパラメータのpkを受け取りbodyの地区番号に更新
        """
        # instanceの取得
        instance = get_object_or_404(Student, pk=pk)

        # シリアライザの作成
        serializer = StudentSerializer(instance=instance, data=request.data, partial=True)

        # バリデーション
        serializer.is_valid(raise_exception=True)

        # DB更新
        serializer.save()

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

### 実行
URL, 実行ファイルについては割愛します。

`student_id='0001'`のデータを`district_no='2'`で更新します。

更新前

|STUDENT ID|CLASS NO|ATTENDANCE NO|NAME|DISTRICT NO|COMMENT|
|---|---|---|---|---|---|
|0001|1|1|山田太郎|地区1|１組１番|

更新後

|STUDENT ID|CLASS NO|ATTENDANCE NO|NAME|DISTRICT NO|COMMENT|
|---|---|---|---|---|---|
|0001|1|1|山田太郎|<font color="red">地区2</font>|１組１番|

ちゃんと更新してくれましたね！


## partial=Trueについて

ちょっと難しかったというか、イメージがつかなかったのでいろいろ試して説明してみます。

### まずは流れるSQL。

partial=TrueがついてるバージョンのSQL
> UPDATE "students_student" SET "class_no" = '1', "attendance_no" = 1, "name" = '山田太郎', "district_no" = '2', "comment" =１番' WHERE "students_student"."student_id" = '0001'; args=('1', 1, '山田太郎', '2', '１組１番', '0001')

partial=TrueがついてないバージョンのSQL
> UPDATE "students_student" SET "class_no" = '1', "attendance_no" = 1, "name" = '山田太郎', "district_no" = '2', "comment" =１番' WHERE "students_student"."student_id" = '0001'; args=('1', 1, '山田太郎', '2', '１組１番', '0001')

同じですね・・・てっきりrequest.dataのところだけ更新するSQLが流れると思っていましたが、違いました。

### serializer
ここに違いがありました。
今まではserializerは以下の定義だったので問題なしです。

``` python
    class Meta:
        model = Student
        fields = [
            'district_no'
        ]
```

ですが、serializerをこのようにfields全体で指定した場合のことを考えます。

``` python
    class Meta:
        model = Student
        fields = '__all__'
```

この場合、`partial=True`をつけないと、validationで怒られます。
> {"student_id":["この項目は必須です。"],"class_no":["この項目は必須です。"],"attendance_no":["この項目は必須です。"],"name":["この項目は必須です。"]}

必須な項目がないよってことですね。patchリクエストでどの項目をupdateするかをAPIリクエスト時に決めるようなAPIであればfieldsは__all__にして、リクエスト次第で決めるという作りにしたいときがあると思います。
こういうときに`partial=True`を使うようです。
`partial=True`をつけるとうまく動きました。
> {"result":true}

## 汎用APIViewを使用
汎用APIViewを使用してpatch処理をしてみます。

### view

以下のようになりました。

``` python:views.py
from rest_framework import generics

from ..models import Student
from ..serializers import StudentSerializer


class StudentDistrictUpdateAPIView(generics.UpdateAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentSerializer
```

おいおいまじか・・・ってくらいのコード量ですが。。以下が実行結果です。地区番号を4に更新しています。
> {"student_id":"0001","class_no":"1","attendance_no":1,"name":"山田太郎","district_no":"4","comment":"にゃ〜"}

汎用APIViewｍだとデフォルトではupdate後のinstanceをそのまま返してくれるみたいですね。いやー、ほんとにコード量少なくてすごい。~~なにしてるのか分かりづらい。。。~~

## 一旦基礎編は終了です
今までget, post, patchをやってきて、主要なAPIは作成できたかなと思います。
次回からは応用編ということで、外部キー接続しているテーブルを操作するAPIについて作成していこうと思います。