domingo, 9 de agosto de 2020

Flask 12. Formularios con Flask-Wtf





Formularios con Flask-WTF


Flask-Wtf (https://flask-wtf.readthedocs.io/en/stable/) es una extensión de Flask que nos va a permitir crear y validar formularios de forma más cómoda. Nos permite trabajar con la libreria WTform de python. (https://wtforms.readthedocs.io/en/2.3.x/)

Lo primero que hay que hacer en una aplicación de Flask antes de usar cualquier extensión es instalarla en el sistema, en mi caso dentro del entorno virtual que creado para el proyecto.

Si estas usando un entorno virtual, actívalo, y una vez dentro ejecuta:

pip install Flask-WTF

En todo este capitulo vamos a trabajar con los archivos que creamos en el capitulo anterior, donde creamos una calculadora de dos números con las operaciones básicas. Tendremos que hacer unas serie de cambios en los archivos para adaptarlos al uso de Flask-WTF.

Al usar Flask-Wtf todo el formulario se representa a través de una clase. Esta clase hereda del objeto FlaskForm y en ella se definen los campos del formulario como variables de clase.

1º ) En el directorio de trabajo de nuestra aplicación vamos a crear un nuevo archivo llamado forms.py al mismo nivel que inicio.py. Este fichero recogerá todos los formularios de nuestra aplicación:


Como el código de los ejemplos va siendo de un tamaño más grande voy a dejar un enlace al final de cada entrada con el código del capítulo.

Comenzamos importando todas las librerías que necesitaremos.

Primeramente importamos la librería FlaskForm la cual por dependencia nos va a instalar wtforms. Déspues de wtforms importamos todos los campos del formulario que vayamos a necesitar y por último también de wtform importamos los validadores que necesitaremos para validar los formularios.

Como ves, hemos creado una clase "formulario_calculadora" que hereda de la clase FlaskForm. Además cada uno de los campos del formulario se ha definido como una variable de clase de un tipo especifico. Primer_numero y segundo_numero son del tipo IntegerField y la variable de clase operación es del tipo SelectField, por ejemplo. Esto lo que hace es que posteriormente el campo se renderice correctamente en la plantilla en función del tipo con lo que hayamos declarado, es decir Flask va a programar por nosotros las etiquetas html5, label e input, que hayamos definido para cada campo.

