如何使用django rest framework';s测试客户端?
我有一个Django应用程序,它的视图接受要上传的文件。使用Django REST框架,我正在对APIView进行子类化,并实现post()方法,如下所示:如何使用django rest framework';s测试客户端?,django,django-rest-framework,django-unittest,Django,Django Rest Framework,Django Unittest,我有一个Django应用程序,它的视图接受要上传的文件。使用Django REST框架,我正在对APIView进行子类化,并实现post()方法,如下所示: class FileUpload(APIView): permission_classes = (IsAuthenticated,) def post(self, request, *args, **kwargs): try: image = request.FILES['image
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'>)
回溯(最近一次呼叫最后一次):
文件“/home/vagrant/.virtualenvs/myapp/lib/python3.3/site packages/django/utils/encoding.py”,第104行,强制文本
s=6。文本类型(s、编码、错误)
UnicodeDecodeError:“utf-8”编解码器无法解码位置118处的字节0xff:无效的开始字节
在处理上述异常期间,发生了另一个异常:
回溯(最近一次呼叫最后一次):
文件“/home/vagrant/webapp/myproject/myapp/tests.py”,测试中第31行,即接受jpeg图像
response=self.client.post('my_url',{'image':data},format='multipart')
文件“/home/vagrant/.virtualenvs/myapp/lib/python3.3/site-packages/rest\u framework/test.py”,第76行,在post中
返回self.generic('POST',路径,数据,内容类型,**额外)
文件“/home/vagrant/.virtualenvs/myapp/lib/python3.3/site packages/rest\u framework/compat.py”,第470行,通用格式
数据=强制字节或智能字节(数据、设置、默认字符集)
智能文本中的文件“/home/vagrant/.virtualenvs/myapp/lib/python3.3/site packages/django/utils/encoding.py”,第73行
返回强制文本(s、编码、仅字符串、错误)
文件“/home/vagrant/.virtualenvs/myapp/lib/python3.3/site packages/django/utils/encoding.py”,第116行,强制文本
提升数据删除错误(s,*e.args)
django.utils.encoding.DjangoUnicodeDecodeError:“utf-8”编解码器无法解码第118位的字节0xff:无效的开始字节。您传入了b'--BoUnDaRyStRiNg\r\n内容配置:表单数据;name=“image”;filename=“tmpyz2wac.jpg”\r\n内容类型:image/jpeg\r\n\r\n\xff\xd8\xff[二进制数据省略]()
如何让测试客户端发送数据而不尝试将其解码为UTF-8?当测试文件上载时,您应该将流对象传递到请求中,而不是数据 委员会的评论指出了这一点 改为传递{'image':文件} 但这并不能完全解释为什么需要它(也与问题不符)。对于这个特定的问题,您应该
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框架处理它。当您只是传入文件数据时,Django和Django REST框架将其解释为字符串,这会导致问题,因为它需要一个流
对于那些来这里查看另一个常见错误的人来说,为什么文件上传无法正常工作,而普通表单数据将正常工作:确保在创建请求时设置format=“multipart”
这也提出了一个类似的问题,并在评论中指出
这是因为我缺少格式='multipart'
Python3用户:确保
以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')
对于那些使用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)
不同之处在于,正如这个答案()所指出的,在Windows中关闭文件后,无法使用该文件。在Linux下,@Meistro的答案应该是可行的。如果你想使用补丁方法,要理解如何做并不那么简单,但我在中找到了解决方案
您可以使用内置的Django
:
从django.core.files.uploadedfile导入SimpleUploadedFile
类TestFileUpload(APITestCase):
...
接受def测试文件(自我):
...
tmp_file=SimpleUploadedFile(
“file.jpg”,“file\u content”,content\u type=“image/jpg”)
响应=self.client.post(
'我的url',{'image':tmp_文件},format='multipart')
self.assertEqual(response.status\u代码,status.HTTP\u 201\u已创建)
Pass{'image':文件}
instead@arocks鹰眼!我已经更正了帖子中的错误,实际代码没有这个问题。你的代码对我有用。非常感谢。我也有同样的问题。你解决了吗?这是因为我缺少格式='multipart'-dohI认为{'image':data}
应该是{'image':fp}
。在找到这篇文章之前,我一直在努力上传,但直到我在上面描述的{'image':data}
字典中放置文件句柄对象fp
而不是data
,我的测试才通过。({'image':fp}
在我的案例中有效,{'image':data}
未更新)。谢谢DMfll。由于某种原因,这导致了400错误。返回的错误是{“file”:[“提交的文件是空的。”]}。请注意,如果出于任何原因(perf将是一个原因)不想分配tempfile,也可以执行tmp_file=BytesIO(b'some text')
;这将为您提供一个可以作为文件对象传递的二进制流。()。必须在post
之前添加tmp_文件。seek(0)
,但在其他方面非常完美!这几乎把我逼疯了,谢谢!Image
模块来自哪里?@RudolfOlahImage
来自枕头库。请参见。在OSX10.12.5上,我得到了ValueError:I/O操作关闭的文件
我认为您不需要tmp_文件.close()
和with
语句。
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
)