2021年7月22日木曜日

【和訳】Django Rest Framework チュートリアル1:シリアル化

【和訳】Django Rest Framework クイックスタート

【和訳】Django Rest Framework チュートリアル1:シリアル化

【和訳】Django Rest Framework チュートリアル2:リクエストとレスポンス

【和訳】Django Rest Framework チュートリアル3:クラスベースのビュー

【和訳】Django Rest Framework チュートリアル4:認証とアクセス許可

【和訳】Django Rest Framework チュートリアル5:リレーションとハイパーリンクされたAPI

【和訳】Django Rest Framework チュートリアル6:ビューセットとルーター 


チュートリアル1:シリアル化


前書き

本チュートリアルでは、WebAPIを強調表示する簡単なコードスニペット(テキスト装飾サービス)の作成について説明します。
その過程で、RESTフレームワークを構成するさまざまなコンポーネントを紹介していき、すべてがどのように組み合わさっているのか、あなたが包括的に理解できるようにします。

チュートリアルはかなり詳細なので、始める前にクッキーとお気に入りのビールを1杯用意するのがいいでしょう。
おおまかな概要を知りたい場合は、代わりに
クイックスタートご覧ください


:このチュートリアルのコードは、GitHubencode / rest-framework-tutorialリポジトリにあります。
完成した実装は、テスト用のサンドボックスバージョンとして、
ここから入手できます



新しい環境のセットアップ

まず初めにvenvを使用して新しい仮想環境を作成します
こうしておけば、今回作成するappのパッケージ構成が、あなたの既存のプロジェクトとは別になり、切り分けられた状態で保たれます。

python3 -m venv env
source env/bin/activate

仮想環境内にいるので、必要なパッケージをインストールします。

pip install django
pip install djangorestframework
pip install pygments  # ハイライト表示用にこのパッケージを使用します。

注:仮想環境を終了したい時は、deactivateと入力するだけです。
詳細については、venvのドキュメントを参照してください


入門

さて、コーディングを始める準備ができました。
まず、新規プロジェクト
tutorialを作成し、作成したtutorialフォルダに移動します。

cd ~
django-admin startproject tutorial
cd tutorial

次に、snippetsアプリを作成します。

python manage.py startapp snippets

続いて、tutorial/settings.pyファイルのINSTALLED_APPSに、snippetsアプリとrest_frameworkアプリを追加します。
以下のように編集しましょう

INSTALLED_APPS = [
    ...
    'rest_framework',
    'snippets.apps.SnippetsConfig',
]

これで準備ができました。


使用するモデルの作成

このチュートリアルでは、コードスニペットを格納するために使用するシンプルSnippetモデルの作成から始めます。
snippets/models.pyファイルを編集していきましょう
 注:コメントは優れたプログラミング手法の一つです。このチュートリアルのリポジトリバージョン中にはコメントが掲載されていますが、ここではコード自体に焦点を当てるため、    省略しています。

from django.db import models
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles

LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
STYLE_CHOICES = sorted([(item, item) for item in get_all_styles()])


class Snippet(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    title = models.CharField(max_length=100, blank=True, default='')
    code = models.TextField()
    linenos = models.BooleanField(default=False)
    language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
    style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)

    class Meta:
        ordering = ['created']

また、スニペットモデル用にmigrateを実行し、データベースを同期します。

python manage.py makemigrations snippets
python manage.py migrate


Serializerクラスの作成

Web APIを使い始めるために最初に必要なことは、スニペットインスタンスをjsonなどの表現にシリアル化および逆シリアル化する方法を提供することです

 ●シリアル化:スニペットインスタンス→json

 ●逆シリアル化:スニペットインスタンス←json

