如何在Django中通过PUT请求处理文件上载?

2024-10-06 11:23:25 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在实现一个REST风格的接口,希望能够通过HTTP PUT请求创建(通过上传)文件。我想创建一个TemporaryUploadedFileInMemoryUploadedFile,然后我可以将其传递到作为模型一部分的对象上的现有FileField.save(),从而存储文件。

我不太清楚如何处理文件上传部分。具体来说,这是一个put请求,我无权访问request.FILES,因为它不存在于PUT请求中。

所以,有些问题:

  • 我是否可以利用HttpRequest类中的现有功能,特别是处理文件上载的部分?我知道directPUT不是一个多部分MIME请求,所以我不这么认为,但值得一问。
  • 如何推断正在发送的内容的mime类型?如果我是对的,一个放置体就是没有前奏的文件。因此,我是否要求用户在其标题中指定mime类型?
  • 如何将此扩展到大量数据?我不想把它全部读入记忆,因为那样效率很低。理想情况下,我会做TemporaryUploadFile和相关代码所做的事情-一次只写一部分?

我看了一下this code sample,它诱使Django将PUT作为POST请求处理。如果我没弄错的话,它只能处理表单编码的数据。这是REST,所以最好的解决方案是不要假设表单编码的数据会存在。不过,我很高兴听到关于以某种方式使用mime(而不是多部分)的适当建议(但是上传应该只包含一个文件)。

Django 1.3是可以接受的。所以我可以用request.raw_post_data或者request.read()(或者其他更好的访问方法)做些事情。有什么想法吗?


Tags: 文件数据djangoresthttp表单类型编码
2条回答

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?

你不想触碰request.raw_post_data-这意味着将整个请求体读入内存,如果你在讨论文件上传可能会有很大的量,所以request.read()才是正确的方法。在Django<;=1.2的情况下也可以这样做,但这意味着在HttpRequest中寻找正确的方法来使用私有接口,这是一个真正的阻力,可以确保代码也与Django>;=1.3兼容。

我建议您要做的是复制existing file upload behaviour parts of the ^{} class

  1. request.upload_handlers(默认为MemoryFileUploadHandler&;TemporaryFileUploadHandler)检索上载句柄
  2. 确定请求的内容长度(在HttpRequestMultiPartParser中搜索内容长度以查看正确的方法。)
  3. 通过让客户端使用url的最后一个路径部分指定文件名,或者让客户端在“filename=”part of the ^{} header中指定文件名,来确定上载文件的文件名。
  4. 对于每个处理程序,使用相关参数调用handler.new_file(模拟字段名)
  5. 使用request.read()并为每个块调用handler.receive_data_chunk()以块形式读取请求正文。
  6. 对于每个处理程序调用handler.file_complete(),如果它返回一个值,那就是上传的文件。

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?

要么让客户端在内容类型头中指定它,要么使用python's mimetype module猜测媒体类型。

我很想知道你是怎么处理这件事的-这是我一直想调查自己的事情,如果你能评论让我知道它是怎么回事就好了!


由Ninefingers编辑根据要求,这是我所做的,完全基于上述内容和django来源。

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

既然我在这里定义了API,跨浏览器支持就不再是问题了。就我的协议而言,不提供正确的信息是一个坏请求。我对是否要说image/jpeg; charset=binary或是否允许不存在的字符集有两种看法。无论如何,我把设置Content-Type有效地作为客户端的责任。

类似地,对于我的协议,文件名被传入。我不确定field_name参数的用途,而且源代码没有给出太多线索。

下面发生的事情实际上比看起来简单得多。询问每个处理程序是否将处理原始输入。作为上述状态的作者,默认情况下您拥有MemoryFileUploadHandler&;TemporaryFileUploadHandler。好吧,当被要求创建一个new_fileMemoryFileUploadHandler将决定它是否处理该文件(基于各种设置)。如果它决定要这样做,它会抛出一个异常,否则它不会创建文件并让另一个处理程序接管。

我不知道counters的目的是什么,但我一直不知道它的来源。其余的应该是直截了当的。

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!

由于https://gist.github.com/g00fy-/1161423,较新的Django版本允许更轻松地处理这个问题

我这样修改了给定的解决方案:

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()

能够访问POST中的文件和其他数据。如果希望数据是只读的,可以删除对.dict()的调用。

相关问题 更多 >