Cannot PUT file to Django REST API by python requests

ghz 5days ago ⋅ 6 views

I am using python requests to put file to django rest api:

put:

def pretty_print(req):
    print('1,{}\n2,{}\n3,{}\n\n4,{}'.format(
        '-----------START-----------',
        req.method + ' ' + req.url,
        '\n'.join('{}: {}'.format(k, v) for k, v in req.headers.items()),
        req.body[0:10],
    ))
    print "----------END------------"


try:
    req = requests.Request('PUT',
            'https://myip/myproject/api/upload',
            headers={'content-type':'multipart/form-data'},
            files={"file": ('file1', open(filepath, "rb"))} )
    prepped = req.prepare()
    print "prepped:", prepped.headers
    #print "prepped:", prepped.body
    pretty_print( prepped )
    s = requests.Session()
    r = s.send(prepped, verify=False)
    print "response:", r.json()
except Exception as e:
    print "Exception:", e

views.py

class FileUploadView(APIView):
    parser_classes = (MultiPartParser,)

    def put(self, request):
        try:
            print "data:", request.data.dict()
            print "file:", request.FILES.dict()
            print "----------"
            data = {'files': 'testing'}
            response = Response(data)

But, I got the empty dicts.

data: {}
file: {}

And the print out of the requests:

prepped: {'Content-Length': '18364', 'content-type': 'text/plain'}
1,-----------START-----------
2,PUT https://10.32.64.4/reporting-central/api/upload
3,content-type: text/plain
Content-Length: 18364

4,--e9541743
----------END------------
/usr/local/lib/python2.7/dist-packages/requests/packages/urllib3/util/ssl_.py:318: SNIMissingWarning: An HTTPS request has been made, but the SNI (Subject Name Indication) extension to TLS is not available on this platform. This may cause the server to present an incorrect TLS certificate, which can cause validation failures. You can upgrade to a newer version of Python to solve this. For more information, see https://urllib3.readthedocs.org/en/latest/security.html#snimissingwarning.
  SNIMissingWarning
/usr/local/lib/python2.7/dist-packages/requests/packages/urllib3/util/ssl_.py:122: InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately and may cause certain SSL connections to fail. You can upgrade to a newer version of Python to solve this. For more information, see https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning.
  InsecurePlatformWarning
/usr/local/lib/python2.7/dist-packages/requests/packages/urllib3/connectionpool.py:821: InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.org/en/latest/security.html
  InsecureRequestWarning)
/usr/local/lib/python2.7/dist-packages/requests/packages/urllib3/connectionpool.py:821: InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.org/en/latest/security.html
  InsecureRequestWarning)
response: {u'files': u'testing'}

Anything wrong? Any comments welcomed. Thanks.

UPDATE

my app url.py:

from api.views import *

urlpatterns = [
    url(r'^upload/$', FileUploadView.as_view(), name='api-upload'),
    url(r'^test/$', test, name='api-test'),
]

my project url.py:

urlpatterns = patterns('',
        url(r'^api/', include('api.urls')),
)

UPDATE2

I found that maybe my requests PUT is wrong. because I used postman to do put, which was successful. Still hope to figure out why my requests PUT not work.

Answer

The issue with your code lies in how the requests library handles the PUT request with a file upload. Specifically:

  1. Content-Type Misconfiguration: When uploading files, the Content-Type should be set to multipart/form-data. However, in your code, it's being overridden as text/plain in the headers dictionary, which causes the Django server to fail to parse the uploaded file properly.

  2. Incorrect Request Object Usage: You're using requests.Request and preparing it manually, but files is handled better directly in requests.put or requests.post.

  3. Empty FILES and data Dicts: This occurs because the request isn't formatted as a proper multipart/form-data request.


Fixing the Code

Here’s the corrected code for uploading a file with a PUT request:

Client-Side (Python Requests)

import requests

def pretty_print(req):
    print('1, {}\n2, {}\n3, {}\n\n4, {}'.format(
        '-----------START-----------',
        req.method + ' ' + req.url,
        '\n'.join('{}: {}'.format(k, v) for k, v in req.headers.items()),
        req.body[:100],  # Print first 100 characters of the body for brevity
    ))
    print("----------END------------")

filepath = "path/to/your/file.txt"

try:
    with open(filepath, "rb") as f:
        # Using requests.put directly
        files = {"file": ("file1.txt", f)}
        headers = {"Accept": "application/json"}  # No need to specify content-type manually
        url = "https://myip/myproject/api/upload"
        
        response = requests.put(url, files=files, headers=headers, verify=False)
        
        # Pretty print request and response
        pretty_print(response.request)
        print("Response:", response.status_code, response.json())
except Exception as e:
    print("Exception:", e)

Server-Side (Django)

Ensure your views.py is correctly set up to handle file uploads. The FileUploadView appears fine, but here’s a double-check:

from rest_framework.views import APIView
from rest_framework.parsers import MultiPartParser
from rest_framework.response import Response

class FileUploadView(APIView):
    parser_classes = (MultiPartParser,)  # Ensure MultiPartParser is used

    def put(self, request, *args, **kwargs):
        file_obj = request.FILES.get('file')  # Access uploaded file
        if file_obj:
            print("Received file:", file_obj.name)
            return Response({"message": "File received successfully", "file_name": file_obj.name})
        return Response({"error": "No file uploaded"}, status=400)

Key Points

  1. Use files Argument: In requests.put, always use the files argument for file uploads, and let requests handle the Content-Type header.
  2. Correct File Access: Use request.FILES.get('file') on the Django side to retrieve the uploaded file.
  3. Validation: Add checks to ensure the file is present and handle errors gracefully.
  4. Testing: Use a tool like Postman or curl to validate your API endpoints independently of the client-side code.

Example curl Test

curl -X PUT -F "file=@path/to/your/file.txt" https://myip/myproject/api/upload

If this works, it confirms that the issue lies with your Python client code. The fixed version provided above should resolve the problem.