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

时间:2011-04-20 14:28:00

标签: python django rest http-put django-1.3

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

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

所以,有些问题:

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

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

Django 1.3是可以接受的。因此,我可以使用request.raw_post_datarequest.read()(或者其他更好的访问方法)执行某些操作。有什么想法吗?

3 个答案:

答案 0 :(得分:8)

  

Django 1.3是可以接受的。所以我可以   要么做点什么   request.raw_post_data或   request.read()(或者一些   其他更好的访问方法)。任何   想法?

您不希望触及request.raw_post_data - 这意味着将整个请求正文读入内存,如果您正在谈论文件上传可能是一个非常大的数量,那么request.read()是要走的路。您也可以使用Django< = 1.2进行此操作,但这意味着在HttpRequest中挖掘以找出使用私有接口的正确方法,这是一个真正的拖累,以确保您的代码也将是兼容Django> = 1.3。

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

  1. request.upload_handlers(默认为MemoryFileUploadHandler& TemporaryFileUploadHandler
  2. 检索上传错误
  3. 确定请求的内容长度(在HttpRequestMultiPartParser中搜索内容长度,以查看正确的方法。)
  4. 确定上传文件的文件名,方法是让客户端使用网址的最后一个路径部分指定,或让客户在the Content-Disposition header的“filename =”部分指定它。
  5. 对于每个处理程序,使用相关的args调用handler.new_file(模拟字段名称)
  6. 使用request.read()以块为单位阅读请求正文,并为每个块调用handler.receive_data_chunk()
  7. 对于每个处理程序调用{​​{1}},如果它返回一个值,那就是上传的文件。
  8.   

    我怎样才能推断出什么的mime类型   正在发送?如果我做对了,a   PUT正文就是没有的文件   序幕。因此,我是否需要这样做   用户指定mime类型   他们的标题?

    让客户端在Content-Type标头中指定它,或使用python's mimetype module猜测媒体类型。

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


    按照要求,

    由Ninefingers编辑,这就是我所做的,完全基于上面和django来源。

    handler.file_complete()

    由于我在这里定义API,因此不需要跨浏览器支持。就我的协议而言,不提供正确的信息是一个破碎的请求。我有两个想法,我是否要说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 或者我是否要允许不存在的字符集。在任何情况下,我都将image/jpeg; charset=binary设置为有效的客户端责任。

    同样,对于我的协议,传入文件名。我不确定Content-Type参数的用途是什么,并且源没有提供很多线索。

    下面发生的事情实际上比看起来简单得多。你问每个处理程序它是否会处理原始输入。正如上述作者所说,你已经field_name&默认情况下为MemoryFileUploadHandler。好吧,当被要求创建一个TemporaryFileUploadHandler来判断它是否会处理文件时(基于各种设置),结果会MemoryFileUploadHandler。如果它确定它会发生,它会抛出异常,否则它将不会创建该文件并让另一个处理程序接管。

    我不确定new_file的目的是什么,但我保留了它的来源。其余的应该是直截了当的。

    counters

答案 1 :(得分:1)

由于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()的调用。

答案 2 :(得分:0)

在使用Django 2.2时,我遇到了这个问题,并且一直在寻找一些可以通过PUT请求上传文件的东西。

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)