Как мне обработать загрузку файла через запрос PUT в Django? - PullRequest
18 голосов
/ 20 апреля 2011

Я реализую интерфейс в стиле REST и хотел бы иметь возможность создавать (с помощью загрузки) файлы через запрос HTTP PUT. Я хотел бы создать TemporaryUploadedFile или InMemoryUploadedFile, которые затем я могу передать своим существующим FileField и .save() на объекте, который является частью модели, сохраняя таким образом файл.

Я не совсем уверен, как обработать часть загрузки файла. В частности, это запрос пут, у меня нет доступа к request.FILES, поскольку он не существует в запросе PUT.

Итак, несколько вопросов:

  • Могу ли я использовать существующие функции в классе HttpRequest, в частности, в части, которая обрабатывает загрузку файлов? Я знаю, что прямой PUT не является составным MIME-запросом, поэтому я так не думаю, но его стоит спросить.
  • Как я могу определить тип MIME того, что отправляется? Если я правильно понял, тело PUT - это просто файл без прелюдии. Поэтому я требую, чтобы пользователь указал тип mime в своих заголовках?
  • Как мне расширить это на большие объемы данных? Я не хочу читать все это в памяти, так как это крайне неэффективно. В идеале я бы делал то, что делает TemporaryUploadFile и связанный с ним код - писать его по частям?

Я посмотрел на этот пример кода , который обманывает Django при обработке PUT как POST запроса. Если я правильно понял, он будет обрабатывать только закодированные данные. Это REST, поэтому лучшим решением было бы не предполагать, что данные, закодированные в форме, будут существовать. Тем не менее, я рад услышать соответствующий совет по использованию mime (не multipart) как-то (но загрузка должна содержать только один файл).

Джанго 1.3 приемлемо. Так что я могу сделать что-то с request.raw_post_data или request.read() (или, альтернативно, каким-нибудь другим лучшим способом доступа). Есть идеи?

Ответы [ 2 ]

8 голосов
/ 23 апреля 2011

Джанго 1.3 приемлемо. Так что я могу либо сделать что-то с request.raw_post_data или request.read () (или альтернативно некоторые другой лучший способ доступа). любой идеи?

Вы не хотите касаться request.raw_post_data - это подразумевает чтение всего тела запроса в память, что, если вы говорите о загрузке файлов, может быть очень большой суммой, поэтому request.read() - это путь , Вы можете сделать это и с Django <= 1.2, но это означает, что нужно покопаться в <code>HttpRequest, чтобы выяснить, как правильно использовать частные интерфейсы, и это реальная проблема, чтобы убедиться, что ваш код также будет совместим с Django. > = 1,3.

Я бы посоветовал вам скопировать существующие части поведения при загрузке файлов MultiPartParser класса :

  1. Получите обработчики загрузки из request.upload_handlers (который по умолчанию будет MemoryFileUploadHandler & TemporaryFileUploadHandler)
  2. Определите длину содержимого запроса (Поиск Content-Length в HttpRequest или MultiPartParser, чтобы увидеть правильный способ сделать это.)
  3. Определите имя файла загруженного файла, либо позволив клиенту указать это, используя последнюю часть пути URL, либо позволив клиенту указать его в части «filename =» заголовка Content-Disposition .
  4. Для каждого обработчика вызывать handler.new_file с соответствующими аргументами (макетировать имя поля)
  5. Считайте тело запроса в чанках, используя request.read() и вызывая handler.receive_data_chunk() для каждого чанка.
  6. Для каждого вызова обработчика handler.file_complete(), и если он возвращает значение, это загруженный файл.

Как я могу вывести тип пантомимы, что отправляется? Если я правильно понял, PUT body - это просто файл без прелюдия. Поэтому я требую, чтобы пользователь указывает тип MIME в их заголовки?

Либо разрешите клиенту указать его в заголовке Content-Type, либо используйте модуль mimetype python , чтобы угадать тип носителя.

Мне было бы интересно узнать, как вы справляетесь с этим - я хотел подумать о себе, было бы здорово, если бы вы могли прокомментировать, чтобы я знал, как это происходит!


Редактирование 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 по умолчанию. Что ж, оказывается, 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!
1 голос
/ 28 февраля 2017

Новые версии Django позволяют справиться с этим намного проще благодаря https://gist.github.com/g00fy-/1161423

Я изменил данное решение так:

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(), если хотите, чтобы ваши данные были доступны только для чтения.

...