Archivos en formulario de Flask

Publicado por Andrea Navarro en

Creación de formulario

En este artículo veremos cómo manejar la subida de archivos desde formularios en el framework Flask, cómo guardarlos y cómo limitar el tipo y tamaño de archivos permitidos a través de las validaciones de campo.

Para permitir la subida de archivos en nuestra aplicación Flask utilizaremos la extensión Flask-WTF que permite la creación y manejo de formularios a través de clases. En este ejemplo crearemos un formulario que tenga solamente un campo para la carga de un archivo y haremos que ese archivo se almacene en un directorio al ser enviado el formulario.

Empezaremos por crear la clase de formulario, esta tendrá definido cada campo del formulario y las validaciones para cada uno de ellos. Para especificar un campo del tipo archivo se utiliza la clase FileField. El primer argumento será la etiqueta del campo, a continuación se listan las validaciones requeridas para el campo. Los campos del tipo FileField aceptan un tipo de validador FileRequired que verifica que un archivo ha sido cargado en el campo.

Finalmente agregamos también un campo del tipo SubmitField que representará el botón de envío del formulario.

from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileRequired
from wtforms import  SubmitField

class ArchivoForm(FlaskForm):
    archivo = FileField('Selecciona un archivo', validators=[FileRequired()])
    submit = SubmitField('Subir')

A continuación realizaremos el template donde se mostrará el formulario. En este ejemplo solo se muestra la parte del archivo que involucra el formulario pero para una mejor visualización es recomendable hacer uso de todos los recursos de Jinja como la herencia de templates y macros.

La etiqueta de formulario debe especificar el tipo de protocolo de envío (GET o POST) y la url a la que se realizará dicho envío. Cuando un formulario debe ser capaz de enviar archivos es necesario especificar también enctype="multipart/form-data" ya que de caso contrario el campo enviará un valor vacío.

Se muestra la etiqueta del campo de archivo form.archivo.label y el campo de subida con form.archivo. El botón de envío es renderizado al final del formulario con form.submit .

Para activar la protección CSRF en el formulario debe crearse adicionalmente un campo oculto con el nombre csrf_token cuyo valor debe está dado por la llamada a la función csrf_token().

<form method="POST" action="{{ url_for('subir') }}" enctype="multipart/form-data" >
	      <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
	     	 {{ form.archivo.label }}
             {{ form.archivo }}
	    	 {{ form.submit }}
      </form>

Esto dará como resultado un formulario con un campo que permite la carga de archivos y un botón de envío.

Para poder utilizar la protección CSRF es necesario inicializar la clase CSRFProtect al crear la aplicación Flask. Esta clase requiere que se configure la clave secreta SECRET_KEY ya que está será utilizada como parte del cálculo del csrf_token.

En este ejemplo también se ha creado una configuración DIRECTORIO que será usada por la aplicación para determinar la ubicación de los archivos al guardarlos.

import os
from flask import Flask
from flask_wtf import CSRFProtect
csrf = CSRFProtect()

def create_app():
    app = Flask(__name__)
    app.config["SECRET_KEY"] = "clave-secreta"
    app.config["DIRECTORIO"] = "/tmp"
    csrf.init_app(app)
    return app

Dentro de la ruta de nuestro formulario debemos inicializar la clase del formulario. Este objeto será enviado al template para renderizarlo. El método validate_on_submit verificará si el formulario ha sido enviado y que las condiciones de validación se hayan cumplido correctamente.

Antes de almacenar el archivo es altamente recomendable aplicar primero la función secure_filename de la librería de seguridad werkzeug. Esta función se encargará de modificar el nombre del archivo por uno seguro evitando de esta manera que un atacante pueda ingresar un nombre de archivo que apunte a un directorio del sistema. El nombre del archivo enviado puede obtenerse en el atributo filename de los datos del campo.

Finalmente el archivo puede guardarse utilizando el método save al cual le pasaremos como parámetro la ubicación obtenida de el directorio previamente configurado y el nombre seguro de archivo generado.

from flask import render_template, redirect, url_for, current_app
from ..forms.archivo_form import ArchivoForm
from werkzeug.utils import secure_filename
import os

@app.route('/subir', methods=["POST","GET"])
def subir():
    form = ArchivoForm()
    if form.validate_on_submit():
        f = form.archivo.data
        filename = secure_filename(f.filename)
        f.save(os.path.join(
            current_app.config['DIRECTORIO'], filename
        ))
        return redirect(url_for('index'))
    return render_template('archivo_form.html', form = form)

Validar tipos de archivos

Para restringir el tipo de archivo permitido dentro del campo de nuestro formulario se utiliza el validador FileAllowed. Este permitirá especificar en forma de listo las extensiones de archivo aceptadas.

from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileRequired, FileAllowed
from wtforms import  SubmitField

class ArchivoForm(FlaskForm):
     archivo = FileField('Selecciona un archivo',
              validators= [FileRequired(),
                          FileAllowed(['jpg','jpeg', 'png'],'Formato no permitido')])
     submit = SubmitField('Subir')

Validar tamaño de archivos

Si se quiere limitar el tamaño de los archivos subidos debe configurarse MAX_CONTENT_LENGTH en la aplicación. Esto restringirá cualquier subida cuyo tamaño exceda el configurado. En el siguiente ejemplo se limita el tamaño a 10MB.

import os
from flask import Flask
from flask_wtf import CSRFProtect
csrf = CSRFProtect()

def create_app():
    app = Flask(__name__)
    app.config["SECRET_KEY"] = "clave-secreta"
    app.config["DIRECTORIO"] = "/tmp"
    app.config['MAX_CONTENT_LENGTH'] = 1 * 1024 * 1024
    csrf.init_app(app)
    return app

Cuando el tamaño es excedido Flask generará un error 413 y mostrará una pantalla de error por defecto. Para cambiar este comportamiento es necesario utilizar el decorador errorhandler para definir la manera de manejar el error.

@app.errorhandler(413)
def exc_tamanio(e):
    #Definir manejo de error

En este artículo hemos visto como utilizar los formularios de Flask para subir archivos a nuestro sistema. Hemos utilizado los tipos de validaciones más comunes para restringir el tipo y tamaño de los archivos como así también algunas técnicas de seguridad. Hasta la próxima!


¿Preguntas? ¿Comentarios?

Si tenés dudas, o querés dejarnos tus comentarios y consultas, sumate al grupo de Telegram de la comunidad JuncoTIC!
¡Te esperamos!


Andrea Navarro

- Ingeniera en Informática - Docente universitaria - Investigadora