python - ¿Cómo manejo la carga de archivos a través de la solicitud PUT en Django?
rest http-put (2)
Django 1.3 es aceptable. Así que puedo hacer algo con request.raw_post_data o request.read () (o, alternativamente, algún otro método de acceso mejor). ¿Algunas ideas?
No quieres estar tocando request.raw_post_data
- eso implica leer todo el cuerpo de la solicitud en la memoria, lo que si estás hablando de cargas de archivos puede ser una cantidad muy grande, por lo que request.read()
es el camino a seguir. También puedes hacer esto con Django <= 1.2, pero significa excavar en HttpRequest
para descubrir la forma correcta de usar las interfaces privadas, y es un verdadero obstáculo para asegurarte de que tu código también sea compatible con Django> = 1.3.
Le sugiero que lo que quiere hacer es replicar las partes existentes del comportamiento de carga de archivos de la clase MultiPartParser
:
- Recupere los gestores de carga de
request.upload_handlers
(que de forma predeterminada seránMemoryFileUploadHandler
yTemporaryFileUploadHandler
) - Determine la longitud del contenido de la solicitud (Búsqueda de la longitud del contenido en
HttpRequest
oMultiPartParser
para ver la forma correcta de hacerlo). - Determine el nombre de archivo del archivo cargado, ya sea permitiendo que el cliente lo especifique utilizando la última parte de la ruta de la url, o permitiendo que el cliente lo especifique en la parte "filename =" del encabezado
Content-Disposition
. - Para cada controlador, llame a
handler.new_file
con loshandler.new_file
relevantes (handler.new_file
un nombre de campo) - Lea el cuerpo de la solicitud en trozos usando
request.read()
y llamando ahandler.receive_data_chunk()
para cada trozo. - Para cada controlador, llame a
handler.file_complete()
, y si devuelve un valor, ese es el archivo cargado.
¿Cómo puedo deducir el tipo mime de lo que se está enviando? Si lo tengo bien, un cuerpo PUT es simplemente el archivo sin preludio. Por lo tanto, ¿necesito que el usuario especifique el tipo mime en sus encabezados?
Deje que el cliente lo especifique en el encabezado Content-Type o use el módulo mimetype de python para adivinar el tipo de medio.
Me interesaría saber cómo te va con esto. Es algo que he querido ver en mí mismo. ¡Sería genial si pudieras comentar para saber cómo me va!
Editado por Ninefingers según lo solicitado, esto es lo que hice y se basa completamente en lo anterior y en la fuente de 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
Ya que estoy definiendo la API aquí, el soporte entre navegadores no es una preocupación. En lo que respecta a mi protocolo, no proporcionar la información correcta es una solicitud interrumpida. Estoy en dos mentes en cuanto a si quiero decir image/jpeg; charset=binary
image/jpeg; charset=binary
o si voy a permitir charsets inexistentes. En cualquier caso, estoy poniendo la configuración de Content-Type
válidamente como una responsabilidad del lado del cliente.
De manera similar, para mi protocolo, se pasa el nombre del archivo. No estoy seguro de para qué es el parámetro field_name
y la fuente no dio muchas pistas.
Lo que sucede a continuación es en realidad mucho más simple de lo que parece. Le preguntas a cada manejador si manejará la entrada en bruto. Como el autor de los estados anteriores, usted tiene MemoryFileUploadHandler
& TemporaryFileUploadHandler
de forma predeterminada. Bueno, resulta que new_file
decidirá si se creará un new_file
si manejará o no el archivo (según la configuración). Si decide que lo va a hacer, lanza una excepción, de lo contrario no creará el archivo y permitirá que otro controlador se haga cargo.
No estoy seguro de cuál era el propósito de los counters
, pero lo he mantenido de la fuente. El resto debe ser sencillo.
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!
Estoy implementando una interfaz estilo REST y me gustaría poder crear (a través de la carga) archivos a través de una solicitud HTTP PUT. Me gustaría crear un InMemoryUploadedFile
TemporaryUploadedFile
o un InMemoryUploadedFile
mi InMemoryUploadedFile
que luego pueda pasar a mi FileField
existente y .save()
en el objeto que forma parte del modelo, almacenando así el archivo.
No estoy muy seguro de cómo manejar la parte de carga de archivos. Específicamente, siendo esta una solicitud de venta, no tengo acceso a la request.FILES
ya que no existe en una solicitud PUT
.
Entonces, algunas preguntas:
- ¿Puedo aprovechar la funcionalidad existente en la clase
HttpRequest
, específicamente la parte que maneja las cargas de archivos? Sé que unPUT
directo no es una solicitud MIME de varias partes, así que no lo creo, pero vale la pena preguntar. - ¿Cómo puedo deducir el tipo mime de lo que se está enviando? Si lo tengo bien, un cuerpo PUT es simplemente el archivo sin preludio. Por lo tanto, ¿necesito que el usuario especifique el tipo mime en sus encabezados?
- ¿Cómo extiendo esto a grandes cantidades de datos? No quiero leerlo todo en la memoria ya que es altamente ineficiente. Lo ideal sería hacer lo que hace
TemporaryUploadFile
y el código relacionado: ¿escribirlo parte por vez?
He echado un vistazo a este ejemplo de código que engaña a Django para que maneje PUT
como una solicitud POST
. Sin embargo, si lo tengo bien, solo manejará datos codificados en el formulario. Esto es REST, por lo que la mejor solución sería no asumir que los datos codificados en el formulario existirán. Sin embargo, me alegra escuchar el consejo apropiado sobre el uso de mime (no multiparte) de alguna manera (pero la carga solo debe contener un solo archivo).
Django 1.3 es aceptable. Así que puedo hacer algo con request.raw_post_data
o request.read()
(o, alternativamente, algún otro método de acceso mejor). ¿Algunas ideas?
Las nuevas versiones de Django permiten que esto sea mucho más fácil de manejar gracias a https://gist.github.com/g00fy-/1161423
He modificado la solución dada de esta manera:
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()
Para poder acceder a archivos y otros datos como en POST. Puede eliminar las llamadas a .dict()
si desea que sus datos sean de solo lectura.