これは、Djangoのフォームと非常によく似た動作をするシリアライザーを宣言すれば実現できます。snippetsディレクトリにserializers.pyファイルを作成し以下を追加します。

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):
        """
        検証済みデータを渡すと、`Snippet` インスタンスを新規作成して返す。
        """
        return Snippet.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        検証済みデータを与えると、既存の `Snippet` インスタンスを更新して返す。
        """
        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

シリアライザークラスの最初のプロパティの部分は、シリアル化/逆シリアル化されるフィールドを定義します。
create()メソッド、update()メソッドは、serializer.save()を呼び出した時に、インスタンスが作成されたり、更新される手順を定義します。

シリアライザークラスは、DjangoのFormクラスに非常に似ており、requiredmax_lengthdefaultのように、各フィールドに対する検証フラグを備えています。

これらのフィールドフラグは、HTMLにレンダリングする場合など、特定の状況で、シリアライザーの表示方法を制御することもできます。
上記の
{'base_template': 'textarea.html'}フラグは、DjangoFormクラスでwidget=widgets.Textarea使用するのと同じです。これは、チュートリアルの後半で説明するように、ブラウザで閲覧可能なAPIの表示方法を制御する場合に特に役立ちます。

後で説明するようにModelSerializerクラスを使用することで、時間を大幅に節約することもできますが、今のところは、あえてシリアライザーを明示的に定義しておきます。


シリアライザーの操作

先に進む前に、新しいSerializerクラスの使い方について理解しておきます。
Djangoシェルを起動しましょう。

python manage.py shell

さて、モジュールのインポートをいくつか済ませて、コードスニペットをいくつか作成しましょう。

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser

snippet = Snippet(code='foo = "bar"\n')
snippet.save()

snippet = Snippet(code='print("hello, world")\n')
snippet.save()

これで、操作できるスニペットインスタンスが2つあります。
これらのインスタンスのうち1つをシリアル化する方法を見てみましょう。

serializer = SnippetSerializer(snippet)
serializer.data
# {'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}

まず、モデルインスタンス(snippet)をPythonネイティブデータ型に変換しました。
 ●Serializer(MODEL_INSTANCE):

  snippet(モデルインスタンス)→Pythonネイティブデータ型(ディクショナリ)

そして、シリアル化プロセスを完了するために、データをjsonレンダリングします
 ●JsonRenderer().render(TARGET_DICT):

  Pythonネイティブデータ型(ディクショナリ)→JSON

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

デシリアライズも同様です。
まず、ストリームをPythonネイティブデータ型(ディクショナリ)に解析します。
 ●JSONParser().parse(io.BytesIO(TARGET_JSON)):

  JSON→Pythonネイティブデータ型(ディクショナリ)

import io

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

...次に、これらのネイティブデータ型を、データを含んだオブジェクトインスタンスに復元します。

 ●Serializer(data=TARGET_DICT):

  Pythonネイティブデータ型(ディクショナリ)→モデルインスタンス(snippet)

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()
# <Snippet: Snippet object>

APIがフォームの操作にどれほど似ているかに注目してください。
シリアライザーを使用するビューを書き始めると、類似性がさらに明らかになるはずです。

モデルインスタンスの代わりにクエリセットをシリアル化することもできます。これを行うmany=Trueには、シリアライザー引数にフラグを追加するだけです。(複数モデルインスタンスの一括シリアライズ)

serializer = SnippetSerializer(Snippet.objects.all(), many=True)
serializer.data
# [OrderedDict([('id', 1), ('title', ''), ('code', 'foo = "bar"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 2), ('title', ''), ('code', 'print("hello, world")\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 3), ('title', ''), ('code', 'print("hello, world")'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])]


ModelSerializersの使用

私たちのSnippetSerializerクラスには、Snippetモデルに含まれている多くの情報が重複しています。
コードをもう少し簡潔にできれば素晴らしいですね。

DjangoがFormクラスとModelFormクラスの両方を提供するのと同じように、RESTフレームワークにはSerializerクラスとModelSerializerクラスの両方を提供しています

ModelSerializerクラスを使用してシリアライザーをリファクタリングする方法を見てみましょう
snippets/serializers.pyファイルを再度開き、SnippetSerializerクラスを次のように置き換えます。

class SnippetSerializer(serializers.ModelSerializer):
    class Meta:
        model = Snippet
        fields = ['id', 'title', 'code', 'linenos', 'language', 'style']

また、シリアライザーが持つ優れた特性の1つは、シリアライザーインスタンスの表現をプリントすることで、そのインスタンスのすべてのフィールドを検査できることです。
Djangoシェルを開き、
python manage.py shellで以下のコードを試してください。

from snippets.serializers import SnippetSerializer
serializer = SnippetSerializer()
print(repr(serializer))
# SnippetSerializer():
#    id = IntegerField(label='ID', read_only=True)
#    title = CharField(allow_blank=True, max_length=100, required=False)
#    code = CharField(style={'base_template': 'textarea.html'})
#    linenos = BooleanField(required=False)
#    language = ChoiceField(choices=[('Clipper', 'FoxPro'), ('Cucumber', 'Gherkin'), ('RobotFramework', 'RobotFramework'), ('abap', 'ABAP'), ('ada', 'Ada')...
#    style = ChoiceField(choices=[('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('colorful', 'colorful')...

ModelSerializerクラスは魔法のようなことは特に何もしないことを覚えておくことが重要です。
ModelSerializerクラスはシリアライザークラスを作成するためのショートカットにすぎません。

  • 自動的に決定されたフィールドのセット。
  • create()およびupdate()メソッドの単純なデフォルトの実装


シリアライザーを使用して通常のDjangoビューを作成する

新しいSerializerクラスを使用していくつかのAPIビューを作成する方法を見てみましょう。現時点では、RESTフレームワークの他の機能は使用せず、ビューを通常のDjangoビューとして記述します。

snippets/views.pyファイルを編集し、以下を追加します。

from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.parsers import JSONParser
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer

APIのルートは、既存のすべてのスニペットの一覧表示、またはスニペットの新規作成をサポートするビューになります。

@csrf_exempt
def snippet_list(request):
    """
    全コードスニペットの一覧表示 と 新規作成(ListとCreate)
    """
    if request.method == 'GET':
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return JsonResponse(serializer.data, safe=False)

    elif request.method == 'POST':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=201)
        return JsonResponse(serializer.errors, status=400)

CSRFトークンを持たないクライアントからこのビューにPOSTできるようにするため、ビューをcsrf_exemptとしてマークする必要があることに注意してください。

これは通常あなたが実行したいことではないでしょう、そして、RESTフレームワークビューは実際にはこれよりも賢明な動作を行いますが、今は私たちの目的のためにあえてこうしてます。

また、個々のスニペットに対応し、スニペットを取得、更新、または削除するために使用できるビューも必要です。

@csrf_exempt
def snippet_detail(request, pk):
    """
    個別コードスニペットの表示、更新、削除
    """
    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoesNotExist:
        return HttpResponse(status=404)

    if request.method == 'GET':
        serializer = SnippetSerializer(snippet)
        return JsonResponse(serializer.data)

    elif request.method == 'PUT':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(snippet, data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data)
        return JsonResponse(serializer.errors, status=400)

    elif request.method == 'DELETE':
        snippet.delete()
        return HttpResponse(status=204)

最後に、これらのビューを接続する必要があります。snippets/urls.pyファイルを作成します。

from django.urls import path
from snippets import views

urlpatterns = [
    path('snippets/', views.snippet_list),
    path('snippets/<int:pk>/', views.snippet_detail),
]

またスニペットアプリのURLを含めるために、tutorial/urls.pyファイルにルートurlconfを接続する必要があります。

from django.urls import path, include

urlpatterns = [
    path('', include('snippets.urls')),
]

現在、適切な処理を施していないエッジケース(問題がある場合)がいくつかあることには注意する必要があります。例えば、不正なjson形式でデータを送信した場合、やビューの処理が未定義のメソッドを使用したリクエストが行われた場合、500の「サーバーエラー」応答が返されます。それでも一応、動作はします。


WebAPIでの最初の試みのテスト

これで、スニペットを提供するサンプルサーバーを起動できます。

シェルを終了します

quit()

そしてDjangoの開発サーバーを起動します。

python manage.py runserver

Validating models...

0 errors found
Django version 1.11, using settings 'tutorial.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

別のターミナルウィンドウで、サーバーをテストできます。

curlまたはhttpieを使用してAPIをテストできますHttpieは、Pythonで記述されたユーザーフレンドリーなhttpクライアントです。それをインストールしましょう。

pipを使用してhttpieをインストールできます。

pip install httpie

最後に、すべてのスニペットのリストを取得できます。

http http://127.0.0.1:8000/snippets/

HTTP/1.1 200 OK
...
[
  {
    "id": 1,
    "title": "",
    "code": "foo = \"bar\"\n",
    "linenos": false,
    "language": "python",
    "style": "friendly"
  },
  {
    "id": 2,
    "title": "",
    "code": "print(\"hello, world\")\n",
    "linenos": false,
    "language": "python",
    "style": "friendly"
  }
]

または、IDを参照して特定のスニペットを取得できます。

http http://127.0.0.1:8000/snippets/2/

HTTP/1.1 200 OK
...
{
  "id": 2,
  "title": "",
  "code": "print(\"hello, world\")\n",
  "linenos": false,
  "language": "python",
  "style": "friendly"
}

同様に、WebブラウザでこれらのURLにアクセスすると、同じjsonを表示できます。


これからどうするか?

これまでのところ順調に進んでいます。
DjangoのFormsAPIと非常によく似たシリアル化APIと、いくつかの通常のDjangoビューがあります。

私たちのAPIビューは、jsonレスポンスを提供する以外に、現時点では特に特別なことは何もしていません
また、クリーンアップしたいエラー処理のエッジケースがいくつかありますが、このWebAPIは動作しています。

チュートリアルのパート2でこれらの問題を改善する方法を説明します

0 件のコメント:

コメントを投稿

【和訳】Django Rest Framework 目次

目次 【和訳】Django Rest Framework クイックスタート 【和訳】Django Rest Framework チュートリアル1:シリアル化 【和訳】Django Rest Framework チュートリアル2:リクエストとレスポンス 【和訳】Django Res...