[DRF] APIViewを使った基本的なViewの書き方
DRFでAPIを作成するとき、普段はrest_framework.genericsのViewを継承して書くことが多く、これらを継承することでget, postなどのメソッドを個別に記述することが不要になります。
ですが、Viewにはどのようにgetやpostのメソッドを書く必要があるのかを知ってみたくなったので、それらの継承元であるAPIViewを継承し、Viewの書き方について調べてみました。
APIViewの継承
まずは、APIViewを継承したクラスを作成してみます。
クラスの中身はいったん書かないでおきます。
views.py
class FirstApiView(APIView):
pass
urls.py
urlpatterns = [
path('first/', FirstApiView.as_view()),
]
http://localhost:8000/basic-apiview/first/
getやpostのメソッドを定義していないため何も表示されませんが、ひとまずクラスの中身を書かなくても、ブラウザ上でAPIの画面が表示されることを確認できました。
get/postメソッドを追加する
先ほどのViewに、get/postメソッドを加えてみます。
下記のBookモデルを使って確認していきます。
models.py
class Book(models.Model):
title = models.CharField(max_length=50)
author = models.CharField(max_length=20)
def __str__(self):
return self.title
あらかじめ、楽天APIで'Django'を含む書籍を検索して表示された結果を2件登録しています。
Viewの記述は下記のようになります.
views.py
class FirstGetPostApiView(APIView):
def get(self, request, format=None):
response = Response(Book.objects.all().values())
return response
def post(self, request, format=None):
book = Book()
book.id = request.data['id']
book.title = request.data['title']
book.author = request.data['author']
book.save()
response = Response(Book.objects.all().values(), status=status.HTTP_201_CREATED)
return response
まずgetメソッドですが、Bookモデルに格納されたデータをすべて取得し、rest_framework.response.Response()に渡しています。
postメソッドでは、引数requestで登録するモデルのデータが渡されます。
Bookクラスのインスタンスを生成し、requestで渡された値を格納します。
インスタンスに対し、save()メソッドを呼び出すことでDBへの登録が完了します。
レスポンスには、新たに登録したデータを含むBookモデルの全データと、ステータスコードとして作成に成功したことを示す201を渡します。
実際には、requestに渡したデータをバリデーションしてからDBに登録する必要がありますが、今回は簡略化するために、正しいデータが渡されることを前提として省略しています。
それではAPIを呼び出してみます。
GETメソッドを呼び出した結果として、事前に登録してあった2件のデータが表示されています。
また、POSTメソッドを許可しているため、POSTする内容を入力する画面も表示されています。
それではPOSTできるかどうか確認してみます。
ContentにはJson形式でBookモデルの内容を記述します。
{
"id": 3,
"title": "実践Django Pythonによる本格Webアプリケーション開発",
"author": "芝田 将"
}
POSTボタンをクリックすると、結果が返ってきます。
post()に記述した通り、登録したid=3のデータを含む全データと、201のステータスコードが表示されました。
Serializerを使ったViewの記述
先ほどはpostメソッドでBook()のインスタンスを生成し、requestに渡された内容を一つ一つ取り出してデータを登録していきましたが、Serializerを使うことでもう少し簡単に記述できるようになります。
Serializerは、Pythonで記述したモデルの形式とHTTPのレスポンスの形式を相互に変換する役割を持っています。
まずはrest_framework.serializers.Serializerを継承し、Bookモデルのシリアライザーを記述してみます。
serializers.py
from .models import Book
class BookSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
title = serializers.CharField(required=True, max_length=50)
author = serializers.CharField(required=True, max_length=20)
def create(self, validated_data):
return Book.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.title = validated_data.get('title', instance.title)
instance.author = validated_data.get('author', instance.author)
instance.save()
return instance
まず、モデルを記述するときと同様に、フィールドを定義します。データの型はモデルと同じになるようにします。
そしてPOSTに対応するcreateメソッド、PUTに対応するupdateメソッドを記述します。
createメソッドにはバリデーションされたレスポンスが渡されるので、これをBookモデルのcreateメソッドに渡します。
updateメソッドでは変更対象のデータを持つinstance, 変更後の値を持つvalidated_dataが渡されるので、instanceのフィールドをvalidated_dataの値で書き換え、save()メソッドを呼び出して更新します。
このBookSerializerを使い、Viewを実装してみます。
まずはget/postメソッドを持つFirstSerializerApiViewを作成します。
views.py
class FirstSerializerApiView(APIView):
def get(self, request, format=None):
books = Book.objects.all()
serializer = BookSerializer(books, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = BookSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
getメソッドでは、まずBookモデルの全データを取得します。
このデータをBookSerializerに渡し、HTTPの形式に沿ったデータに変換します。
レスポンスにはserializer.dataを渡します。
postメソッドでは、BookSerializerにリクエストに渡されたBookモデルの新データを足します。
このシリアライザーのバリデーションに問題がなければ、リクエストされたデータを登録し、ステータスコード201を返します。
バリデーションに通らなかった場合、400エラーを返すようにします。
このViewをブラウザで呼び出した結果が下記になります。
![image.png]https://hotaru-tech.s3.ap-northeast-3.amazonaws.com/c11f5c42-2752-44fd-9b2c-2025a7678e61/4.png)
FirstGetPostApiViewと同じ表示がされました。
下記のデータをPOSTメソッドで登録してみます。
{
"id": 4,
"author":"チーム・カルポ",
"title":"Django4 Webアプリ開発 実装ハンドブック"
}
結果は下記のようになります。
FirstGetPostApiViewとは異なり、レスポンスに渡したのは新規登録するデータだけでしたので、id=4のデータだけが表示されました。
続いて、一つのデータを取り出すgetメソッドと、putメソッドも実装してみます。
これらを呼び出すにはBookモデルのidフィールドを指定する必要があります。
views.py
class DetailGetPutApiView(APIView):
def get(self, request, pk, format=None):
try:
book = Book.objects.get(pk=pk)
except Book.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
serializer = BookSerializer(book, many=False)
return Response(serializer.data)
def put(self, request, pk, format=None):
try:
book = Book.objects.get(pk=pk)
except Book.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
serializer = BookSerializer(book, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
idはそれぞれのメソッドのpk(primary key)に渡されます。
まずpkで渡されたidがBookモデルにあるかどうかをチェックし、無い場合にはNot Foundの404エラーを返します。
getメソッドでは、BookSerializerに指定したidのインスタンスを渡します。
FirstSerializerApiViewと異なるのは、many=Falseとしている点です。
manyはモデルデータのリストを返すのか、priimary keyを指定して一つのデータを返すのかを示しています。
Trueの時はリスト、Falseの時は単体のデータになります。デフォルトはFalseです。
updateメソッドでは、リクエストに渡された変更後のデータを渡します。これがBookSerializerに先ほど作成したupdateメソッドに渡され、validateされたデータであれば、データの更新を行います。
postメソッドと同様にバリデーションに通らなかった場合には400エラーを返します。
ブラウザでid=3のデータを確認してみます。
http://localhost:8000/basic-apiview/serializer/3/
id=3のデータが表示され、GET/PUTメソッドが有効になっていることを確認できます。
PUTメソッドで下記のデータに更新してみます。
{
"id": 3,
"title": "ジッセンジャンゴ パイソンニヨルホンカクウェブアプリケーションカイハツ",
"author": "シバタ マサシ"
}
データが更新されることが確認できました。
ModelSerializerを使ったViewの記述
先ほどのBookSerializerをrest_framework.serializers.ModelSerializerを継承して書き換えてみます。
ModelSerializerを使うことでSerializerをより簡単に書けるようになります。
具体的には下記のように記述します。
serializers.py
class BookModelSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ['id', 'title', 'author']
Metaクラスを定義し、その中に対象のモデルとそのフィールドを指定するだけで、BookSerializerに記述したような内容を処理してくれます。
このBookModelSerializerをViewから呼び出します。
views.py
class ModelSerializerApiView(APIView):
def get(self, request, pk, format=None):
try:
book = Book.objects.get(pk=pk)
except Book.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
serializer = BookModelSerializer(book)
return Response(serializer.data)
def put(self, request, pk, format=None):
try:
book = Book.objects.get(pk=pk)
except Book.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
serializer = BookModelSerializer(book, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Viewでの記述はBookSerializerと同じです。
ブラウザでの動作も同じになります。
まとめ
rest_framework.genericsやrest_framework.serializers.ModelSerializerを使うと、内部的にどのような動きになっているのかを気にしなくても簡単に実装できてしまうのですが、今回少しだけ深く知ることができたような気がします。
今後もDRFの動きについて深堀していけたらなと思います。