How can I test binary file uploading with django-rest-framework's test client?

后端 未结 5 1640
忘了有多久
忘了有多久 2020-12-05 17:11

I have a Django application with a view that accepts a file to be uploaded. Using the Django REST framework I\'m subclassing APIView and implementing the post() method like

相关标签:
5条回答
  • 2020-12-05 17:28

    Python 3 users: make sure you open the file in mode='rb' (read,binary). Otherwise, when Django calls read on the file the utf-8 codec will immediately start choking. The file should be decoded as binary not utf-8, ascii or any other encoding.

    # 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')
    
    0 讨论(0)
  • 2020-12-05 17:48

    For those in Windows, the answer is a bit different. I had to do the following:

    resp = None
    with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as tmp_file:
        image = Image.new('RGB', (100, 100), "#ffffd")
        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)
    

    The difference, as pointed in this answer (https://stackoverflow.com/a/23212515/72350), the file cannot be used after it was closed in Windows. Under Linux, @Meistro's answer should work.

    0 讨论(0)
  • 2020-12-05 17:49

    You can use Django built-in 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)
    
    
    0 讨论(0)
  • 2020-12-05 17:51

    When testing file uploads, you should pass the stream object into the request, not the data.

    This was pointed out in the comments by @arocks

    Pass { 'image': file} instead

    But that didn't full explain why it was needed (and also didn't match the question). For this specific question, you should be doing

    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)
    

    This will match a standard Django request, where the file is passed in as a stream object, and Django REST Framework handles it. When you just pass in the file data, Django and Django REST Framework interpret it as a string, which causes issues because it is expecting a stream.

    And for those coming here looking to another common error, why file uploads just won't work but normal form data will: make sure to set format="multipart" when creating the request.

    This also gives a similar issue, and was pointed out by @RobinElvin in the comments

    It was because I was missing format='multipart'

    0 讨论(0)
  • 2020-12-05 17:51

    It's not so simple to understand how to do it if you want to use the PATCH method, but I found the solution in 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
        )
    
    0 讨论(0)
提交回复
热议问题