Aqui puedes encontrar alguno de los módulos que están definidos en wtform.( https://wtforms.readthedocs.io/en/latest/fields/)

Entre otros tenemos:

BooleanField - Representa una única casilla de selección. Puede estar seleccionada o no.

DateField - Control para introducir una fecha. Por defecto el formado es de año, mes y dia. Sin embargo se puede cambiar. Por ejemplo DateField("Etiqueta para el campo", format='%d-%m-%Y'). Lo que haría que una fecha valida fuera 02-08-2020

DecimalField - Es un campo de texto que muestra y fuerza a que los datos sean decimales. Tiene varios parámeteros que se pueden utilizar, entre otros:

  • places = Cuantos decimales se usarán en el formulario. Por defecto se usan dos decimales.
  • rounding = Como se redondea el decimal.

EmailField - Se suele utilizar a través de un campo StringField. No obstante para usar el validador  "Email"previamente hay que importar el módulo email-validator con la instrucción: pip install email-validator

FileFiled - Representa un campo de carga de archivos. Por defecto, su valor será el nombre del archivo enviado en los datos del formulario.

FloatField - Es un campo de texto en el que todas las entradas se convierten en un número de coma flotante. Las entradas erróneas se ignoran y no se aceptan como valor. En la mayor parte de los casos es preferible usar DecimalField.

HiddenField - Es un campo oculto.

IntegerField - Es un campo de texto, excepto que todas las entradas se convierten en un número entero.La entrada errónea se ignora y no se acepta como valor.

MultipleFileField - Como el anterior pero permite escoger múltiples archivos.

PasswordField - Campo donde introducir la contraseña. Lo que se teclea aparece por defecto en pantalla como puntos.

RadioField - representan varias casillas de selección (radio buttons)

SelectField - representa una lista desplegable donde se escoge una opción. 

SelectMultipleField - Igual que el anterior con la la diferencia de que en la lista desplegable puedes seleccionar multiples elementos.

StringField - Campo de texto de línea simple. Los saltos de línea son eliminados automáticamente del valor introducido.

SubmitField - Botón que envía el formulario.

TextField - Es un campo de texto del tipo StringField.

TextAreaField -  Este campo representa un campo de texto pero que puede usar varias lineas de texto.

Puedes encontrar más información sobre los diferentes campos de la etiqueta input de html 5 en 

Una vez creada la clase, definir un campo para el formulario es tan sencillo como teclear:

Nombre de la variable de clase (nombre_del_campo) = TipoDeCampo(propiedades o atributos)

en nuestro ejemplo:

primer_numero = IntegerField("Número 1", validators=[DataRequired("Tienes que introducir un número entero")])

primer_numero es la variable de clase con la que hemos definido el nombre del campo para nosotros en nuestro formulario.

IntegerField representa el tipo de campo que usaremos. Es un campo de texto que al validarse solo admite números enteros. Si alguien teclea una letra en vez de un número no se acepta como valor válido. 

El primer parámetro que le pasamos al campo "Numero 1" es el nombre con el que se mostrará al usuario, es decir la etiqueta <label> del campo en HTML.

Opcionalmente podemos pasar también un listado de validadores a cada campo, por ejemplo:

- para comprobar que los campos obligatorios no se dejan vacíos DataRequired() o InputRequired(). A no ser que exista una razón especifica se recomienda usar esta última.

- para especificar la longitud de los mismos usaremos Length(min=valor, max=varlor).

- para comprobar que la estructura de un correo electrónico sea válida además de usar el campo EmailField podemos usar el validador Email(). Eso si, como ya dije tienes que instalarlo previamente con pip install email-validator.

- para verificar cuando se registra una contraseña que esta es correcta se suele utilizar un segundo campo para que se vuelva a teclear. Se puede utilizar el validador EqualTo('nombre del campo a comparar', mensaje=None) para comparar el valor de los dos campos. 

- algunos campos que quieres que sean opcionales, hay ocasiones en que tiene que usar el validador Optional() para que te funcionen en el proyecto, ya que si no da error. No vale con no poner nada en los validadores. Este validador permite una entrada vacía y detiene la comprobación del resto de validadores. Si la entrada esta vacía también elimina errores anteriores (como errores de procesamiento) del campo.

Puedes ver unos cuantos en la documentación. No obstante el propio navegador también valida los campos que definimos si no especificamos lo contrario con "novalidate" dentro de la etiqueta form.


2) formulario_post.html

El siguiente paso que vamos a realizar es actualizar el contenido de la plantilla formulario_post.html que realizamos en el cápitulo anterior para que use la clase formulario_calculadora. El cambio principal es que la plantilla espera un objeto de la clase formulario_calculadora, que instanciaremos en la vista correspondiente en inicio.py, y que hemos llamado form 





En la plantilla podemos generar el formulario campo por campo o también podemos recorrerlo.

Generando el formulario campo por campo.

En el ejemplo de arriba puedes ver como dejamos a flask-Wtf que renderice la etiqueta (label) de cada campo con {{form.<nombre_del_campo>.label}} y que genere el código de cada campo con {{form.<nombre_del_campo>()}}

Si quieres utilizar alguna de las propiedades de la etiqueta de html5 puedes hacerlo. Por ejemplo si queremos poner la propiedad placeholder, que nos pone un texto apagado para que ayude al usuario a rellenar este campo teclearíamos:

{{form.nombre_del_campo(placeholder="texto a mostrar")}}

o si por ejemplo se quiere que un determinado campo no se autocomplete por el navegador usaríamos:
                
{{form.nombre_del_campo(autocomplete="off")}}

o que tenga un determinado tamaño, lo que también podríamos haber hecho al definir los validadores del campo con Length(min=, max=)

{{form.nombre_del_campo(size=32)}}


Os dejo un enlace de etiquetas html para el campo input y sus atributos.

Si quieres puedes dejar que el navegador valide los campos por ti, pero para ver mejor el uso de los validadores en el ejemplo, lo he desactivado añadiendo el atributo novalidate a la etiqueta del formulario. (Esto es opcional)

Además después de insertar cada campo podrías recorrer el diccionario errors para mostrar al usuario los posibles errores que pudiera haber en el mismo.

{% for error in form.nombre_campo.errors %}
            <span style="color: red;">{{ error }}</span>
{% endfor %} 

No obstante en el ejemplo he optado por recorrer el diccionario de errores y mostrar los errores y la etiqueta del campo que lo genera al final del formulario de la siguiente forma:

{% for field, errors in form.errors.items() %}
         <!-- Usamos ",".join(errors) para trasformar la lista en texto
         y que no salgan los corchetes por pantalla -->
        {{ form[field].label }}: {{",".join(errors)}}
{% endfor %}


