Djangoさん「UnicodeDecodeError: 'utf-8' codec can't decode byte 0xbb in position 10: invalid start byte」
formのFileFieldに複数ファイルをまとめたzipをアップロードして保存しようとするとこのエラーになった。
結論から言うと、テストの時に放り投げていたデータ形式が悪かったみたい。
ただ、通るようになってエラーを再現しようとするとなぜかクリアするので、エラーの再現が困難なのが意味わからない。めっちゃ困ってたのに・・・
解決策
まずエラーが出てたときは、
self.zip_error_test = File(open('tdd.zip'))
file_data = {'upload_train_file': uploadedFile }
form = DocumentForm(file_data)
form.is_valid()
端折りますがこんな感じでした。
こんなことするとdjangoさんが怒ります。
ちなみに1つのファイルを固めたZIPでも同じように怒られました。
下のように変更すると解決しました。
with open("tdd.zip", mode="rb") as f:
uploadedFile = InMemoryUploadedFile(f, "field", "tdd.zip", "application/x-zip-compressed", 100, None, content_type_extra={})
file_data = {'field': uploadedFile }
form = DocumentForm(request, data, file_data)
self.assertFalse(form.is_valid(), f"error:{form.errors}/複数ファイル入ったZIPがバリデーションされませんでした")
はい。
これで動きます。
やったね。
原因は何だったのか。
いろいろ考えました。
そもそもforms.py
のカスタムバリデーションに書いていたので、それが原因なのかなと思ってみたり、
cleaned_data
を持ってきてるのがダメなのかなと思ってto_python()をいじってみたり。
forms.py
ではファイルを保存できないのかな、とかもういろいろ考えてました。
ふと思い立ってテストではなく、実際にHTMLのフォームからアップロードしたらどうなるんだろうとおもってやってみた結果
通りました。
これはformで受け取ってるデータ違うんじゃねと思って、viewsとtestsで受け取っているデータの型を見比べてみました。
すると
def clean_field(self):
print(type(self.cleaned_data['field']))
# views > django.core.files.uploadedfile.InMemoryUploadedFile
# tests > django.forms.widgets.ClearableFileInput
わーお違ってるやーん
これのせいだったので、テストのほうのアップロードを先述のものに変更したわけです。
これにて一件落着です。
おかげでFormについて詳しくなれた気がします。
いわゆる完全に理解したってやつですね。かんぺきです。
まとめ
解決したあとにこの記事書こうと思って、再度エラーになるであろうコードを書くとなぜかクリア。
マジで謎。
上記で通るって書いているけど
with open("tdd.zip", mode="rb") as f:
file_data = {'field': f}
form = DocumentForm(request, data, file_data)
self.assertFalse(form.is_valid(), f"error:{form.errors}/複数ファイル入ったZIPがバリデーションされませんでした")
これでも通ります。何でですかね。通らなかったんですよ?
何ならmode="rb"
をなくしても通ります。なんでなんですかね。エラー出てたんですよ?本当です。しんじて。
もしかすると再起動かけるとまた動かなくなっちゃうかもと思って上のほうのコードにしています。
明示的にクラスを変えればさすがに問題ないでしょう。
ということでzipがなぜか保存できない現象の解決方法でした。
多分来週くらいの私が、この件で得たformの知識をまとめてくれます。
期待してます。