Django-rest-framework勉強中です
前回までに書いた記事はこちらのとおりです。
記事 |
---|
[DRF]apiの作成(post) |
[DRF]apiの作成(get)part2 |
[DRF]apiの作成(get)part1 |
準備
前回までと同じですが、一応Model構成載せておきます。
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 | 2組2番 |
0004 | 2 | 1 | hogehoge | 地区2 | 2組1番 |
0003 | 1 | 3 | 山田3番 | 地区4 | 1組3番 |
0002 | 1 | 2 | 山田花子 | 地区2 | 1組2番 |
0001 | 1 | 1 | 山田太郎 | 地区1 | 1組1番 |
リソースの一部更新
特定のカラムのupdateのapiを作りたいです。restではupdateはpatchとputがあります。putはどちらかというと"置換"を表すようなので、今回はpatchが正しそうですね。patchメソッドを用いたapiを作成していきましょう。
serializer
地区番号だけもらって、更新をする引っ越しAPIとしましょう。
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の値は一部のみで良い)
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 | 1組1番 |
更新後
STUDENT ID | CLASS NO | ATTENDANCE NO | NAME | DISTRICT NO | COMMENT |
---|---|---|---|---|---|
0001 | 1 | 1 | 山田太郎 | 地区2 | 1組1番 |
ちゃんと更新してくれましたね!
partial=Trueについて
ちょっと難しかったというか、イメージがつかなかったのでいろいろ試して説明してみます。
まずは流れるSQL。
partial=TrueがついてるバージョンのSQL
UPDATE "students_student" SET "class_no" = '1', "attendance_no" = 1, "name" = '山田太郎', "district_no" = '2', "comment" =1番' WHERE "students_student"."student_id" = '0001'; args=('1', 1, '山田太郎', '2', '1組1番', '0001')
partial=TrueがついてないバージョンのSQL
UPDATE "students_student" SET "class_no" = '1', "attendance_no" = 1, "name" = '山田太郎', "district_no" = '2', "comment" =1番' WHERE "students_student"."student_id" = '0001'; args=('1', 1, '山田太郎', '2', '1組1番', '0001')
同じですね・・・てっきりrequest.dataのところだけ更新するSQLが流れると思っていましたが、違いました。
serializer
ここに違いがありました。
今まではserializerは以下の定義だったので問題なしです。
class Meta:
model = Student
fields = [
'district_no'
]
ですが、serializerをこのようにfields全体で指定した場合のことを考えます。
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
以下のようになりました。
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":"にゃ〜"}
汎用APIViewmだとデフォルトではupdate後のinstanceをそのまま返してくれるみたいですね。いやー、ほんとにコード量少なくてすごい。なにしてるのか分かりづらい。。。
一旦基礎編は終了です
今までget, post, patchをやってきて、主要なAPIは作成できたかなと思います。
次回からは応用編ということで、外部キー接続しているテーブルを操作するAPIについて作成していこうと思います。