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

18 python django rest http-put django-1.3

我正在实现一个REST风格的界面,并希望能够通过HTTP PUT请求创建(通过上传)文件.我想创建一个TemporaryUploadedFile或一个InMemoryUploadedFile,然后我可以传递给我现有的FileField.save()作为模型一部分的对象,从而存储文件.

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

所以,有些问题:

  • 我可以利用HttpRequest类中的现有功能,特别是处理文件上传的部分吗?我知道直接PUT不是多部分MIME请求,所以我不这么认为,但值得一提.
  • 如何推断出发送的mime类型?如果我做对了,PUT主体就是没有前奏的文件.因此,我是否要求用户在其标题中指定mime类型?
  • 如何将其扩展到大量数据?我不想把它全部读入内存,因为这是非常低效的.理想情况下,我会做什么TemporaryUploadFile和相关的代码 - 一次写一部分?

我已经看过这个代码示例,它将Django PUT作为POST请求处理.如果我做得对,它只会处理表格编码数据.这是REST,因此最好的解决方案是不假设表格编码数据将存在.但是,我很高兴听到有关使用mime(而不是multipart)的适当建议(但上传应该只包含一个文件).

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

Tom*_*tie 8

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

你不想触摸request.raw_post_data- 这意味着将整个请求体读入内存,如果你谈论文件上传可能是一个非常大的数量,那么request.read()是要走的路.您也可以使用Django <= 1.2进行此操作,但这意味着要深入研究HttpRequest使用私有接口的正确方法,然后确保您的代码也与Django兼容> = 1.3 .

我建议你想要做的就是复制了现有的文件上传行为配件MultiPartParser:

  1. request.upload_handlers(默认情况下将是MemoryFileUploadHandler&TemporaryFileUploadHandler)检索上传手柄
  2. 确定请求的内容长度(在内容中搜索内容长度HttpRequestMultiPartParser查看正确的方式来执行此操作.)
  3. 确定上传文件的文件名,方法是让客户端使用url的最后一个路径部分指定它,或者让客户端Content-Disposition文件的"filename ="部分指定它.
  4. 对于每个处理程序,handler.new_file使用相关的args 调用(模拟字段名称)
  5. 使用request.read()并调用handler.receive_data_chunk()每个块来读取块中的请求体.
  6. 对于每个处理程序调用handler.file_complete(),如果它返回一个值,那就是上传的文件.

如何推断出发送的mime类型?如果我做对了,PUT主体就是没有前奏的文件.因此,我是否要求用户在其标题中指定mime类型?

让客户端在Content-Type标头中指定它,或者使用python的mimetype模块来猜测媒体类型.

我有兴趣了解你是如何继续这样做的 - 这是我一直想要调查自己的东西,如果你能评论让我知道它是怎么回事,那就太棒了!


由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
Run Code Online (Sandbox Code Playgroud)

由于我在这里定义API,因此不需要跨浏览器支持.就我的协议而言,不提供正确的信息是一个破碎的请求.我有两个想法,我是否想要说image/jpeg; charset=binary或者我是否会允许不存在的字符集.无论如何,我将设置Content-Type有效地作为客户端责任.

同样,对于我的协议,文件名被传入.我不确定field_name参数是什么,并且源没有给出很多线索.

下面发生的事情实际上比它看起来要简单得多.你问每个处理程序它是否会处理原始输入.由于上述国家的作者,你有MemoryFileUploadHandlerTemporaryFileUploadHandler默认.好吧,MemoryFileUploadHandler当被要求创建一个new_file决定是否会处理文件时(基于各种设置),它会被证明.如果它确定它会发生,它会抛出异常,否则它将不会创建该文件并让另一个处理程序接管.

我不确定目的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!
Run Code Online (Sandbox Code Playgroud)