メモ〜フォームのテストの中で画像を扱う方法


今回のお題

今回はdjangoのunittestにおいて、フォームに画像ファイルをアップロードする方法をまとめます。

私自身仕組みまではわかっていない面が多く、解説書というよりは手順書の側面が強くなってしまいますがご了承ください。

目次

  • 解説に用いるフォーム
  • 前提知識〜ImageFieldがあるフォームの引数
  • SimpleUploadFileクラス
  • テスト例

解説に用いるフォーム

models.py
class Menu(models.Model):
  name = models.CharField(max_langth=20)
  price = models.IntegerField()
  image = models.ImageField(upload_to="images/")
forms.py
class MenuForm(ModelForm):
  class Meta:
    fields = "__all__"

前提知識〜ImageFieldがあるフォームの引数

本題に入る前に、フォームの引数について話しておきます。

テスト内で(あるいはそれ以外で)フォームのインスタンスを作成する場合には、

form = MenuForm({
  "name": "カレーライス",
  "price": 1000,
})

あるいは

form_data = {
  "name": "カレーライス",
  "price": 1000,
}
form = MenuForm(form_data)

のように辞書形式で引数を設定していると思います。

しかしフォームがImageFieldを持つ場合に関しては、

form_data = {
  "name": "カレーライス",
  "price": 1000,
}
form_image = {
  "image": image
}
form = MenuForm(form_data, form_image)

のようにImageFieldとそれ以外で別々の辞書を用意する必要があります。

関数ベースビューでフォームを作成する場合にも

form = MenuForm(request.POST, request.FILES)

と引数を二つ指定しているのでなんとなくイメージは掴めると思いますが、ここをひとまとめにしてしまうとフォームが画像のデータを受け取れなくなるので注意しましょう。

SimpleUploadFileクラス

アップロードされたファイルを扱うための組み込みクラスの一つです。

from django.core.files.uploadfile import SimpleUploadFile
file = SimpleUploadFile(file_name, content, content_type)

とすることで作成されるファイルを、前項のform_imageにセットします。

テスト例

class TestFileUpload(TestCase):
  def test_file_upload(self):
    form_data = {
      "name": "カレーライス",
      "price": 1000,
    }
    with open("xxxx", "rb") as f: 
      file = SimpleUploadFile(
        f.name, 
        f.read(),
        content_type="image/png"
      )
      form_image = { "image": file }
      form = MenuForm(form_data, form_image)
    self.assertTrue(form.is_valid())

SimpleUploadFileオブジェクトの引数として、with openで開いたファイルのファイル名および中身を指定しています。

"xxxx"については画像のパスですが、アプリのルートではなくローカル環境のルートディレクトリからのパスになるので

import sys
sys.path.append("...")
from myproject.settings import BASEDIR
path = BASEDIR / "static/myapp/images/test_image.png"
with open(path, "rb"):

などとした方がわかりやすいかと思います。

キーワード引数のcontent_typeについては、次項で調べ方を解説しています。

content_typeの調べ方

標準ライブラリであるmemutypesを用いるとcontent_typeが確認できます。

import memutypes
memutypes.guess_types("/aaa/bbb/ccc/ddd.png")
# 引数としてファイルまでのパスを与えるとcontent_typeが返される