Django Serializerの中身

DjangoのSerializerでいつ何がどう呼ばれてどのmethodをどう実装すればよいかがわからないことがあります。
[公式ドキュメント](https://www.django-rest-framework.org/api-guide/serializers/)を読んでもいまいちどう実装すればいいかわかないという人(自分も具体的な使い方が想像できませんでした。)も多いと思うのでよく使う以下の3つのメソッド/プロパティについて中のModelSerializerクラスのソースを実際に読みながらまとめてみました。
- save
- is_valid
- data


## serializer.save()
まずはObject作成時や更新時に呼ぶメソッドの解説をします。
一部省略していますが、saveは以下のように実装されています。

```serializers.py
    def save(self, **kwargs):
        # 一部省略
        # ①
        assert hasattr(self, '_errors'), (
            'You must call `.is_valid()` before calling `.save()`.'
        )

        assert not self.errors, (
            'You cannot call `.save()` on a serializer with invalid data.'
        )

        # Guard against incorrect use of `serializer.save(commit=False)`
        assert 'commit' not in kwargs, (
            "'commit' is not a valid keyword argument to the 'save()' method. "
            "If you need to access data before committing to the database then "
            "inspect 'serializer.validated_data' instead. "
            "You can also pass additional keyword arguments to 'save()' if you "
            "need to set extra attributes on the saved model instance. "
            "For example: 'serializer.save(owner=request.user)'.'"
        )

        assert not hasattr(self, '_data'), (
            "You cannot call `.save()` after accessing `serializer.data`."
            "If you need to access data before committing to the database then "
            "inspect 'serializer.validated_data' instead. "
        )

        # ②
        validated_data = dict(
            list(self.validated_data.items()) + list(kwargs.items())
        )
        
        # ③
        if self.instance is not None:
            self.instance = self.update(self.instance, validated_data)
            assert self.instance is not None, (
                '`update()` did not return an object instance.'
            )
        else:
            self.instance = self.create(validated_data)
            assert self.instance is not None, (
                '`create()` did not return an object instance.'
            )

        return self.instance

```
### ①の解説
①では属性のチェックをしています。
上から順に、`assert hasattr(self, '_errors')...`ではインスタンスに`_errors`属性が生成されているかをチェックしています。
これは後で確認しますが、`is_valid`を実行すると作成されます。

続いて`assert not self.errors...`では、errorsが空であるかをチェックしています。これも`is_valid`で生成されます。
errorsがある場合はそこでエラーとなります。

3つ目の`assert 'commit' not in kwargs...`では`commit`というキーで引数を渡していないかをチェックしています。
commitというkeyは利用できないようですね。

4つ目の`assert not hasattr(self, '_data')...`は、`_data`という属性を持っていないかをチェックしています。
これも後で確認しますが、`data`プロパティを参照したときに生成されるオブジェクトです。
保存前にdataプロパティを参照することはできません。

### ②の解説
引数で渡した`kwargs`と`validated_data`を結合して新たな`validated_data`を生成しています。
`kwargs`は引数で渡したものそのものなので、そのままです。
では`validated_data`はどこで生成されてるかというと、これも`is_valid`で生成される`_validated_data`を参照するプロパティです。

```serializers.py
    @property
    def validated_data(self):
        if not hasattr(self, '_validated_data'):
            msg = 'You must call `.is_valid()` before accessing `.validated_data`.'
            raise AssertionError(msg)
        return self._validated_data
```

### ③の解説
instanceがあるかないかで`update`か`create`を呼ぶかが切り替わります。
instanceが生成されるかどうかは初期化時に引数を渡すかどうかで決まります。
以下が`__init__`の中身です。

```serializers.py
    def __init__(self, instance=None, data=empty, **kwargs):
        self.instance = instance
        if data is not empty:
            self.initial_data = data
        self.partial = kwargs.pop('partial', False)
        self._context = kwargs.pop('context', {})
        kwargs.pop('many', None)
        super(BaseSerializer, self).__init__(**kwargs)
```
`Serializer`クラスではこの`update`や`create`はabstractmethodでオーバーライドせずに呼び出した場合、`NotImplementedError`となりますが
`ModelSerializer`クラスでは`Meta`クラスよりデフォルトでupdateやcreateが実装されています。

## serializer.is_valid()
続いて先程の`save`でも多く登場した`is_valid`を解説します。
ソースは以下のようになっています。

```serializers.py
    def is_valid(self, raise_exception=False):
        # 省略
        # ①
        assert hasattr(self, 'initial_data'), (
            'Cannot call `.is_valid()` as no `data=` keyword argument was '
            'passed when instantiating the serializer instance.'
        )

        # ②
        if not hasattr(self, '_validated_data'):
            try:
                self._validated_data = self.run_validation(self.initial_data)
            except ValidationError as exc:
                self._validated_data = {}
                self._errors = exc.detail
            else:
                self._errors = {}

        # ③
        if self._errors and raise_exception:
            raise ValidationError(self.errors)

        return not bool(self._errors)
```
### ①の解説
`assert hasattr(self, 'initial_data')...`の箇所ですが、これは先程の`__init__`で確認できます。`data=empty`でdefaultの値を入れ
`if data is not empty:`で判定しているのでインスタンス作成時に`data`を引数で渡していない場合にエラーになります。

### ②の解説
`if not hasattr(self, '_validated_data'):`はすでに`is_valid`が呼ばれているかどうかの判定です。2回目以降が呼ばれても処理結果は1回目のものを返すだけです。
ここの処理自体は、エラーがあれば、`self._errors`を詰めるということをしているだけですが、
その前の`run_validation`で何をしているかを見てみます。

```serializers.py
    def run_validation(self, data=empty):
        """
        We override the default `run_validation`, because the validation
        performed by validators and the `.validate()` method should
        be coerced into an error dictionary with a 'non_fields_error' key.
        """
        (is_empty_value, data) = self.validate_empty_values(data)
        if is_empty_value:
            return data

        value = self.to_internal_value(data)
        try:
            self.run_validators(value)
            value = self.validate(value)
            assert value is not None, '.validate() should return the validated data'
        except (ValidationError, DjangoValidationError) as exc:
            raise ValidationError(detail=as_serializer_error(exc))

        return value
```
いろいろ処理していますが、最終的な戻り値は、`value = self.validate(value)`です。
これが最終的に`self._validation_data`に代入されます。
`validate`はデフォルトでは引数をそのままリターンするだけのメソッドです。

```serializers.py
    def validate(self, attrs):
        return attrs
```
`validate`をオーバーライドすることで独自のvalidationとvalidate_dataの生成が可能になります。

## serializer.data
dataの中身は以下のようになっています。

```serializers.py
    @property
    def data(self):
        ret = super(Serializer, self).data
        return ReturnDict(ret, serializer=self)
```
スーパークラスの`Serializer`クラスを参照しているのでそちらを見ると

```serializers.py
    @property
    def data(self):
        # ①
        if hasattr(self, 'initial_data') and not hasattr(self, '_validated_data'):
            msg = (
                'When a serializer is passed a `data` keyword argument you '
                'must call `.is_valid()` before attempting to access the '
                'serialized `.data` representation.\n'
                'You should either call `.is_valid()` first, '
                'or access `.initial_data` instead.'
            )
            raise AssertionError(msg)
        #  ②
        if not hasattr(self, '_data'):
            if self.instance is not None and not getattr(self, '_errors', None):
                self._data = self.to_representation(self.instance)
            elif hasattr(self, '_validated_data') and not getattr(self, '_errors', None):
                self._data = self.to_representation(self.validated_data)
            else:
                self._data = self.get_initial()
        return self._data
```

### ①の解説
`initial_dataが含まれる`、かつ`_validated_dataが含まれない`ということですが
`initial_data`は初期化時に、`_validated_data`は`ModelSerializer`では`is_valid`を読んだときに生成されるデータでした。
つまりこのif文を別の言い方にすると**初期データを渡した場合は、is_validを呼ばないとdataプロパティにはアクセスできない**ということになります。
実際のエラー文言も翻訳すると
```
シリアライザーにデータキーワード引数が渡されると、シリアル化された `.data`表現にアクセスする前に` .is_valid（） `を呼び出す必要があります。
最初に `.is_valid（）`を呼び出すか、代わりに `.initial_data`にアクセスする必要があります。
```
**initial_data**ならアクセスしてもいいということです。

### ②の解説
一番外側ののif文は、初回呼ばれたときのみ`_data`を生成するロジックを呼ぶようにしている分岐ですね。2回目以降は、初回に生成された`_data`を参照するようになっています。

内側の3つの分岐も見ていきましょう。
`if self.instance is not None and not getattr(self, '_errors', None):`の分岐は、`instance`があり、`_errors`がない場合に実行されます。
`self.instance is not None`を満たすためには、初期化時に`instance`を引数で渡すか、`save`メソッドを実行する必要があります。
`not getattr(self, '_errors', None)`は、`is_valid`でエラーが発生していない、あるいは`is_valid`が呼ばれていないことになります。 ただ`is_valid`が呼ばれていない場合は、そもそも①の分岐で弾かれるので`is_valid`でエラーが発生していないという条件だけが残ります。
このinstanceを引数に`self.to_representation(self.instance)`の戻り値を最終的にリターンします。

次の分岐`elif hasattr(self, '_validated_data') and not getattr(self, '_errors', None):`は後半部分は先ほどと同様で`is_valid`でエラーがないことが条件になります。
1つ目の条件の逆も条件に加わるので、`self.instance is None`という異なります。`instance`は初期化時と`save`時に生成・更新されるので初期化時に`instance`を引数に渡さず、`save`を呼んでいない場合にこの分岐に入ります。
この`instance`を引数に`self.to_representation(self.instance)`の戻り値を最終的にリターンします。

最後の`else`ですが、これはシンプルに`is_valiud`でエラーが詰められた場合に通ります。

つまり`data`プロパティを呼ぶには、`is_valid`を呼んでいる必要があり、
エラーがないかつ、初期化時に`self.instance`に詰められる第一引数を与えた場合、または`save`メソッドを呼んだ場合は、1つ目の分岐に、
エラーがないかつ、初期化時に`self.instance`に詰められる第一引数を与えず、`save`メソッドを呼んでいない場合は、2つ目の分岐に、
`is_valid`でエラーが発生していた場合には、3つ目の分岐に入ることになります。

さて1つ目の分岐と2つ目の分岐で共通して呼ばれている`self.to_representation`を見てみましょう。

```serializers.py
    def to_representation(self, instance):
        """
        Object instance -> Dict of primitive datatypes.
        """
        ret = OrderedDict()
        fields = self._readable_fields

        for field in fields:
            try:
                attribute = field.get_attribute(instance)
            except SkipField:
                continue

            # We skip `to_representation` for `None` values so that fields do
            # not have to explicitly deal with that case.
            #
            # For related fields with `use_pk_only_optimization` we need to
            # resolve the pk value.
            check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute
            if check_for_none is None:
                ret[field.field_name] = None
            else:
                ret[field.field_name] = field.to_representation(attribute)

        return ret
```
`self._readable_fields`は`Meta`で指定した`fields`で`extra_kwargs`に`'write_only': True`としていないものが取得入ってきます。
以下の例だと`name`、`id`は`data`で取得できるが`password`は取得できなくなります。

```user_serializer.py
class UserSerializer(ModelSerializer):

    class Meta:
        model = User
        fields = [
            'id',
            'name',
            'password'
        ]
        extra_kwargs = {'password': {'write_only': True}} # この場合to_repsentationの戻り値にpasswordは入っていこない

```

```shell
>>> user = User.objects.first()
>>> serialzier = UserSerializer(user, {'name': 'テスト', 'password': '1234password'})
>>> serialzier.data
error: You should either call `.is_valid()` first, or access `.initial_data` instead.
>>> serialzier.is_valid()
>>> serialzier.save()
>>> serialzier.data
{'id': 1, 'name' : 'テスト'}
```

## まとめ
処理の順序と実装(オーバーライド)可能なメソッドを表にまとめてみました。

|  method 項目 | 役割 | 条件 | オーバーライドすることでカスタマイズ可能なmehtod |
| :---: | :--- | :--- | :--- |
|  is_valid | データの整合性をチェックする | - | - validate<br/>&emsp;&emsp;- 引数: to_internal_valueの戻り値<br/>&emsp;&emsp;- 戻り値: validated_dataに詰められる、create, udpateの引数<br/>- to_internal_value<br/>&emsp;&emsp;- 引数: 初期化時にdata引数<br/>&emsp;&emsp;- 戻り値:  内部で扱うように整形、validateメソッド等の引数 |
|  save | データを保存する | is_validが呼ばれており、エラーがない<br/>初期化時にinstance引数を渡すとupdate<br/>渡さないとcreateが呼ばれる | - create<br/>&emsp;&emsp;- 引数: validated_data<br/>&emsp;&emsp;- 戻り値: 新しく生成したインスタンス<br/>- update:<br/>&emsp;&emsp;- 引数: validated_data<br/>    &emsp;&emsp;- 戻り値: 新しく生成したインスタンス |
|  data | データの参照、表現 | is_validが呼ばれている | - to_representaion<br/>&emsp;&emsp;- 引数: createまたはupdateの戻り値<br/>&emsp;&emsp;- 戻り値:  データ参照用のDict |