この記事は ハンズラボ Advent Calendar 2021 7日目の記事です。
お疲れさまです。@naokiurです。
今年もこの季節がきましたね。担当させて頂きます!
Webアプリケーションフレームワーク、便利ですよね。
これまでいくつか、Django REST Framework(以下DRF)を使用してシステムを開発してまいりました。
Webアプリケーションフレームワークに限らず、
ライブラリ、既存のシステムがどのような動きをしているかを読み解いていくと、
力になるよ、Tomcatのソースコードとか読んで見ると良いよ
と、自分が業界に入りたてのころ、おすすめされた記憶があります。
Tomcatのソースコードは読めていませんが、
今回は「いい感じにやってくれる」DRFの、
Serializer
がどのように「いい感じにやってくれている」か、
以前少しコードリーディングしたので、その記録を残させていただきたいと思います。
どんな方のためになりそうか
- 「どんな動きをしているか、ざっと知りたい!」 という方
- DRFを触り始めた、あのころの自分
(勘違いやコードの誤読がありましたら、ご指摘いただけると幸いです。)
環境
名称 | バージョン |
---|---|
Python | 3.8.12 |
Django | 3.2.9 |
djangorestframework | 3.12.4 |
今回記録したこと概要
POSTリクエストを受領後のDRF Serializerの流れイメージです。
ざっと線とオブジェクトで記載しましたが、これが一番わかりやすいかもしれません。
データ持ち回りコードリーディング
今回、サンプルとして、DjangoとDjango REST Frameworkのチュートリアルを元に、
ModelViewSetとModelSerializerを活用した形で、
とても小さいAPIを作成しました。
その中の POSTリクエスト時の動き
を元に、
ModelSerializerの中身を記録していきます。
元のコードを見れば一目瞭然な内容が多いですが、
このイメージがコードを読む際の助けになれれば幸いです。
サンプルコード
from django.db import models
class Question(models.Model):
question_text = models.TextField(max_length=200)
pub_date = models.DateTimeField(verbose_name='date published')
from rest_framework import viewsets
from polls.models import Question
from polls.serializers import QuestionSerializer
class QuestionViewSet(viewsets.ModelViewSet):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
from rest_framework import serializers
from polls.models import Question
class QuestionSerializer(serializers.ModelSerializer):
class Meta:
model = Question
fields = '__all__'
from django.conf.urls import url
from django.urls import path, include
from drf_yasg import openapi
from drf_yasg.views import get_schema_view
from rest_framework import routers, permissions
from polls.views import QuestionViewSet
# drf-yasg
schema_view = get_schema_view(
openapi.Info(
title="Snippets API",
default_version='v1',
description="Test description",
terms_of_service="https://www.google.com/policies/terms/",
contact=openapi.Contact(email="contact@snippets.local"),
license=openapi.License(name="BSD License"),
),
public=True,
permission_classes=(permissions.AllowAny,)
)
router = routers.DefaultRouter()
router.register(r'questions', QuestionViewSet)
urlpatterns = [
# drf-yasg swagger
url(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
url(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
path('api/', include(router.urls))
]
Serializer
DRFのSerializerは、
QuerysetやModelのインスタンスのなどの複雑なデータを、
JSON, XML, その他色々なコンテンツタイプへ容易にレンダリングできるよう、
Pythonネイティブに変換する
と書かれています。(意訳)
https://www.django-rest-framework.org/api-guide/serializers/
それを実現するために用意されているのが以下のプロパティかなと考えています。
- initial_data
- fields
- validators
- validated_data
- errors
これらを活用し、受け取ったデータを変換し、バリデーションし、レンダリングして、
呼び出し元(ViewSet)へ返却しています。1
initial_data
このプロパティは、リクエストされたときのデータを保持するため
のプロパティだと考えています。
今回のサンプルで活用した ModelViewSet
クラスは、
各種*ModelMixin
を継承しています。
POSTリクエスト
されたときのふるまいは、CreateModelMixin
に記載されています。
この CreateModelMixin
クラスの中で、
リクエストボディを元に、Serializerをインスタンス化しています。
その際に、引数で渡されたリクエストボディを格納するプロパティが、 initial_data
です。
fields
基本的にSerializerは、保持する・操作するデータを、
当該Serializerのプロパティとして定義する、と認識しており、
それらを fields
と呼んでいます。
今回のサンプルで活用した ModelSerializer
は、 Meta
クラスに設定したModel定義から、
Serializerの fields
を定義することができます。
BaseSerializer#fields
で呼ばれる、
ModelSerializer#get_fields
の処理の中で、
Meta
クラスを取得し、fields
として定義している、と認識しております。
validators
Serializerは 正しい値
をレンダリングするために、
様々なバリデーションを定義・生成することができます。
それらを格納するプロパティが、 validators
です。
validators
というプロパティは、Serializer
クラスの継承元である Field
クラスに定義されています。
そのため、Serializerのvalidators
はSerializerで、
Serializerの各fields
のvalidators
は、各fields
で保持することができる、と認識しております。
Serializer
├── validators
├── feild_a
│ └── validators
├── feild_b
│ └── validators
└── feild_c
└── validators
というイメージです。
今回のサンプルで活用した ModelSerializer
は、 Meta
クラスに設定したModel定義を元に、
question_text
fieldのバリデーションを生成しています。2
validated_data, errors
これら定義されたvalidators
は、
Serializerのis_valid()
によって実行され、バリデーション結果を記録します。
バリデーション結果、エラーがなければ validated_data
にデータが保持されます。
名前の通りこのプロパティには、バリデーション済みのデータが格納されるのです。
エラーがあった場合は、エラー内容をプロパティに保持します。
それが errors
プロパティです。
レンダリング(呼び出し元に返却)する際、Serializerは validated_data
に保持したデータを活用します。
今回サンプルとして活用したViewSetの CreateModelMixin
クラスの中で、
Serializerの data
プロパティを活用してデータベースへの登録を行なっています。
この data
で、validated_data
を活用しています。
ライフサイクルイメージ
上記までで言葉で書いてしまいましたが、
Serializerには、データのライフサイクルがある、というイメージを持っています。
初期データ(initial_data)
↓
バリデーション済みデータ(validated_data)
↓
返却用データ(data)
そしてそれらが想定通り流れるよう、
is_valid()実行前に validated_dataを参照すると、AssertionErrorが発生する
など、
様々なアサーションで、DRFは気づかせてくれます。
最後に
ここまでお読み頂き、ありがとうございます。
いい感じにやってくれる
部分の本当に一部ですが、今回改めて読むことで、
理解が深まりました。
今回の内容を踏まえて、より良いコーディングができるようになっていきたいと思います。
ハンズラボ Advent Calendar 2021 明日の8日目は、 @shnskfjwr さんです!