Как обрабатывать загрузку файлов через запрос 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
класс:
- получить обработчики загрузки из
request.upload_handlers
(который по умолчанию будетMemoryFileUploadHandler
&TemporaryFileUploadHandler
) - определите содержание запроса length (поиск контента-длина в
HttpRequest
илиMultiPartParser
посмотреть правильный способ сделать это.) - определите имя файла загруженного файла, либо разрешив клиенту указать это, используя последнюю часть пути url, либо разрешив клиенту указать его в части " filename="на
Content-Disposition
заголовок. - для каждого обработчика, вызов
handler.new_file
с соответствующими args (издеваясь над именем Поля) - прочитайте тело запроса кусками, используя
request.read()
и зовет!--14--> для каждого блока. - для каждого вызова обработчика
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()
Если вы хотите, чтобы ваши данные только для чтения.