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

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

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

Итак, вопросы:

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

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

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

2 ответов


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

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

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

  1. получить обработчики загрузки из request.upload_handlers (который по умолчанию будет MemoryFileUploadHandler & TemporaryFileUploadHandler)
  2. определите содержание запроса length (поиск контента-длина в HttpRequest или MultiPartParser посмотреть правильный способ сделать это.)
  3. определите имя файла загруженного файла, либо разрешив клиенту указать это, используя последнюю часть пути url, либо разрешив клиенту указать его в части " filename="на Content-Disposition заголовок.
  4. для каждого обработчика, вызов handler.new_file с соответствующими args (издеваясь над именем Поля)
  5. прочитайте тело запроса кусками, используя request.read() и зовет!--14--> для каждого блока.
  6. для каждого вызова обработчика handler.file_complete(), и если он возвращает значение, это загруженный файл.

как я могу вывести тип mime того, что посылают? Если я правильно понял, Положить тело-это просто файл без вступление. Поэтому я требую, чтобы пользователь указывает тип 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!

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