Pino bio photo

Pino

I (infrequently) blog about data science, business intelligence, big data, web technologies and free software.

Twitter LinkedIn

Para un nuevo proyecto en el que me estoy embarcando, he tenido que hacer un script que permitiese subir archivos zip en Django y los descomprimiese. Tras un inicio dubitativo, me he puesto a buscar alguna aplicación o código que ya trajese esta opción hecha y al final he terminado haciendo una mezcla entre código obtenido de django-photo-albumseste trozo de código publicado en DjangoSnippets.

El script escribe el fichero enviado por un formulario POST en disco, comprueba que el fichero es efectivamente un zip y que no está corrupto y lo descomprime, almacenando su contenido en la ruta MEDIA_ROOT configurada en el fichero settings.py del proyecto.

Es bastante simple, pero espero que pueda servir de ayuda a alguien y, sobre todo, que alguien me pueda sugerir mejoras para incluirlas luego en el proyecto.

# -*- coding: utf-8 -*-

import tempfile, os
import settings
from django.http import HttpResponse
from django import forms
from zipfile import ZipFile, BadZipfile
from django.shortcuts import render_to_response

class UploadFileForm(forms.Form):
	title = forms.CharField(max_length=50) # No sirve para nada
	file  = forms.FileField()

	# Almacena en disco el fichero
	# Comprueba que el zip no está corrupto
	# Devuelve el path absoluto a dicho fichero
	def clean_file(self):
		def ffile_path(uploaded_file):
			'''  Converts InMemoryUploadedFile to on-disk file so it will have path. '''
			try:
				return uploaded_file.temporary_file_path()
			except AttributeError:
				fileno, path = tempfile.mkstemp()
				temp_file = os.fdopen(fileno,'w+b')
				for chunk in uploaded_file.chunks():
					temp_file.write(chunk)
				temp_file.close()
				return path

		path = ffile_path(self.cleaned_data['file'])

		try: # Comprobación de que el fichero no está corrupto
			zf = ZipFile(path)
			bad_file = zf.testzip()
			if bad_file:
				raise forms.ValidationError(_('El fichero "%s" del ZIP está corrupto.') % bad_file)
			zf.close()
		except BadZipfile:
			raise forms.ValidationError('El fichero subido no es un ZIP.')

		return path

	def process_file(self):

		# Ruta donde se encuentra el fichero
		zip_filename = self.cleaned_data['file']

		# Lugar donde se alojarán los ficheros descomprimidos
		dirname = settings.MEDIA_ROOT

		zip = ZipFile(zip_filename)

		lista_ficheros = []

		# Recorremos todos los ficheros que contiene el zip
		for filename in zip.namelist():

			ruta_total = os.path.join(dirname, filename)

			# Si es un directorio, lo creamos
			if filename.endswith('/'):
				try: # Don't try to create a directory if exists
					os.mkdir(ruta_total)
				except:
					pass
			# Si es un fichero, lo escribimos
			else:
				outfile = open(ruta_total, 'wb')
				outfile.write(zip.read(filename))
				outfile.close()
				lista_ficheros.append(ruta_total)

		zip.close()
		os.unlink(zip_filename)

		return lista_ficheros

# Vista que muestra el formulario o gestiona la petición POST de éste
def upload_file(request):
	if request.method == 'POST':

		form = UploadFileForm(request.POST, request.FILES)

		# Si el formulario el válido, proceso el fichero
		if form.is_valid():

			zipdata = request.FILES['file']
			title = request.FILES['file'].name

			listado = form.process_file()

			# Aquí podríamos trabajar con los ficheros subidos
			# cuyas rutas totales se encuentran en listado

			# Esta es la respuesta que enviamos al usuario una vez
			# finalizado el proceso
			return HttpResponse("Subido!")

	else:
		form = UploadFileForm()
	return render_to_response('upload.html', {'form': form})