Подтвердить что ты не робот

Как обрабатывать загрузку файлов через запрос PUT в Django?

Я реализую интерфейс стиля 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_data или request.read() (или, альтернативно, другого лучшего метода доступа). Любые идеи?

4b9b3361

Ответ 1

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

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

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

  • Извлеките загрузчиков из request.upload_handlers (которые по умолчанию будут MemoryFileUploadHandler и TemporaryFileUploadHandler)
  • Определите длину содержимого запроса (поиск длины контента в HttpRequest или MultiPartParser, чтобы увидеть правильный способ сделать это.)
  • Определите имя загруженного файла, либо указав его клиенту с использованием последней части пути URL-адреса, либо предоставив клиенту указать его в разделе "filename =" заголовок Content-Disposition.
  • Для каждого обработчика вызовите handler.new_file с соответствующими аргументами (издеваясь над именем поля)
  • Прочитайте тело запроса в кусках с помощью request.read() и вызывая handler.receive_data_chunk() для каждого фрагмента.
  • Для каждого вызова обработчика 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

Поскольку я определяю 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!

Ответ 2

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