Mira la lección en vídeo
Accede al vídeo completo de esta lección y a más contenido exclusivo con el Plan Plus.
Desbloquear Plan PlusUpload de archivos
La subida de archivos es una funcionalidad esencial en muchas aplicaciones web modernas. Flask proporciona herramientas integradas para manejar archivos enviados desde formularios HTML de manera segura y eficiente. A través del objeto request.files
, podemos acceder a los archivos subidos y procesarlos según las necesidades de nuestra aplicación.
Configuración básica para upload de archivos
Antes de implementar la funcionalidad de subida, necesitamos configurar algunos parámetros básicos en nuestra aplicación Flask. La configuración más importante es establecer una carpeta de destino donde se almacenarán los archivos subidos:
import os
from flask import Flask
app = Flask(__name__)
# Configuración para upload de archivos
UPLOAD_FOLDER = 'uploads'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
# Crear la carpeta si no existe
if not os.path.exists(UPLOAD_FOLDER):
os.makedirs(UPLOAD_FOLDER)
Esta configuración define la ruta base donde se guardarán todos los archivos subidos. Es importante crear la carpeta automáticamente si no existe para evitar errores durante la ejecución.
Formulario HTML para subida de archivos
Para permitir la subida de archivos, el formulario HTML debe incluir el atributo enctype="multipart/form-data"
y utilizar el método POST. El campo de archivo se define con un input de tipo file
:
<form method="POST" enctype="multipart/form-data">
<div>
<label for="archivo">Seleccionar archivo:</label>
<input type="file" id="archivo" name="archivo" required>
</div>
<button type="submit">Subir archivo</button>
</form>
El atributo enctype
es fundamental para que el navegador envíe correctamente los datos binarios del archivo. Sin este atributo, la subida de archivos no funcionará.
Implementación del controlador
El controlador debe manejar tanto la visualización del formulario (GET) como el procesamiento de la subida (POST). Utilizamos request.files
para acceder a los archivos enviados:
from flask import request, redirect, url_for, render_template, flash
from werkzeug.utils import secure_filename
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
# Verificar si se envió un archivo
if 'archivo' not in request.files:
flash('No se seleccionó ningún archivo', 'error')
return redirect(request.url)
file = request.files['archivo']
# Verificar si se seleccionó un archivo válido
if file.filename == '':
flash('No se seleccionó ningún archivo', 'error')
return redirect(request.url)
if file:
# Asegurar el nombre del archivo
filename = secure_filename(file.filename)
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
# Guardar el archivo
file.save(file_path)
flash(f'Archivo {filename} subido correctamente', 'success')
return redirect(url_for('upload_file'))
return render_template('upload.html')
Función secure_filename
La función secure_filename()
de Werkzeug es crucial para la seguridad de la aplicación. Esta función limpia el nombre del archivo eliminando caracteres peligrosos que podrían comprometer el sistema:
from werkzeug.utils import secure_filename
# Ejemplos de cómo funciona secure_filename
print(secure_filename('mi archivo.txt')) # mi_archivo.txt
print(secure_filename('../../../etc/passwd')) # etc_passwd
print(secure_filename('archivo con espacios.pdf')) # archivo_con_espacios.pdf
Esta función previene ataques como directory traversal y asegura que los nombres de archivo sean compatibles con diferentes sistemas operativos.
Verificación de archivos subidos
Es importante implementar validaciones básicas antes de procesar cualquier archivo. Estas verificaciones incluyen comprobar si realmente se envió un archivo y si tiene un nombre válido:
def is_valid_file(file):
"""Verifica si el archivo es válido para subir"""
# Verificar si el objeto file existe
if not file:
return False
# Verificar si tiene nombre
if file.filename == '':
return False
# Verificar si el nombre es seguro
if not secure_filename(file.filename):
return False
return True
@app.route('/upload-advanced', methods=['GET', 'POST'])
def upload_advanced():
if request.method == 'POST':
file = request.files.get('archivo')
if not is_valid_file(file):
flash('Archivo no válido', 'error')
return redirect(request.url)
# Procesar archivo válido
filename = secure_filename(file.filename)
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(file_path)
flash('Archivo subido exitosamente', 'success')
return redirect(url_for('upload_advanced'))
return render_template('upload.html')
Manejo de múltiples archivos
Flask también permite manejar múltiples archivos en una sola petición. Para esto, el formulario HTML debe incluir el atributo multiple
en el input de archivo:
<form method="POST" enctype="multipart/form-data">
<div>
<label for="archivos">Seleccionar archivos:</label>
<input type="file" id="archivos" name="archivos" multiple required>
</div>
<button type="submit">Subir archivos</button>
</form>
El controlador debe iterar sobre todos los archivos recibidos:
@app.route('/upload-multiple', methods=['GET', 'POST'])
def upload_multiple():
if request.method == 'POST':
files = request.files.getlist('archivos')
if not files or all(f.filename == '' for f in files):
flash('No se seleccionaron archivos', 'error')
return redirect(request.url)
uploaded_files = []
for file in files:
if file and file.filename != '':
filename = secure_filename(file.filename)
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(file_path)
uploaded_files.append(filename)
if uploaded_files:
flash(f'Se subieron {len(uploaded_files)} archivos correctamente', 'success')
else:
flash('No se pudo subir ningún archivo', 'error')
return redirect(url_for('upload_multiple'))
return render_template('upload_multiple.html')
Información del archivo subido
Los objetos de archivo en Flask proporcionan información útil sobre el archivo subido que podemos utilizar para logging o procesamiento adicional:
@app.route('/upload-info', methods=['POST'])
def upload_with_info():
file = request.files.get('archivo')
if file and file.filename != '':
# Información disponible del archivo
filename = secure_filename(file.filename)
content_type = file.content_type
content_length = file.content_length
# Guardar archivo
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(file_path)
# Registrar información
print(f"Archivo subido: {filename}")
print(f"Tipo de contenido: {content_type}")
print(f"Tamaño: {content_length} bytes")
flash(f'Archivo {filename} ({content_type}) subido correctamente', 'success')
return redirect(url_for('upload_file'))
Esta información es especialmente útil para auditoría y para implementar validaciones más específicas basadas en el tipo de contenido o el tamaño del archivo.
Validación y almacenamiento
Guarda tu progreso
Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.
Más de 25.000 desarrolladores ya confían en CertiDevs
La validación de archivos es un aspecto crítico en cualquier aplicación que permita subidas de archivos. Sin validaciones adecuadas, nuestra aplicación puede ser vulnerable a ataques maliciosos o experimentar problemas de rendimiento por archivos excesivamente grandes. Flask nos permite implementar múltiples capas de validación para garantizar que solo se procesen archivos seguros y apropiados.
Validación por extensión de archivo
Una de las validaciones más comunes es restringir las extensiones de archivo permitidas. Esto previene que los usuarios suban archivos ejecutables o de tipos no deseados:
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'doc', 'docx'}
def allowed_file(filename):
"""Verifica si la extensión del archivo está permitida"""
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/upload-validated', methods=['GET', 'POST'])
def upload_validated():
if request.method == 'POST':
file = request.files.get('archivo')
if not file or file.filename == '':
flash('No se seleccionó ningún archivo', 'error')
return redirect(request.url)
if not allowed_file(file.filename):
flash('Tipo de archivo no permitido', 'error')
return redirect(request.url)
filename = secure_filename(file.filename)
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(file_path)
flash('Archivo validado y subido correctamente', 'success')
return redirect(url_for('upload_validated'))
return render_template('upload.html')
Validación por tamaño de archivo
Controlar el tamaño máximo de los archivos subidos es esencial para prevenir ataques de denegación de servicio y mantener el rendimiento del servidor:
# Configuración de tamaño máximo (16 MB)
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
def validate_file_size(file):
"""Valida el tamaño del archivo antes de guardarlo"""
# Obtener el tamaño del archivo
file.seek(0, os.SEEK_END)
file_size = file.tell()
file.seek(0) # Volver al inicio del archivo
# Validar tamaño (5 MB máximo para este ejemplo)
max_size = 5 * 1024 * 1024 # 5 MB
return file_size <= max_size, file_size
@app.route('/upload-size-validated', methods=['POST'])
def upload_size_validated():
file = request.files.get('archivo')
if file and file.filename != '':
# Validar extensión
if not allowed_file(file.filename):
flash('Tipo de archivo no permitido', 'error')
return redirect(request.url)
# Validar tamaño
is_valid_size, file_size = validate_file_size(file)
if not is_valid_size:
flash(f'Archivo demasiado grande. Máximo permitido: 5 MB', 'error')
return redirect(request.url)
filename = secure_filename(file.filename)
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(file_path)
flash(f'Archivo subido correctamente ({file_size} bytes)', 'success')
return redirect(url_for('upload_validated'))
Validación por tipo MIME
La validación por tipo MIME proporciona una capa adicional de seguridad, ya que verifica el contenido real del archivo en lugar de confiar únicamente en la extensión:
import mimetypes
ALLOWED_MIME_TYPES = {
'text/plain',
'application/pdf',
'image/png',
'image/jpeg',
'image/gif',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
}
def validate_mime_type(file):
"""Valida el tipo MIME del archivo"""
# Obtener tipo MIME del archivo
mime_type = file.content_type
# Validación adicional usando el nombre del archivo
guessed_type, _ = mimetypes.guess_type(file.filename)
return mime_type in ALLOWED_MIME_TYPES and \
(guessed_type is None or guessed_type in ALLOWED_MIME_TYPES)
@app.route('/upload-mime-validated', methods=['POST'])
def upload_mime_validated():
file = request.files.get('archivo')
if file and file.filename != '':
# Validaciones combinadas
if not allowed_file(file.filename):
flash('Extensión de archivo no permitida', 'error')
return redirect(request.url)
if not validate_mime_type(file):
flash('Tipo de contenido no permitido', 'error')
return redirect(request.url)
filename = secure_filename(file.filename)
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(file_path)
flash('Archivo validado completamente y subido', 'success')
return redirect(url_for('upload_validated'))
Organización del almacenamiento
Para aplicaciones que manejan muchos archivos, es recomendable organizar el almacenamiento en subdirectorios. Esto mejora el rendimiento del sistema de archivos y facilita la gestión:
import datetime
import uuid
def generate_upload_path(filename):
"""Genera una ruta organizada para el archivo"""
# Crear estructura de carpetas por fecha
today = datetime.date.today()
year_month = today.strftime('%Y/%m')
# Crear la ruta completa
upload_path = os.path.join(app.config['UPLOAD_FOLDER'], year_month)
# Crear directorios si no existen
os.makedirs(upload_path, exist_ok=True)
return upload_path
def generate_unique_filename(filename):
"""Genera un nombre único para evitar colisiones"""
name, ext = os.path.splitext(secure_filename(filename))
unique_id = str(uuid.uuid4())[:8]
return f"{name}_{unique_id}{ext}"
@app.route('/upload-organized', methods=['POST'])
def upload_organized():
file = request.files.get('archivo')
if file and file.filename != '':
if not allowed_file(file.filename):
flash('Tipo de archivo no permitido', 'error')
return redirect(request.url)
# Generar nombre único y ruta organizada
unique_filename = generate_unique_filename(file.filename)
upload_path = generate_upload_path(unique_filename)
file_path = os.path.join(upload_path, unique_filename)
# Guardar archivo
file.save(file_path)
flash(f'Archivo guardado como: {unique_filename}', 'success')
return redirect(url_for('upload_validated'))
Validación completa con manejo de errores
Una implementación robusta debe combinar todas las validaciones y manejar errores de manera elegante:
def validate_and_save_file(file):
"""Función completa de validación y guardado"""
try:
# Verificar que existe el archivo
if not file or file.filename == '':
return False, 'No se seleccionó ningún archivo'
# Validar extensión
if not allowed_file(file.filename):
return False, 'Tipo de archivo no permitido'
# Validar tipo MIME
if not validate_mime_type(file):
return False, 'Contenido del archivo no válido'
# Validar tamaño
is_valid_size, file_size = validate_file_size(file)
if not is_valid_size:
return False, 'Archivo demasiado grande'
# Generar nombre y ruta únicos
unique_filename = generate_unique_filename(file.filename)
upload_path = generate_upload_path(unique_filename)
file_path = os.path.join(upload_path, unique_filename)
# Guardar archivo
file.save(file_path)
return True, f'Archivo {unique_filename} guardado correctamente'
except Exception as e:
return False, f'Error al procesar el archivo: {str(e)}'
@app.route('/upload-complete', methods=['GET', 'POST'])
def upload_complete():
if request.method == 'POST':
file = request.files.get('archivo')
success, message = validate_and_save_file(file)
if success:
flash(message, 'success')
else:
flash(message, 'error')
return redirect(url_for('upload_complete'))
return render_template('upload.html')
Configuración de límites globales
Flask permite establecer límites globales que se aplican automáticamente a todas las rutas de la aplicación:
# Configuración global de la aplicación
app.config.update(
UPLOAD_FOLDER='uploads',
MAX_CONTENT_LENGTH=16 * 1024 * 1024, # 16 MB máximo
ALLOWED_EXTENSIONS={'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'},
UPLOAD_SUBFOLDER_BY_DATE=True
)
@app.errorhandler(413)
def too_large(e):
"""Maneja errores de archivos demasiado grandes"""
flash('El archivo es demasiado grande. Máximo permitido: 16 MB', 'error')
return redirect(request.url), 413
Esta configuración asegura que todas las validaciones de tamaño se apliquen automáticamente sin necesidad de verificaciones manuales en cada ruta, proporcionando una capa base de protección para toda la aplicación.
Aprendizajes de esta lección de Flask
- Configurar Flask para manejar la subida de archivos y definir la carpeta de almacenamiento.
- Crear formularios HTML con el atributo enctype adecuado para subir archivos.
- Implementar controladores en Flask para procesar archivos subidos, incluyendo manejo de múltiples archivos.
- Aplicar validaciones de seguridad como extensión, tamaño y tipo MIME de los archivos.
- Organizar el almacenamiento de archivos y manejar errores durante la subida para una aplicación robusta.
Completa este curso de Flask y certifícate
Únete a nuestra plataforma de cursos de programación y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.
Asistente IA
Resuelve dudas al instante
Ejercicios
Practica con proyectos reales
Certificados
Valida tus conocimientos
Más de 25.000 desarrolladores ya se han certificado con CertiDevs