django-rest-frameworkの勉強中
前回の記事の続きです。
drfでgetのAPIを作成をしたので、今回は
- listでの取得
- 汎用APIViewを使用しての取得
を行います。
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番 |
一覧の取得をします
前回は一件だけの取得だったので、次は一覧の取得をします。
たとえば、クラスの生徒全員を返すようなAPIとしましょうか。
Serializer
ひとまず前回と同じでいきましょう。
class StudentSerializer(serializers.ModelSerializer):
"""Studentクラスのシリアライザ"""
class Meta:
model = Student
fields = '__all__'
view
class StudentListAPIView(APIView):
def get(self, request, class_no, *args, **kwargs):
"""Student一覧をclass_noで取得"""
# モデルのオブジェクト取得
queryset = Student.objects.filter(class_no=class_no)
# シリアライズ
serializer = StudentSerializer(instance=queryset, many=True)
# 返却
return Response(serializer.data, status.HTTP_200_OK)
url
path('<class_no>/', views.StudentListAPIView.as_view())
動かしてみました
http://127.0.0.1:8000/student/1
[{"student_id":"0001","class_no":"1","attendance_no":1,"name":"山田太郎","district_no":"1","comment":"1組1番"},{"student_id":"0002","class_no":"1","attendance_no":2,"name":"山田花子","district_no":"2","comment":"1組2番"},{"student_id":"0003","class_no":"1","attendance_no":3,"name":"山田3番","district_no":"4","comment":"1組3番"}]
できましたね!
重要なところはViewsのなかでシリアライズをするときにmany=True
のオプションをつけるところでしょうか。また、リストの場合はModelSerializerでなくてListSerializerでシリアライズすることも可能そうです。
ちなみに条件によるフィルタリングについてだいぶ調べたんですが、django-filter
というライブラリが良く見つかりました。今回、class_noで検索条件を書いていますが、クエリパラメータにより動的に検索条件を決めたいときとかはdjango-filter
を使うと良さそうですね。
http://127.0.0.1:8000/student/?attendance_no=2
こんな感じに書くと検索条件が動的に出席番号になるイメージ。
ListSerializerバージョン
一応やっておきます。更新系はこっち使っといたほうが良いらしい・・・?更新のときにまた調べようと思います。
Serializer
class StudentListSerializer(serializers.ListSerializer):
child = StudentSerializer()
view
シリアライズする部分が変わってます。
# シリアライズ
- serializer = StudentSerializer(instance=queryset, many=True)
+ serializer = StudentListSerializer(instance=queryset)
実行
[{"student_id":"0001","class_no":"1","attendance_no":1,"name":"山田太郎","district_no":"1","comment":"1組1番"},{"student_id":"0002","class_no":"1","attendance_no":2,"name":"山田花子","district_no":"2","comment":"1組2番"},{"student_id":"0003","class_no":"1","attendance_no":3,"name":"山田3番","district_no":"4","comment":"1組3番"}]
できました。
汎用APIViewを用いる
汎用APIViewを用いると、getのAPIがもっと簡単に記述できそうです。
APIの用途に合わせてクラスを継承したViewを作るともっとコード量が少なく書けそうです。
例えば一覧ではなく一件だけ取得するようなAPIを考えてみます。RetrieveAPIViewを使ってみましょう。
url
path('<pk>/retrieve/', views.StudentRetrieveAPIView.as_view())
view
from rest_framework import generics
from .models import Student
from .serializers import StudentSerializer
class StudentRetrieveAPIView(generics.RetrieveAPIView):
queryset = Student.objects.all()
serializer_class = StudentSerializer
実行
{"student_id":"0001","class_no":"1","attendance_no":1,"name":"山田太郎","district_no":"1","comment":"1組1番"}
できました。
えっこれだけ!?
viewの実装が意味わからないくらい少なくなってる・・・。
- from django.shortcuts import get_object_or_404
- from rest_framework import status
- from rest_framework.views import APIView
- from rest_framework.response import Response
+ from rest_framework import generics
from .models import Student
from .serializers import StudentSerializer
- class StudentRetrieveAPIView(APIView):
+ class StudentRetrieveAPIView(generics.RetrieveAPIView):
- def get(self, request, pk, *args, **kwargs):
- """Studentをpkで取得"""
- # モデルのオブジェクト取得
- instance = get_object_or_404(Student, pk=pk)
- # シリアライズ
- serializer = StudentSerializer(instance)
- # 返却
- return Response(serializer.data, status.HTTP_200_OK)
+ queryset = Student.objects.all()
+ serializer_class = StudentSerializer
コード量がすごく少なくなりましたね。。
RetrieveAPIViewでいろいろとやってくれてるって感じですね。pkでの取得も裏でやってくれていそうです。url.pyでpkと書いてあるところを勝手に読み込んでくれています。
RetrieveAPIViewで継承しているRetrieveModelMixin
、GenericAPIView
がいろいろやってくれていそうです。まぁ確かに取得してシリアライズするだけだから汎用的な処理に落とし込めるやろってことですね。
RetrieveAPIViewで複数条件検索
今度はpkではなくclass_no、attendance_noから検索をしてみます。
url
path('<class_no>/<int:attendance_no>/retrieve/', views.StudentRetrieveAPIView.as_view())
view
class StudentRetrieveAPIView(generics.RetrieveAPIView):
serializer_class = StudentSerializer
queryset = Student.objects.all()
def retrieve(self, request, class_no, attendance_no, *args, **kwargs):
instance = get_object_or_404(self.queryset, class_no=class_no, attendance_no=attendance_no)
serializer = StudentSerializer(instance)
return Response(serializer.data)
今回は、検索条件がpkではないので独自の実装が必要になります。
RetrieveViewのアクションメソッドであるretrieveをオーバーライドして実装しています。
(ハンドラメソッド・・・get、アクションメソッド・・・retrieve)
汎用APIViewを用いるときはクラスフィールドにserializer_class
とqueryset
という変数が必要になってくるようです。
実行
{"student_id":"0002","class_no":"1","attendance_no":2,"name":"山田花子","district_no":"2","comment":"1組2番"}
無事取得できました。
うーん・・・
なかなか難しい・・・というか理解がしづらい。
例えばqueryset = Student.objects.all()
のところ。最初に全件とってきてるけど、今回したいのってフィルタリングだから全件とかいらないんだけどって思って調べました。
→queryの遅延評価というものがあるようです。実際にORMでSQLが流れるのは、queryの値を使うときだけのよう。今度記事にしよっと。
あとは
model.object.all()とか、model.object.filter(xx=xx)で戻ってくるのはqueryset型で、それはserializeできない(リストのときにやるやつ)とか、
serializeするときはget_objectで取得(retrieveのとき)とか、いろいろ気にすることはありそうですね。
とりあえずgetはここまでにしようかな。
次はpostあたりをやろうと思います。