8đź‘Ť
Django 1.3 is acceptable. So I can
either do something with
request.raw_post_data or
request.read() (or alternatively some
other better method of access). Any
ideas?
You don’t want to be touching request.raw_post_data
– that implies reading the entire request body into memory, which if you’re talking about file uploads might be a very large amount, so request.read()
is the way to go. You can do this with Django <= 1.2 as well, but it means digging around in HttpRequest
to figure out the the right way to use the private interfaces, and it’s a real drag to then ensure your code will also be compatible with Django >= 1.3.
I’d suggest that what you want to do is to replicate the existing file upload behaviour parts of the MultiPartParser
class:
- Retrieve the upload handers from
request.upload_handlers
(Which by default will beMemoryFileUploadHandler
&TemporaryFileUploadHandler
) - Determine the request’s content length (Search of Content-Length in
HttpRequest
orMultiPartParser
to see the right way to do this.) - Determine the uploaded file’s filename, either by letting the client specify this using the last path part of the url, or by letting the client specify it in the “filename=” part of the
Content-Disposition
header. - For each handler, call
handler.new_file
with the relevant args (mocking up a field name) - Read the request body in chunks using
request.read()
and callinghandler.receive_data_chunk()
for each chunk. - For each handler call
handler.file_complete()
, and if it returns a value, that’s the uploaded file.
How can I deduce the mime type of what
is being sent? If I’ve got it right, a
PUT body is simply the file without
prelude. Do I therefore require that
the user specify the mime type in
their headers?
Either let the client specify it in the Content-Type header, or use python’s mimetype module to guess the media type.
I’d be interested to find out how you get on with this – it’s something I’ve been meaning to look into myself, be great if you could comment to let me know how it goes!
Edit by Ninefingers as requested, this is what I did and is based entirely on the above and the django source.
upload_handlers = request.upload_handlers
content_type = str(request.META.get('CONTENT_TYPE', ""))
content_length = int(request.META.get('CONTENT_LENGTH', 0))
if content_type == "":
return HttpResponse(status=400)
if content_length == 0:
# both returned 0
return HttpResponse(status=400)
content_type = content_type.split(";")[0].strip()
try:
charset = content_type.split(";")[1].strip()
except IndexError:
charset = ""
# we can get the file name via the path, we don't actually
file_name = path.split("/")[-1:][0]
field_name = file_name
Since I’m defining the API here, cross browser support isn’t a concern. As far as my protocol is concerned, not supplying the correct information is a broken request. I’m in two minds as to whether I want say image/jpeg; charset=binary
or if I’m going to allow non-existent charsets. In any case, I’m putting setting Content-Type
validly as a client-side responsibility.
Similarly, for my protocol, the file name is passed in. I’m not sure what the field_name
parameter is for and the source didn’t give many clues.
What happens below is actually much simpler than it looks. You ask each handler if it will handle the raw input. As the author of the above states, you’ve got MemoryFileUploadHandler
& TemporaryFileUploadHandler
by default. Well, it turns out MemoryFileUploadHandler
will when asked to create a new_file
decide whether it will or not handle the file (based on various settings). If it decides it’s going to, it throws an exception, otherwise it won’t create the file and lets another handler take over.
I’m not sure what the purpose of counters
was, but I’ve kept it from the source. The rest should be straightforward.
counters = [0]*len(upload_handlers)
for handler in upload_handlers:
result = handler.handle_raw_input("",request.META,content_length,"","")
for handler in upload_handlers:
try:
handler.new_file(field_name, file_name,
content_type, content_length, charset)
except StopFutureHandlers:
break
for i, handler in enumerate(upload_handlers):
while True:
chunk = request.read(handler.chunk_size)
if chunk:
handler.receive_data_chunk(chunk, counters[i])
counters[i] += len(chunk)
else:
# no chunk
break
for i, handler in enumerate(upload_handlers):
file_obj = handler.file_complete(counters[i])
if not file_obj:
# some indication this didn't work?
return HttpResponse(status=500)
else:
# handle file obj!
3đź‘Ť
Newer Django versions allow for handling this a lot easier thanks to https://gist.github.com/g00fy-/1161423
I modified the given solution like this:
if request.content_type.startswith('multipart'):
put, files = request.parse_file_upload(request.META, request)
request.FILES.update(files)
request.PUT = put.dict()
else:
request.PUT = QueryDict(request.body).dict()
to be able to access files and other data like in POST. You can remove the calls to .dict()
if you want your data to be read-only.
- Relation does not exist error in Django
- Assign Value of Named URL to a Variable in Django Templates
- Is there any list of blog engines, written in Django?
1đź‘Ť
I hit this problem while working with Django 2.2, and was looking for something that just worked for uploading a file via PUT request.
from django.http import QueryDict
from django.http.multipartparser import MultiValueDict
from django.core.files.uploadhandler import (
SkipFile,
StopFutureHandlers,
StopUpload,
)
class PutUploadMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
method = request.META.get("REQUEST_METHOD", "").upper()
if method == "PUT":
self.handle_PUT(request)
return self.get_response(request)
def handle_PUT(self, request):
content_type = str(request.META.get("CONTENT_TYPE", ""))
content_length = int(request.META.get("CONTENT_LENGTH", 0))
file_name = request.path.split("/")[-1:][0]
field_name = file_name
content_type_extra = None
if content_type == "":
return HttpResponse(status=400)
if content_length == 0:
# both returned 0
return HttpResponse(status=400)
content_type = content_type.split(";")[0].strip()
try:
charset = content_type.split(";")[1].strip()
except IndexError:
charset = ""
upload_handlers = request.upload_handlers
for handler in upload_handlers:
result = handler.handle_raw_input(
request.body,
request.META,
content_length,
boundary=None,
encoding=None,
)
counters = [0] * len(upload_handlers)
for handler in upload_handlers:
try:
handler.new_file(
field_name,
file_name,
content_type,
content_length,
charset,
content_type_extra,
)
except StopFutureHandlers:
break
for chunk in request:
for i, handler in enumerate(upload_handlers):
chunk_length = len(chunk)
chunk = handler.receive_data_chunk(chunk, counters[i])
counters[i] += chunk_length
if chunk is None:
# Don't continue if the chunk received by
# the handler is None.
break
for i, handler in enumerate(upload_handlers):
file_obj = handler.file_complete(counters[i])
if file_obj:
# If it returns a file object, then set the files dict.
request.FILES.appendlist(file_name, file_obj)
break
any(handler.upload_complete() for handler in upload_handlers)