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


この記事は [ハンズラボ Advent Calendar 2021](https://qiita.com/advent-calendar/2021/handslab) 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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129313/4c4025b3-9bdb-eef4-44e9-3335c45b99e5.png)


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

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

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

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

<details>
<summary>サンプルコード</summary>
<div>


```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))
]

```

</div>

</details>


## 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]
[^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]
[^2]: そのため、サンプル上Serializerには何も記載していないにも関わらず、200文字以下のバリデーションがあります。

## 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](https://qiita.com/advent-calendar/2021/handslab) 明日の8日目は、 @shnskfjwr さんです！