diadia

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

djangoにRESTAPIを実装する メモ

DRFAPIを扱うフレームワーク

djangoがwebフレームワークに対してdjango rest frameworkはrest apiを実装するためのフレームワークらしい。
これはCRUDが得意らしく、webアプリケーションのコンテンツを作るのに役立ちそうだ。
公式ドキュメント:https://www.django-rest-framework.org/

DRFの主要構成単位

1.model, 2.シリアライザ, 3.view, 4.URFconfが主要な構成単位だ。これに加えてsettings.pyで諸所の設定項目を定めることでREST API機能を実装していくイメージとなる。

modelはdjangoのアプリケーションのmodelおそのまま使う。したがって新しくmodels.pyを作成する必要はない。しかしdjango rest apiのみでプロジェクトを作成する場合にはmodels.pyを作成する必要がある。

リアライザは、入力データのバリデーションを行う役割をもつ。

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

インストールと設定

参照: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

今困っていること

一応DRFをテスト的に実装した。GETに関してはうまく動作している。しかしながらPOSTの動作確認ができない。なぜならrequestsでJSONデータを投げないといけないがそのやり方がわからないからだ。csvからjson.dumpすればよいのか ?