我有一个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]' (<class 'bytes'>)
如何让测试客户端发送数据而不尝试将其解码为UTF-8?
答案 0 :(得分:30)
在测试文件上传时,您应该将流对象传递给请求,而不是数据。
的评论指出了这一点传递{'image':file}而不是
但这并没有完全解释为什么需要它(也与问题不符)。对于这个具体问题,你应该做
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)
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"
。< / p>
这也提出了类似的问题,@RobinElvin在评论中指出了
这是因为我缺少format ='multipart'
答案 1 :(得分:15)
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')
答案 2 :(得分:1)
对于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/72350)中所指出的那样,该文件在Windows中关闭后无法使用。在Linux下,@ Meistro的答案应该有效。
答案 3 :(得分:1)
如果你想使用PATCH方法,理解如何做到这一点并不是那么简单,但我在this question中找到了解决方案。
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
)
答案 4 :(得分:0)
您可以使用内置的Django SimpleUploadedFile:
from django.core.files.uploadedfile import SimpleUploadedFile
class TestFileUpload(APITestCase):
...
def test_file_is_accepted(self):
...
tmp_file = SimpleUploadedFile(
"file.jpg", "file_content", content_type="image/jpg")
response = self.client.post(
'my_url', {'image': tmp_file}, format='multipart')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)