diadia

興味があることをやってみる。自分のメモを残しておきます。

djangoにRESTAPI(DRF)を実装する まとめ

内容

  1. 公式ドキュメント
  2. DRFの基本的な知識
  3. DRFの関連記事

1. 公式ドキュメント

公式ドキュメント:Home - Django REST framework

2. DRFの基本的な知識

DRFAPIを扱うフレームワーク

djangoがwebフレームワークに対してdjango rest framework(DRF)はrestfulなapiを実装するためのフレームワークらしい。 これはCRUDが得意らしい。個人的にはDRFでエンドポイントを作成しアンドロイドアプリケーションを作成するために使っている。

DRFの主要構成単位

  1. Model
  2. リアライザ
  3. View
  4. URFconf

上記4点が主要な構成単位である。これにsettings.pyで諸所の設定項目を定めることでREST API機能を実装していくのがDRFの使い方である。

Modelについて

Modelを新規に作成するケース既存のModelを利用するケースが存在する。
Modelを新規に作成するケースは、スマホアプリを作成するためにプロジェクトを開始するパターンである。
この場合はレスポンスとしてHTMLファイルを返さず、全てJSONでレスポンスを返すことになる。

既存のModelを利用するケースは、ウェブブラウザユーザーとスマホユーザーを対象としたプロジェクトを作成する場合である。 この場合にはウェブブラウザで使用するモデルを利用する。

リアライザについて

リアライザは入力データのバリデーションを行う役割をもつformのような役割と、ModelインスタンスJSONに変更するまたはJSONからModelインスタンスに変更する役割がある。 具体的にはserializer.Serializerやserializer.ModelSerializer等を使うことになる。

Viewについて

ViewはJSON形式のデータが入ったリクエストオブジェクトを受け取り、API呼び出しに対応するアクションを実行して、JSON形式のレスポンスオブジェクトを作成する役割をもつ。

Viewではユーザーアクションに基づいてインスタンスとして生成したり、インスタンスを修正したり、削除する。 JSON形式のデータをユーザーに出力することも役割のひとつである。具体的には、request.data,Response()やrequest.authを使う場面が出てくる。

インストールと設定

参照:https://www.django-rest-framework.org/#installation

pip install djangorestframework
pip install markdown 
pip install django-filter

rest api機能を実装するためにはrest api専用のアプリを既存のwebapp内に追加で作成する手続きをとる。
主要単位がmodel, シリアライザ、view,URFconfなので既存のmodel等にコードを付け加えて実装するかと思っていた。これは誤りで新たなappにいろいろ書いていく形をとる。したがって以下のようなコマンド入力が必要となる。

python3.6 manage.py startapp api

またsettings.pyのINSTALLED_APPSのリストに"rest_framework"を追加する。

 

リアライザについて

リアライザをどうイメージするか
The first thing we need to get started on our Web API is to provide a way of serializing and deserializing the snippet instances into representations such as json.

上記の通り、あるモデル(ここではSnippetモデル)のインスタンをJSONのような表現形式に変えたり、逆にJSON形式の表現をあるモデルのインスタンスに変形させることを言うのだろう。つまりJSON 形式とモデルインスタンスの変換をシリアライザは担当するとイメージするべきだ。
参考:
https://www.django-rest-framework.org/tutorial/1-serialization/#creating-a-serializer-class

リアライザの種類について

確認しているシリアライザの種類は以下になる。

  • serializers.Serializer
  • serializers.HyperlinkedModelSerializer
  • serializers.ModelSerializer

参考:
https://www.django-rest-framework.org/tutorial/1-serialization/#creating-a-serializer-class
https://www.django-rest-framework.org/tutorial/quickstart/#serializers
https://www.django-rest-framework.org/tutorial/1-serialization/#using-modelserializers

チュートリアルのシリアライザの使い方を参照する
https://www.django-rest-framework.org/tutorial/1-serialization/#creating-a-serializer-class
    
from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES


class SnippetSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(required=False, allow_blank=True, max_length=100)
    code = serializers.CharField(style={'base_template': 'textarea.html'})
    linenos = serializers.BooleanField(required=False)
    language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
    style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')

    def create(self, validated_data):
        """
        Create and return a new `Snippet` instance, given the validated data.
        """
        return Snippet.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        Update and return an existing `Snippet` instance, given the validated data.
        """
        instance.title = validated_data.get('title', instance.title)
        instance.code = validated_data.get('code', instance.code)
        instance.linenos = validated_data.get('linenos', instance.linenos)
        instance.language = validated_data.get('language', instance.language)
        instance.style = validated_data.get('style', instance.style)
        instance.save()
        return instance
We can actually also save ourselves some time by using the ModelSerializer class, as we'll see later, but for now we'll keep our serializer definition explicit.

 

SnippetSerializerでcreateメソッドとupdateメソッドを定義している。これはform.save()でインスタンスを生成したり、更新したのと同様に、serializer.save()でvalidated_dataの内容をインスタンス化するメソッドのようだ。
forms.Formを使わずにforms.ModelFormを使うように、serializers.ModelSerializerを使えば楽ができる。ModelSerializerを使うメリットは以下のように記述してあった。

It's important to remember that ModelSerializer classes don't do anything particularly magical, they are simply a shortcut for creating serializer classes: An automatically determined set of fields. Simple default implementations for the create() and update() methods.

要するにあるモデルのフィールドを自動的に複製してくれるのがModelSerializerの1つ目のメリットであり、2つ目のメリットは自動的にcreateメソッドやupdateメソッドを実装してくれることだ。

#serializerの使い方

python manage.py shell

#ここではSnippetモデルを使うチュートリアルを見る
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser

#Snippetのインスタンスであるsnippetを生成する
snippet = Snippet(code='print("hello, world")\n')
snippet.save()


#インスタンスsnippetをSnippetSerializerに渡し、serializerインスタンスを生成する
serializer = SnippetSerializer(snippet)

#serializerインスタンスにはdataアトリビュートがあり、内容を確認できる。ちなみにデータ型はPython native datatypesであるらしい。
serializer.data
#結果: {'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}


#
content = JSONRenderer().render(serializer.data)
content
# b'{"id": 2, "title": "", "code": "print(\\"hello, world\\")\\n", "linenos": false, "language": "python", "style": "friendly"}'


#逆にJOSN形式のデータをインスタンス化(デシリアライズ化)させるには以下のようにする

import io

stream = io.BytesIO(content)
data = JSONParser().parse(stream)


serializer = SnippetSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# OrderedDict([('title', ''), ('code', 'print("hello, world")\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
serializer.save()
# 


通常のシリアライザはForeignKeyをidで表示する件

ForeignKeyを持つあるモデルをシリアライザにかけて表示すると、ForeignKeyはidで表示されてしまう。idを使う場合には良いかもしれないが、__str__で表示したい場合もあるだろう。
そのときにはStringRelatedFieldをシリアライザに登録することで解決できる。

class AlbumSerializer(serializers.ModelSerializer):
    tracks = serializers.StringRelatedField(many=True)

    class Meta:
        model = Album
        fields = ['album_name', 'artist', 'tracks']

参考:https://www.django-rest-framework.org/api-guide/relations/#stringrelatedfield

複数のモデル(serializer)をResponseに返す方法

参考:https://stackoverflow.com/questions/35485085/multiple-models-in-django-rest-framework

return Response({
    'cart': cart_serializer.data,
    'another': another_serializer.data,
    'yet_another_field': 'yet another value',
    })

Viewについて

参考:https://www.django-rest-framework.org/api-guide/views/

djangoと同様に、viewにはclass-based view(https://www.django-rest-framework.org/api-guide/views/#class-based-views)と、function-based view(https://www.django-rest-framework.org/api-guide/views/#function-based-views)が存在する。

viewはdjangoと同様にviews.pyで使うことになる。

viewの種類について

Viewの種類

  • rest_framework.views.APIView
  • rest_framework.generics.ListAPIView
  • rest_framework.generics.RetrieveAPIView
  • rest_framework.generics.ListCreateAPIView
  • rest_framework.generics.CreateAPIView
  • rest_framework.generics.UpdateAPIView
  • rest_framework.generics.DestroyAPIView
  • rest_framework.generics.RetrieveUpdateAPIView
  • rest_framework.generics.RetrieveDestroyAPIView
  • rest_framework.generics.RetrieveUpdateDestroyAPIView
  • rest_framework.viewsets.ModelViewSet
  • rest_framework.viewsets.ReadOnlyModelViewSet

参考:
https://www.django-rest-framework.org/api-guide/views/#class-based-views
https://www.django-rest-framework.org/api-guide/generic-views/#concrete-view-classes
https://www.django-rest-framework.org/api-guide/viewsets/#api-reference

イメージ

クライアント側とオブジェクトを格納するデータベース側という2視点が存在している。クライアント側では常にJSONという形式でデータを送り出したり、受け取ったりする。データベース側ではJSONというデータ形式では新たにインスタンスを作成することができないのでインスタンスを作成するためにデータの形を成型したり、またGETのようなデータベース内のオブジェクトを参照する場合にクライアントのためにJSON形式でデータを送り出す必要があることが想像できると思う。このデータ形式を変換する役割を担っているのがserislizerシリアライザのようだ。このシリアライザはserializers.pyに規定(ModelSerializerやListSerializerを継承)し、views.pyでこの規定したシリアライザを呼び込んで使うようだ。views.pyではViewのような動きを連想させるAPIViewなるものがある。APIViewはクラスベースのviewなのでgetやpostに合わせて挙動を設定する。getの場合はクエリセットしたオブジェクトをクライアントに提供する前にシリアライザを通してJSON化する。そしてクライアントにそれを渡す。postの場合はJSONデータを一度シリアライザを通すことでオブジェクトを作成するのに適したデータ形式に成型する。その結果をsaveメソッドを使いインスタンスを生成する流れをとる。

ForeignKeyをシリアライザで表現するには?

https://www.django-rest-framework.org/api-guide/relations/#nested-relationships

GerericForeignKeyをシリアライザで表現するには?

https://www.django-rest-framework.org/api-guide/relations/#generic-relationships

3. 関連記事

curlコマンドを使ってクライアントのリクエストを作成する

POSTメソッドに対するViewの動作確認を行う際にcurlコマンドを使うと、クライアント側で簡易的に動作確認環境を準備することができる。
curlコマンドについて - diadia

リクエストヘッダーからユーザーオブジェクトを取得する

DRF tokenからuserオブジェクトを取得する - diadia

CRUDのUPDATE(PATCH)の実装サンプル

DRF PATCHの実装方法 - diadia

django-rest-authでエラー"detail": "Authentication credentials were not provided."が出る

django-rest-authで"detail": "Authentication credentials were not provided."が返される時 - diadia

GerericForeignKeyをシリアライザで表現するには?

DRFでGeneric ForeignKey を扱う - diadia

ForeignKeyをシリアライザで表現するには?

DRFでForeign Keyの値を参照するやり方 - diadia

DRFとretrofitの関係(bodyの観点から)

DRFとretrofitの関係(Bodyに関して) - diadia