我有一个Django应用程序,其视图接受要上载的文件.使用Django REST框架我将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]' ()
如何让测试客户端发送数据而不尝试将其解码为UTF-8?
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')
在测试文件上载时,您应该将流对象传递给请求,而不是数据.
@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 Framework将其解释为字符串,这会导致问题,因为它期待流.
对于那些来到这里寻找另一个常见错误的人,为什么文件上传不起作用,但正常的表单数据将:确保format="multipart"
在创建请求时设置.
这也提出了类似的问题,@ RobinElvin在评论中指出了这一点
那是因为我缺少格式='multipart'