Django REST Frameworkの中身を少し調べた。(serializers.CharField)


serializerでデータを更新できない!!

serializerを使ってデータを操作しようとしたところエラーになってしまい挙動がわからないことがあったので内部のコード眺めてみました。

サンプルのシリアライザ定義

以下、例です(変数名とか雰囲気で読んでください)

cat_serializers.py
class CatSerializer(serializers.ModelSerializer):
    name = serializers.CharField()
    birthday = serializers.DateField(
        format="%Y-%m-%d",
        required=False,
        input_formats=["%Y-%m-%d"])
    gendar_code = serializers.IntegerField(required=False)

    class Meta:
        model = Cats
        fields = (
            'name',
            'birthday',
            'gendar_code'
        )

実行コード

以下、事象発生させるコード。

sample
>>> serializer = CatSerializer(instance, data=params, partial=True)
>>> serializer.is_valid(raise_exception=True) # ここでエラー
>>> serializer.save()

詳しいエラー文言忘れてしまいましたが、Invalid data. String not valid.みたいなこと言われました。

Django REST Frameworkの中を見てみる

「あー…はいはい」と適当に見当つけながらいくつか思いつく方法をためしたのですがだめでした。
このままでは嵌りそうだったのでエラーになった項目のフィールドの処理を見ることにしました。

GitHubでSerializerのFieldタイプごとの処理を確認。
CharFieldの項目でエラーになっていたので確認。
753行目移行がCharFieldの処理。その中に以下のような場所が

fields.py(L795)
    def to_internal_value(self, data):
        # We're lenient with allowing basic numerics to be coerced into strings,
        # but other types should fail. Eg. unclear if booleans should represent as `true` or `True`,
        # and composites such as lists are likely user error.
        if isinstance(data, bool) or not isinstance(data, six.string_types + six.integer_types + (float,)):
            self.fail('invalid')
        value = six.text_type(data)
        return value.strip() if self.trim_whitespace else value

isinstanceでdataのデータ型見てたみたいですね。
sixってなんだ?とも思った場合はこちら。DjangoのUtilityモジュールでした。

内部処理わかったところで原因を見つける

その上で改めて実行していたコードのdata(上記sampleでいうところのprams)を見てみました。
そうすると以下のような感じに。

sample2
>>> print(dir(params))
{name: b'適当な文字列', birthday: '2019-03-06', gendar_code: 1}

「name(実際は違う変数だけど)がバイト列じゃん!こいつか!!」
ということで、このデータに対応するように修正したら動作しました。