50% OFF Plus
--:--:--
¡Obtener!

Subir archivos en formularios Jinja en Flask

Intermedio
Flask
Flask
Actualizado: 20/06/2025

¡Desbloquea el curso de Flask completo!

IA
Ejercicios
Certificado
Entrar

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 Plus

Upload 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.

Progreso guardado
Asistente IA
Ejercicios
Iniciar sesión gratis

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

⭐⭐⭐⭐⭐
4.9/5 valoración