9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ハンズラボAdvent Calendar 2021

Day 7

Django REST FrameworkのSerializerが「いい感じにやってくれている」内部におけるデータの持ち回り方法を読む

Last updated at Posted at 2021-12-06

この記事は ハンズラボ 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の流れイメージです。
ざっと線とオブジェクトで記載しましたが、これが一番わかりやすいかもしれません。

image.png

データ持ち回りコードリーディング

今回、サンプルとして、DjangoとDjango REST Frameworkのチュートリアルを元に、
ModelViewSetとModelSerializerを活用した形で、
とても小さいAPIを作成しました。

その中の POSTリクエスト時の動き を元に、
ModelSerializerの中身を記録していきます。

元のコードを見れば一目瞭然な内容が多いですが、
このイメージがコードを読む際の助けになれれば幸いです。

サンプルコード
models.py
from django.db import models


class Question(models.Model):
    question_text = models.TextField(max_length=200)
    pub_date = models.DateTimeField(verbose_name='date published')

views.py
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

serializers.py
from rest_framework import serializers

from polls.models import Question


class QuestionSerializer(serializers.ModelSerializer):
    class Meta:
        model = Question
        fields = '__all__'

urls.py
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の各fieldsvalidatorsは、各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 さんです!

  1. 昔の自分は、ここがぜんぜんイメージついていませんでした。

  2. そのため、サンプル上Serializerには何も記載していないにも関わらず、200文字以下のバリデーションがあります。

9
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?