web-dev-qa-db-ja.com

Django-rest-frameworkテストクライアントでバイナリファイルのアップロードをテストするにはどうすればよいですか?

Django=アップロードするファイルを受け入れるビューを備えたアプリケーションがあります。Django REST framework I ' m APIViewをサブクラス化し、次のようにpost()メソッドを実装します。

class FileUpload(APIView):
    permission_classes = (IsAuthenticated,)

    def post(self, request, *args, **kwargs):
        try:
            image = request.FILES['image']
            # Image processing here.
            return Response(status=status.HTTP_201_CREATED)
        except KeyError:
            return Response(status=status.HTTP_400_BAD_REQUEST, data={'detail' : 'Expected image.'})

現在、認証が必要であり、アップロードされたファイルが実際に処理されることを確認するために、いくつかのユニットテストを作成しようとしています。

class TestFileUpload(APITestCase):
    def test_that_authentication_is_required(self):
        self.assertEqual(self.client.post('my_url').status_code, status.HTTP_401_UNAUTHORIZED)

    def test_file_is_accepted(self):
        self.client.force_authenticate(self.user)
        image = Image.new('RGB', (100, 100))
        tmp_file = tempfile.NamedTemporaryFile(suffix='.jpg')
        image.save(tmp_file)
        with open(tmp_file.name, 'rb') as data:
            response = self.client.post('my_url', {'image': data}, format='multipart')
            self.assertEqual(status.HTTP_201_CREATED, response.status_code)

ただし、RESTフレームワークがリクエストをエンコードしようとすると失敗します

Traceback (most recent call last):
  File "/home/vagrant/.virtualenvs/myapp/lib/python3.3/site-packages/Django/utils/encoding.py", line 104, in force_text
    s = six.text_type(s, encoding, errors)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 118: invalid start byte

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/vagrant/webapp/myproject/myapp/tests.py", line 31, in test_that_jpeg_image_is_accepted
    response = self.client.post('my_url', { 'image': data}, format='multipart')
  File "/home/vagrant/.virtualenvs/myapp/lib/python3.3/site-    packages/rest_framework/test.py", line 76, in post
    return self.generic('POST', path, data, content_type, **extra)
  File "/home/vagrant/.virtualenvs/myapp/lib/python3.3/site-packages/rest_framework/compat.py", line 470, in generic
    data = force_bytes_or_smart_bytes(data, settings.DEFAULT_CHARSET)
  File "/home/vagrant/.virtualenvs/myapp/lib/python3.3/site-packages/Django/utils/encoding.py", line 73, in smart_text
    return force_text(s, encoding, strings_only, errors)
  File "/home/vagrant/.virtualenvs/myapp/lib/python3.3/site-packages/Django/utils/encoding.py", line 116, in force_text
    raise DjangoUnicodeDecodeError(s, *e.args)
Django.utils.encoding.DjangoUnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 118: invalid start byte. You passed in b'--BoUnDaRyStRiNg\r\nContent-Disposition: form-data; name="image"; filename="tmpyz2wac.jpg"\r\nContent-Type: image/jpeg\r\n\r\n\xff\xd8\xff[binary data omitted]' (<class 'bytes'>)

UTF-8としてデコードしようとせずに、テストクライアントにデータを送信させるにはどうすればよいですか?

41
Tore Olsen

ファイルのアップロードをテストする場合、データではなく、ストリームオブジェクトをリクエストに渡す必要があります

これは @ arocks のコメントで指摘されています

代わりに{'image':file}を渡します

しかし、それがなぜそれが必要なのかを完全には説明していませんでした(また、質問と一致しませんでした)。この特定の質問については、あなたがする必要があります

from PIL import Image

class TestFileUpload(APITestCase):

    def test_file_is_accepted(self):
        self.client.force_authenticate(self.user)

        image = Image.new('RGB', (100, 100))

        tmp_file = tempfile.NamedTemporaryFile(suffix='.jpg')
        image.save(tmp_file)
        tmp_file.seek(0)

        response = self.client.post('my_url', {'image': tmp_file}, format='multipart')

       self.assertEqual(status.HTTP_201_CREATED, response.status_code)

これは、ファイルがストリームオブジェクトとして渡される標準のDjango=リクエスト、およびDjango=REST Frameworkファイルデータを渡すだけで、DjangoおよびDjango =RESTフレームワークは文字列として解釈し、ストリームを予期しているため問題が発生します。

そして、別の一般的なエラーを探している人のために、なぜファイルのアップロードは機能しないのに、通常のフォームデータは次のようになります:必ずformat="multipart"リクエスト作成時

これも同様の問題を引き起こし、コメントで @ RobinElvin によって指摘されました

Format = 'multipart'が欠落していたためです

30
Kevin Brown

Python 3ユーザー:openのファイルがmode='rb'(読み取り、バイナリ)。それ以外の場合、Django=がファイルのreadを呼び出すと、utf-8コーデックはすぐに窒息し始めます。ファイルは、utf-8、ascii、またはその他のエンコーディングではなくバイナリとしてデコードする必要があります。

# This won't work in Python 3
with open(tmp_file.name) as fp:
        response = self.client.post('my_url', 
                                   {'image': fp}, 
                                   format='multipart')

# Set the mode to binary and read so it can be decoded as binary
with open(tmp_file.name, 'rb') as fp:
        response = self.client.post('my_url', 
                                   {'image': fp}, 
                                   format='multipart')
15
Meistro

PATCHメソッドを使用する場合の方法を理解するのはそれほど簡単ではありませんが、解決策は この質問 で見つかりました。

from Django.test.client import BOUNDARY, MULTIPART_CONTENT, encode_multipart

with open(tmp_file.name, 'rb') as fp:
    response = self.client.patch(
        'my_url', 
        encode_multipart(BOUNDARY, {'image': fp}), 
        content_type=MULTIPART_CONTENT
    )
2
Anton Shurashov

Windowsの場合、答えは少し異なります。私は次のことをしなければなりませんでした:

resp = None
with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as tmp_file:
    image = Image.new('RGB', (100, 100), "#ddd")
    image.save(tmp_file, format="JPEG")
    tmp_file.close()

# create status update
with open(tmp_file.name, 'rb') as photo:
    resp = self.client.post('/api/articles/', {'title': 'title',
                                               'content': 'content',
                                               'photo': photo,
                                               }, format='multipart')
os.remove(tmp_file.name)

この回答( https://stackoverflow.com/a/23212515/7235 )で指摘されている違いは、ファイルがWindowsで閉じられた後に使用できないことです。 Linuxでは、@ Meistroの答えが機能するはずです。

1
Diego Jancic