专注细节
努力进步

Flask-Web Forms

Web forms

Configuration

To handle our web forms we are going to use the Flask-WTF extension, which in turn wraps the WTForms project in a way that integrates nicely with Flask apps.

Many Flask extensions require some amount of configuration, so we are going to setup a configuration file inside our root microblog folder so that it is easily accessible if it needs to be edited.

WTF_CSRF_ENABLED = True
SECRET_KEY = '123test'

It’s just two settings that our Flask-WTF extension needs. The WTF_CSRF_ENABLED setting activates the cross-site request forgery.The SECRET_KEY setting is only needed when CSRF(an attack method) is enabled, and is used to create a cryptographic token that is used to validate a form. When you write your own apps make sure to set the secret key to something that is difficult to guess.

Now that we have our config file we need to tell Flask to read it and use it.

from flask import Flask
app = Flask(__name__)
app.config.from_object('config')

from app import views

The user login form

Web froms are represented in Flask-WTF as classes, subclassed from base class From. A form subclass simply defines the fields of the form as class variables.
Now we will create a login form that users will use to identify with the system. The mechanism that we will support in our app is not the standard username/password type, we will have our users login using their OpenID. The benefit is that the authentication is done by the provider of the OpenID, so we don’t have to validate passwords, which make our site more secure to our users.
The OpenID login only requires one string, the so called OpenID.

from flask.ext.wtf import Form
from wtforms import StringField, BooleanField
from wtforms.validators import DataRequired

class LoginForm(Form):
    openid = StringField('openid', validators=[DataRequired()])
    remember_me = BooleanField('remember_me',default = False)

We import the Form class, and the two form field classes that we need, StringField and BooleanField.
The DataRequired import is a validator, a function that can be attached to a field to perform validation on the data submitted by the user.

Form templates

<!-- extend from base layout -->
{% extends "base.html" %}


{% block content %}
<h1>Sign In</h1>
<form action="" method="post" name="login">
    {{form.hidden_tag()}}
    <p>
        Please enter your openID:<br>
        {{ form.openid(size=80)}}<br>
    </p>
    <p>{{ form.remember_me }} Remember Me</p>
    <p><input type="submit" value="Sign In"></p>
</form>
{% endblock %}

In this template we are reusing the base.html template through the extends template inheritance statement.
The form.hidden_tag() template argument will get replaced with a hidden field that implements the CSRF prevention that we enable in the configuration. This field should be in all your forms if you have CSRF enbaled. The good news is that Flask-WTF handles it for use, we just need to make sure it it included in the form.
The actual fields of our form are rendered by the field objects, we just need to refer to a {{form.field_name}} template argument in the place where each field should be inserted.

Form views

It’s actually simple since we just need to pass a form object to the template.

from flask import render_template, flash, redirect
from app import app
from .forms import LoginForm

# index view function suppressed for brevity

@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    return render_template('login.html', 
                           title='Sign In',
                           form=form)

So basically, we have imported our LoginForm class,and instantiated an object from it, sent it down to the template.
The route decorator accept the GET and POST request. Without this method argument, the route will only accept GET requests.

Receiving from data

The validate_on_submit method does all the form processing work. When validate_on_submit is called as part of a form submission request, it will gather all the data, run all the validators attached to fields, and if everything is all right it will return True, indicating that the data is valid and can be processed. This is your indication that this data is safe to incorporate into the application.
When validate_on_submit returns True, our module will calls two new functions: flash and redirect.The flash is very useful when production servers provide feedback to the user regarding an action.

In order to use the flash, we modify the base.html template.

<html>
  <head>
    {% if title %}
    <title>{{ title }} - microblog</title>
    {% else %}
    <title>microblog</title>
    {% endif %}
  </head>
  <body>
    <div>Microblog: <a href="/index">Home</a></div>
    <hr>
    {% with messages = get_flashed_messages() %}
      {% if messages %}
        <ul>
        {% for message in messages %}
            <li>{{ message }} </li>
        {% endfor %}
        </ul>
      {% endif %}
    {% endwith %}
    {% block content %}{% endblock %}
  </body>
</html>

Improving field validation

What we are missing is an indication to the user of what is wrong with the form. Luckily, Flask-WTF also makes this an easy task.

When a field fails validation Flask-WTF adds a descriptive error message to the form object. These messages are available to the template, so we just need to add a bit of logic that renders them.

Modify the login.html to display some error messages. Use a loop to display the errors messages.

<!-- extend base layout -->
{% extends "base.html" %}

{% block content %}
  <h1>Sign In</h1>
  <form action="" method="post" name="login">
      {{ form.hidden_tag() }}
      <p>
          Please enter your OpenID:<br>
          {{ form.openid(size=80) }}<br>
          {% for error in form.openid.errors %}
            <span style="color: red;">[{{ error }}]</span>
          {% endfor %}<br>
      </p>
      <p>{{ form.remember_me }} Remember Me</p>
      <p><input type="submit" value="Sign In"></p>
  </form>
{% endblock %}

Dealing with OpenIDs

In practice, we will find that a lot of people don’t even know that they already have a few OpenIDs.
To make it easier for users to login to our site with one of these OpenIDs, we add links to a short list of them.

Now let’s see how we use this array in our login view function:

@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        flash('Login requested for OpenID="%s", remember_me=%s' %
              (form.openid.data, str(form.remember_me.data)))
        return redirect('/index')
    return render_template('login.html', 
                           title='Sign In',
                           form=form,
                           providers=app.config['OPENID_PROVIDERS'])

Then we modify the login.html to get these provider.

<!-- extend base layout -->
{% extends "base.html" %}

{% block content %}
<script type="text/javascript">
function set_openid(openid, pr)
{
    u = openid.search('<username>')
    if (u != -1) {
        // openid requires username
        user = prompt('Enter your ' + pr + ' username:')
        openid = openid.substr(0, u) + user
    }
    form = document.forms['login'];
    form.elements['openid'].value = openid
}
</script>
<h1>Sign In</h1>
<form action="" method="post" name="login">
    {{ form.hidden_tag() }}
    <p>
        Please enter your OpenID, or select one of the providers below:<br>
        {{ form.openid(size=80) }}
        {% for error in form.openid.errors %}
          <span style="color: red;">[{{error}}]</span>
        {% endfor %}<br>
        |{% for pr in providers %}
          <a href="javascript:set_openid('{{ pr.url }}', '{{ pr.name }}');">{{ pr.name }}</a> |
        {% endfor %}
    </p>
    <p>{{ form.remember_me }} Remember Me</p>
    <p><input type="submit" value="Sign In"></p>
</form>
{% endblock %}

Here we can’t actually login in our system. We will do it later.

分享到:更多 ()