Recorriendo el formulario.

Al utilizar un bucle for recorremos todos los campos del formulario y se generará la etiqueta y el propio campo.

{% for field in form %}
        {{field.label}}{{field}}<br/>
{% endfor %}


Seguridad en los formularios

Si te fijas justo después de la etiqueta form esta definido el siguiente campo:

{{form.csref_token}}

nota: También cumple la misma función {{form.hidden_tag()}}

Por defecto flask-wtf protege los formularios contra el ataque CSRF (Cross-Site Request Forgery  o falsificación de petición en sitios cruzados). Resumidamente este ataque se produce cuando un sitio web malicioso envía solicitudes a un sitio web en el que esta conectada la víctima. 

Para implementar esta protección anti CSRF, Flask-Wtf necesita que configuremos una clave de cifrado (SECRET_KEY), para poder generar tokens encriptados que se utilizarán para verificar la autenticidad de la petición. Vamos a ver como implementarla.

3 ) Vista para el formulario formulario_post.html.



Para no complicarnos, en el archivo inicio.py vamos a teclear esta instrucción (linea 7):

app.secret_key = 'clave de cifrado lo mas robusta posible'

o también puedes modificar el diccionario config del objeto app. En próximos post veremos las diferentes opciones que tiene este diccionario.

app.config["SECRET_KEY"]="clave de cifrado (no se la digas a nadie)"

Esta clave de cifrado, la pones tú y conviene que sea una clave o contraseña, lo más compleja posible.

Dentro de las bibliotecas standar de python está secrets que nos facilita esta labor. Puedes obtener un clave aceptable tecleando directamente en el shell de python

>>> import secrets
>>> secrets.token_hex(20)
'5c4fe301d80bebd89ad0fce78c6c9c1deb6d7667'

Si la contraseña va a formar parte de una url es mejor utilizar token_urlsafe() ya que esto nos garantiza que los caracteres utilizados son seguros de utilizar en una url.

>>> import secrets
>>> secrets.token_urlsafe(20)
'bbehSBYtWCFRfNOeFETwPm44fSE'
>>> 

Seguimos viendo los cambios hechos en la vista del archivo inicio.py.

Lo primero que hemos hecho es, como siempre, importar las bibliotecas de Flask necesarias para ejecutar la aplicación, pero además tenemos que importar la clase formulario_calculadora que hemos creado.

línea 3:

from forms import formulario_calculadora

y a continuación podemos instanciar o crear un objeto a partir de ella en la vista en la que la necesitemos.

línea 13:

form = formulario_calculadora()

El objeto form nos ofrece atributos y métodos para su gestión:

 form.validate_on_submit   Nos permite comprobar si el formulario ha sido enviado y es válido. 
 form.data  Nos ofrece un diccionario con los datos del formulario.
 form.errors  Si el formulario no es válido nos devuelve un diccionario con los 
 errores.
 form.primer_numero.data   Para cada campo (primer_numero es este ejemplo) nos devuelve su
 valor.
 form.primer_numero.errors   Es una tupla con los errores de validación del campo determinado.
 form.primer_campo  Nos devuelve el código html para generar ese campo.
 form.primer_numero.label    Nos devuelve el código html para generar la etiqueta del campo.

Puedes encontrar más atributos y métodos en la documentación de WTforms 

Enviando y gestionando la información del formulario.


Hemos utilizado el diseño que se recomienda en Flask para la creación de formularios

  • La primera vez que acedamos a la ruta lo haremos usando el método GET. Como en nuestro caso el formulario ni se ha validado ni enviado todavía, la intrucción "if form.validate_on_submit()" será falsa y no se ejecutará. Esto hará que se ejecute el ELSE siguiente, renderizando la plantilla formulario_post.html con los campos del fomulario, form, en blanco, ya que aún no tienen ningún dato.
  • Ahora si, rellenamos el formulario y le damos al botón de enviar. El programa lo valida y si es correcto lo envía con el método POST a la misma ruta.
  • Como "if form.validate_on_submit()" ahora es True, se gestiona la información enviada por el formulario y se realiza lo que se tenga que hacer con ella. En nuestro caso calcular un resultado.
  • Para terminar si todo el proceso ha ido bien se volverá a renderizar la plantilla incluyendo la variable resultado, y si algo ha ido mal se mostrará el texto "No se ha podido realizar la Operación".
El resultado final es algo parecido a esto:






No hay comentarios:

Publicar un comentario