如何使用django rest framework';s测试客户端?

如何使用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

我有一个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'>)
回溯(最近一次呼叫最后一次):
文件“/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
模块来自哪里?@RudolfOlah
Image
来自枕头库。请参见。在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
    )