Validación de formularios con WTForms

Cuando tienes que trabajar con datos de formularios enviados por una vista del navegador, el código se vuelve rápidamente muy difícil de leer. Existen librerías diseñadas para hacer este proceso más fácil de manejar. Una de ellas es WTForms que manejaremos aquí. Si te encuentras en la situación de tener muchos formularios, quizás quieras probarla.

Cuando se trabaja con WTForms hay que definir primero los formularios como clases. Recomiendo dividir la aplicación en múltiples módulos (Grandes aplicaciones como paquetes) para ello y añadir un módulo separado para los formularios.

Sacar el máximo partido a WTForms con una extensión

La extensión Flask-WTF amplía este patrón y añade algunos pequeños ayudantes que hacen que trabajar con formularios y Flask sea más divertido. Puedes conseguirla en PyPI.

Los formularios

Este es un ejemplo de formulario para una página de registro típica:

from wtforms import Form, BooleanField, StringField, PasswordField, validators

class RegistrationForm(Form):
    username = StringField('Username', [validators.Length(min=4, max=25)])
    email = StringField('Email Address', [validators.Length(min=6, max=35)])
    password = PasswordField('New Password', [
        validators.DataRequired(),
        validators.EqualTo('confirm', message='Passwords must match')
    ])
    confirm = PasswordField('Repeat Password')
    accept_tos = BooleanField('I accept the TOS', [validators.DataRequired()])

En la vista

En la función de la vista, el uso de esta forma se ve así:

@app.route('/register', methods=['GET', 'POST'])
def register():
    form = RegistrationForm(request.form)
    if request.method == 'POST' and form.validate():
        user = User(form.username.data, form.email.data,
                    form.password.data)
        db_session.add(user)
        flash('Thanks for registering')
        return redirect(url_for('login'))
    return render_template('register.html', form=form)

Fíjate que aquí estamos implicando que la vista está usando SQLAlchemy (SQLAlchemy en Flask), pero eso no es un requisito, por supuesto. Adapta el código como sea necesario.

Cosas para recordar:

  1. crear el formulario a partir del valor de la solicitud form si los datos se envían a través del método HTTP POST y args si los datos se envían como GET.

  2. para validar los datos, llama al método validate(), que devolverá True si los datos se validan, False en caso contrario.

  3. para acceder a valores individuales del formulario, accede a form.<NAME>.data.

Formularios en plantillas

Ahora al lado de las plantillas. Cuando pasas el formulario a las plantillas, puedes fácilmente renderizarlas allí. Mira la siguiente plantilla de ejemplo para ver lo fácil que es esto. WTForms ya hace la mitad de la generación de formularios por nosotros. Para hacerlo aún más bonito, podemos escribir una macro que renderice un campo con etiqueta y una lista de errores si los hay.

Aquí hay un ejemplo de plantilla _formhelpers.html con dicha macro:

{% macro render_field(field) %}
  <dt>{{ field.label }}
  <dd>{{ field(**kwargs)|safe }}
  {% if field.errors %}
    <ul class=errors>
    {% for error in field.errors %}
      <li>{{ error }}</li>
    {% endfor %}
    </ul>
  {% endif %}
  </dd>
{% endmacro %}

Esta macro acepta un par de argumentos de palabras clave que se envían a la función de campo de WTForm, que renderiza el campo por nosotros. Los argumentos de la palabra clave serán insertados como atributos HTML. Así, por ejemplo, puedes llamar a render_field(form.username, class='username') para añadir una clase al elemento de entrada. Ten en cuenta que WTForms devuelve cadenas estándar de Python, por lo que tenemos que decirle a Jinja2 que estos datos ya están escaneados en HTML con el filtro |safe.

Aquí está la plantilla register.html para la función que usamos arriba, que aprovecha la plantilla _formhelpers.html:

{% from "_formhelpers.html" import render_field %}
<form method=post>
  <dl>
    {{ render_field(form.username) }}
    {{ render_field(form.email) }}
    {{ render_field(form.password) }}
    {{ render_field(form.confirm) }}
    {{ render_field(form.accept_tos) }}
  </dl>
  <p><input type=submit value=Register>
</form>

Para obtener más información sobre WTForms, visite el sitio web de WTForms.