diadia

興味があることをやってみる。自分のメモを残しておきます。

エラー:AssertionError: You cannot call `.save()` on a serializer with invalid data.

AssertionError: You cannot call `.save()` on a serializer with invalid data.

上記のエラーが表示された。
エラーが出た状況:
インスタンスを生成することを目的にdjango rest apiでrequests.post()をする。するとdjango アプリケーションで上記のエラーが表示された。
詳しくは、csvファイルをcsv.DictReaderを使ってdict型に変換する。変換したものをjson.dumps()関数を使った戻り値をdataとし、requests.post(URL, data=data)を投げる。しかしながらエラー発生。

具体的なコード

import csv, sqlite3, json, requests
INPUT_FILE   = "/Users/*****/*****/myjson_test.csv"


fieldnames = ("id","title","length","datetime")
with open(INPUT_FILE,"r",encoding="utf-8") as f:
	reader = csv.DictReader(f, fieldnames)
	for row in reader:
		headers = {'content-type': 'application/json'}
		URL = "http://127.0.0.1:8000/api/"
		res = requests.post(URL, data=json.dumps(row), headers=headers)

エラーの原因を探る

REST apiの設計にエラー原因があるか?

適切にRest APIの設計はできているか。不適切だからエラーが発生しているのではないか?
この問に対して、django rest frameworkの設計によるエラーではないことが判明した。

test = {}
test["id"] = "1234567890123"
test["title"] = "test_title"
headers = {'content-type': 'application/json'}
URL = "http://127.0.0.1:8000/api/"

res = requests.post(URL, data=json.dumps(test), headers=headers)
print(res)

上記のコードが通り、django adminにおいてインスタンスが作成されたのを確認できた。したがって他が原因であることが判明した。

csvファイルをcsv.DictReader()で辞書型にするのに原因があるのか?

上記のデータtestは実際にdict型のデータをjsonのデータに変換している。しかしながらエラーが出たコードはcsvをdict化しているのであってdict型データではない。dict型データでないモノをjson化した際にエラーが発生の原因となってしまう可能性が考えられる。そこで直接的な原因検証ではないがtype()関数を使いデータ型を確認してみた。

test = {}
test["id"] = "1234567890123"
test["title"] = "test_title"
headers = {'content-type': 'application/json'}
URL = "http://127.0.0.1:8000/api/"
res = requests.post(URL, data=json.dumps(test), headers=headers)
print(type(test))
print(type(json.dumps(test))
#結果
<class 'dict'>
<class 'str'>

つづいてエラー原因のコードを検討する。

import csv, sqlite3, json, requests
INPUT_FILE   = "/Users/*****/*****/myjson_test.csv"

fieldnames = ("id","title","length","datetime")
with open(INPUT_FILE,"r",encoding="utf-8") as f:
	reader = csv.DictReader(f, fieldnames)
	for row in reader:
		headers = {'content-type': 'application/json'}
		URL = "http://127.0.0.1:8000/api/"
		res = requests.post(URL, data=json.dumps(row), headers=headers)
		print(type(row))
		print(type(json.dumps(row)))
# 結果
<class 'collections.OrderedDict'>
<class 'str'>

やはりdict()関数とcsv.DictReader()関数でデータ型は厳密には異なるようだ。しかしながらjson.dumps()の返り値はstr型なので適切にjson型のデータに変換したと暫定的にみなす。

jsonデータに空の値がある場合どのような挙動になるか?

REST apiにおいて受け渡されるJSONデータのキーに対する値が空である場合、適切にインスタンスが生成されるか検証していなかった。空の値の場合を検証する。

test = {}
test["id"] = "1234567890123"
test["title"] = "test_title"
test["length"] = ""    # 空の値を設置
headers = {'content-type': 'application/json'}
URL = "http://127.0.0.1:8000/api/"

res = requests.post(URL, data=json.dumps(test), headers=headers)
print(res)

上記のようにしたところエラーが発生した。

AssertionError: You cannot call `.save()` on a serializer with invalid data.

当初と同じエラーが出てきたので、これが原因だと推定できる。したがって値が空の場合についてどう対処するか考えていく。

jsonデータから空の値のデータは削除してrequests.postしたところインスタンスを生成することができた。対処法はここを参照。しかしながらできないものも出てきた。
結論から言うとmodelsのCharField max_lengthが120に対しそれ以上のものを登録しようとしていた時に同じエラーが起きた。これに対してはmax_lengthをさらに大きくしたところエラーをはかずすべてインスタンスを生成することができた。

 

まとめ:エラーAssertionError: You cannot call `.save()` on a serializer with invalid data.の原因

まずJSONのキーに対して値が空の場合にこのエラーが出てしまう。
さらに加えてCharFieldのmax_lengthを超えた文字数を登録しようとした場合に同じエラーが出てしまうことが分かった。

追記:その他の原因としてImgaeFieldにファイルパスだけ登録しようと、文字列を渡しても同じようにAssertionError: You cannot call `.save()` on a serializer with invalid data.が出る。