Flask Biography Tutorial Part VI : Adding Sign Up Form using Bootstrap 3 and Flask-WTF

 
 
   
    Figure VI-1 A secure Sign Up form with validation and nice visual layout
 

Welcome to the sixth instalment of this Bio Application Development Tutorial. I constructed this tutorial, in such a way that even a newcomer in Python will be able to grasp its content and follow it easily. Thanks to Python intuitive coding environment, my job is not that hard. But it is you who really decide whether my goals are met or I still have to refine how I presented ideas, instructions and concepts.

Previously, our Bio Application having its first data access capability by using a great ORM tool, SQLAlchemy. It able to construct Users tables, populate data and query those data.  But it still lack an important feature : a visitor still unable to register him/herself into our application. In this article, we are going to show you how exactly it is done.

There are many ways where you can present Sign Up form to your site visitors. As we use Twitter Bootstrap, it's good that we learn how Twitter itself presents Sign Up form to their site visitors. Try opening http://www.twitter.com, and you will see this persuasive Sign Up form:

Twitter persuasive Sign Up form
Figure VI-2 Twitter persuasive Sign Up form

What I mean by persuasive is, the Sign Up form is already open! What kind of visitor that didn't engaged to type their full name, email address, password and directly hit Sign Up button? Try to evaluate another website with sometimes not a clear way how to sign up or a long list of fields that is so tiring to look at for a signup form. It took guts just to sign up to them!

Clicking that Sign Up button will bring you to the full page of Sign Up form.

Twitter full page signup form 
Figure VI-3 Twitter full page signup form

This is where the signup process really occur! The first Sign Up form is just a teaser. Pretty smart isn't it?

In this part of our Bio Application Development Tutorial, we going to implement only the single full page Sign Up form part. We still let our Sign Up button in the homepage responsible for triggering this full page Sign Up form. Before continuing reading this article, take a moment to think about what steps that you are going to need to implement this new Sign Up page : what code that needs to be change, where do you change it and do we going to need another new file for this? Take your time!

A. Building Full Page SignUp Form 

A1. Steps That You Need 

Good.

Here is what we need to implement this new single full page Sign Up form:

  1. Make the Signup Now button on the Homepage open a new page.
  2. As we develop our web application on Flask framework, this mean we have to introduce a new route/view method on our application code (which is up until now, still reside on one single main.py file).
  3. This route/view method must render a new html page, let say signup.html (not surprisingly).

Great. Now, let's implement it.

A2. Create Route/View Method 

As the Signup Now button is just an HTML hyperlink element, <a>, this mean we have to supply its href attribute to an url in your application. How do you know what url path to supply? Simple. Implement our route/view method first and then use url_for method to let Flask create a correct URL for you.

This is our route/view method:

1
2
3
@application.route('/signup')
def signup():
    return render_template('signup.html', page_title = 'Signup to Bio Application')

And this is our Sign Up Now button in homepage:

1
<span class="btn btn-primary btn-lg">Signup now!</span>

Pretty self explanatory, right? It's pretty much the same in how we render our template in def index() method. In signup() method, we render a new html template named signup.html. While in templates/index.html in href attribute of Signup Now button we use url_for to generate a proper URL for signup() method. Don' get confused, as it's not always a URL having the same name with its route/view method. You can omit the use of url_for method and directly use /signup as the URL to trigger the signup() method in your application. But if later on you change the URL path but not the method name, you are going to have a 404 error page when user try to navigate to /signup.

A3. Designing Form Layout Using Bootstrap 3

As I am a coder with not so much great CSS skills (got to be honest here!), this is the most challenging steps so far. And as I finish it, I simply don't know how I can do this easily without Bootstrap. Or, the right question is, how do I design this with Bootstrap 3?   

Have a look at Twitter Full Page Signup Form in Figure VI-3. It's a rounded rectangle with margin in the left and right of it, containing all form elements. This rounded rectangle in turn is centered horizontally within web browser. This mean we have a nested container. Have a look at this layout scratch:

Signup Page Design Scratch
Figure VI-4 Signup Page Design Scratch

Using Bootstrap 3 CSS classes, here is code snippet of our new templates/signup.html.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{% extends "base-layout.html" %}
{% block content %}
<div class="row">
<div class="col-sm-2">
</div>
<div class="col-sm-8 well" style="background-color: #ffffff">
<div class="container">
<div class="row">
<div class="col-sm-2">
</div>
<div class="col-sm-8">
{{ form contents }}
</div>
<div class="col-sm-2">
</div>
</div>
</div>
</div>
<div class="col-sm-2">
</div>
</div>
{% endblock %}

We use Jinja2 extends mechanism for signup.html to create a new template based on existing base-layout.html. Recall that the block content is wrapped inside a Bootstrap container class, which will allow proper alignment and padding of row element. The first row is divided into three column, with the larger one being the container of another row class. The second row class is the class that eventually handled our Sign Up Form. Do a quick inspection, that all of the col-sm-* class if added, will summed up into 12. Hence, creating a perfect 12 Column Grid System. You can experience with other col-sm-* combinations to make the column width larger/smaller for certain column. You can also try to use other class beside sm (small), which is xs (extra small), md (medium desktop) and lg (large desktop). Just to be sure that the summed column number is 12.

For the rounded rectangle part, we use Bootstrap well class modifier. The default well will have greyish background. If we want it to be white, for simplicity sake, we can add a CSS inline style with background-color attribute set to white. If things get complex in the future, surely we will refactor this CSS inline style to separate CSS file in order it to be reusable by another template file.

A4. Designing Form and Its Input Fields

In general, laying out form and its input fields using Bootstrap 3 will have this class hierarchy : form -> form-group -> form-control. But depends on your need, you may choose from three kind of Bootstrap approach to form layout, which are:

  1. Basic : every input control will have 100% width. Great if you want to have a longer label for input control. Use <form class="form"> to use a Basic Layout Form. We are going to use this form layout for our Sign Up form.
  2. Inline : a very compact design, suitable to be used inside a navigation bar. Use <form class="form-inline"> to use an Inline Layout Form.
  3. Horizontal : this is a common layout for data entry form, with form label and input control in one row. Use <form class="form-horizontal"> to use a Horizontal Layout Form.

The choice of form type is greatly depend on your preference. In this Bio Application Sign Up form, we are going to use a basic layout form, trying to mimic Twitter Sign Up form.

A4-1. Basic Layout Form

A basic layout form is the easiest to setup, and depending on your need, it maybe the best too. You don't have to worry about screen space limitation, as the control will automatically have 100% horizontal width. Below is our sign up form using basic layout form.

Sign Up Form in Basic Layout Form
Figure VI-5 Sign Up Form in Basic Layout Form

The form layout is also perfected with validation text area, where we show error validation message. Below is the snippet of our sign up form in basic layout form:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<form action="/signup" class="form" method="POST">
        <div style="display: none">
        <input id="csrf_token" name="csrf_token" type="hidden" value="1383635238.9##d8f7f433e854cc682ade261cd9bb7113a147ee29" />
        </div>
        <div class="heading">
        <h4 class="form-heading">Publish your biography to the world!</h4>
        </div>
        <div class="form-group ">
        <label for="email">Email address</label>
        <input class="form-control" id="email" name="email" type="text" value="elawibowo@gmail.com" /> 
        <p class="help-block">
         
        
        </div>
        <div class="form-group  has-error ">
        <label for="password">Pick a secure password</label>
        <input class="form-control" id="password" name="password" type="password" />
        <p class="help-block">
        This field is required.
        
        </div>
        <div class="form-group  has-error ">
        <label for="username">Choose your username</label>
        <input class="form-control" id="username" name="username" type="text" /> 
        <p class="help-block">
        This field is required.
        
        </div>
        <div class="form-group  has-error ">
        <label class="checkbox">
        <input id="agree" name="agree" type="checkbox" value="y" /> <label for="agree">I agree all your </label></label>Terms of Services
        <p class="help-block">
        You must accept our Terms of Service
        
        </div>
        <button class="btn btn-success" type="submit">Sign Up</button>
</form>

We use the general form hierarchy class : form -> form-group -> form-control. For each form-control class, we simply put our label, input field and help-block there.  This making a basic layout form the easiest form to setup.  

A4-2. Horizontal Layout Form

As the horizontal layout form is so common in data entry application, I think it's important if I also show you how to create a horizontal layout form. Have a look at the result of making our Sign Up form as horizontal layout form (complete with error validation text):


Figure VI-6 Horizontal Layout Form

To achieve a good result with a horizontal layout form, you have to make sure the row that will contain the form contains just enough column width. If previously I use a col-sm-2, col-sm-8 and col-sm-2 for a basic form layout, here I use a col-sm-1, col-sm-10 and col-sm-1 setup. This will allow more space for form in col-sm-10 column, resulting in a much better experience for a horizontal layout form. Below is a snippet for a horizontal layout form:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<form action="/signup" class="form-horizontal" method="POST">
        <div style="display: none">
        <input id="csrf_token" name="csrf_token" type="hidden" value="1383635907.93##3ea432bba94e0eb0f769c169ced98e0a8196bdd7" />
        </div>
        <div class="heading">
        <h4 class="form-heading">Publish your biography to the world!</h4>
        </div>
        <div class="form-group ">
        <label class="col-sm-4" for="email">Email address</label>
        <div class="col-sm-3">
        <input class="form-control" id="email" name="email" type="text" value="elawibowo@gmail.com" /> 
        </div>
        <p class="col-sm-5 help-block">
         
        
        </div>
        <div class="form-group  has-error ">
        <label class="col-sm-4" for="password">Pick a secure password</label>
        <div class="col-sm-3">
        <input class="form-control" id="password" name="password" type="password" />
        </div>
        <p class="col-sm-5 help-block">
        This field is required.
        
        </div>
        <div class="form-group  has-error ">
        <label class="col-sm-4" for="username">Choose your username</label>
        <div class="col-sm-3">
        <input class="form-control" id="username" name="username" type="text" />
        </div>
        <p class="col-sm-5 help-block">
        This field is required.
        
        </div>
        <div class="form-group  has-error ">
        <label class="checkbox col-sm-7">
        <input id="agree" name="agree" type="checkbox" value="y" /> <label for="agree">I agree all your </label></label>Terms of Services
        <p class="col-sm-5 help-block">
        You must accept our Terms of Service
        
        </div>
        <button class="btn btn-success" type="submit">Sign Up</button>
</form>

Using a form-horizontal class will make your form-group class behave as it is a row class, meaning you can easily add column class there (col-xs-*, col-sm-*, col-md-* and col-lg-*). Have a look at how I define each form-group to behave as a row having three columns, each with a col-sm-4 (for the control label), col-sm-3 (for the input fields) and col-sm-5 (for the validation error). This will allow a very neat placement of form input fields.

Do a quick comparison with our basic form layout earlier, and you can easily understand that a horizontal layout form require more horizontal space. Because of this need, we have to modify our layout earlier and come up with layout having more horizontal space as below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div class="row">
<div class="col-sm-1">
</div>
<div class="col-sm-10 well" style="background-color: #ffffff">
<div class="container">
<div class="row">
<div class="col-sm-12">
{{ form contents }}
</div>
</div>
</div>
</div>
<div class="col-sm-1">
</div>
</div>

In this layout, inside the well I simply create a single column layout using col-sm-12. This give a much wider column width for each form-group classes, which later will create another three col-sm-* as mentioned earlier.  Another approach to allow more horizontal space for your input fields is to make the validation error behave as a floating layout alert. Try experimenting with it! 

For easy reference, I save this horizontal layout form in the file wsgi/templates/signup-horizontal.html.  

B. Implementing SignUp Form using Flask-WTF

B1. Defining Form Class

You have already saw our signup form code snippet, consisting the regular HTML tag elements <form>, <input>, <button>, etc. But a form in a web application is not just a simple form elements as shown. A professional web form must also include a proper field level validation mechanism which will prevent your application to process invalid data. Imagine having code that trying to process an empty username data, in which this data is required. Surely we can handled that empty data in our processing code, but of course it's easier if we can prevent our processing code to process invalid data, by making another layer with its sole purpose is to validate data in web form. This is the main task of Flask-WTF.

Flask-WTF is an integration layer for your Flask application to access features offered by WTForms, which is a popular python package to handle form input and validation. If previously you had already a develop data aware web applications, you will know that not all application model/table can be mapped into its web form. This is where WTForms come handy. You will have to create another class for your web form, consisting of all input fields to be presented to user. Another question may arise, "What, another class? Another layer? Why not just use our existing Users application model for this?". As I said earlier, it's not always an application model can be mapped to its web form. A simple CRUD form maybe. But a complex/simplified (hence, user friendly) form is not.

Before starting to use Flask-WTF, you will have to import the dependency first:

1
2
3
4
5
from flask.ext.wtf import Form
from wtforms import TextField, PasswordField, validators, HiddenField
from wtforms import TextAreaField, BooleanField
from wtforms.validators import Required, EqualTo, Optional
from wtforms.validators import Length, Email

Beside that, there are two configurations that need to be added in your application.config dictionary:

1
2
application.config['CSRF_ENABLED'] = True
application.config['SECRET_KEY'] = 'rahasiabesar'

Flask-WTF come with prebuilt CSRF prevention. The only things that you need to do is to enable it and define your secret key. That's all

Now, let's just start with our SignUp form code, pasted below:

1
2
3
4
5
6
7
8
9
10
11
class SignupForm(Form):
   email = TextField('Email address', validators=[
           Required('Please provide a valid email address'),
           Length(min=6, message=(u'Email address too short')),
           Email(message=(u'That\'s not a valid email address.'))])
   password = PasswordField('Pick a secure password', validators=[
           Required(),
           Length(min=6, message=(u'Please give a longer password'))])
   username = TextField('Choose your username', validators=[Required()])
   agree = BooleanField('I agree all your Terms of Services',
           validators=[Required(u'You must accept our Terms of Service')])

Your form class must be derived from Form class, which is the container of WTForms fields.  Each field will be rendered to the correct HTML input elements by using its Widget instance. WTForms defined a complete set of HTML input fields which you can refer to this documentation. Here, we only use a TextField (will be rendered to text input) and BooleanField (will be rendered to checkbox).

The interesting part of this fields definition is the validators array. You can add any WTForms validator to this validators array and let WTForms validate data sent by your user. Here, I use Required (assuring us that our user will eventually input this data), Email (it will only accept valid email address) and Length (our user will be forced to input data with certain minimum length).

Our next question is, how do we relate this form class to the real HTML form in our signup page? Read on! 

B2. Creating and Rendering Form Template 

To render the form, we must modify our existing signup() method we defined earlier in part A2. Beside simply calling render_template using signup.html as the template, our method must also instaniate our newly created SignUp form as follows:

1
2
3
@application.route('/signup')
def signup():
    return render_template('signup.html', form = SignupForm(), page_title = 'Signup to Bio Application')

We simply pass an instance of SignupForm to render_template, making all of its properties and methods available in signup.html.

"What will be the content of our signup.html? As you already pasted a bunch of HTML snippet earlier, do I have to type those in my editor?", you may be wondering. The answer are 1) that snippet was generated by Flask-WTF, I just Copy&Paste it from Google Chrome and 2) you don't have to type all of those HTML. As you already passed SignUp instance to signup.html, from inside signup.html you can easily refer to your SignUp form fields. Snippet of signup.html pasted below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<form action="{{url_for('signup')}}" class="form-horizontal" method="POST">
        {{ form.hidden_tag() }}
        <div class="heading">
        <h4 class="form-heading">Publish your biography to the world!</h4>
        </div>
        <div class="form-group {% if form.email.errors %} has-error {% endif %}">
        {{form.email.label(class="col-sm-4")}}
        <div class="col-sm-3">
        {{ form.email(placeholder="E.g. brucewayne@wayne.com", class="form-control") }} 
        </div>
        <p class="col-sm-5 help-block">
        {{ form.email.errors[0] }}
        
        </div>
        <div class="form-group {% if form.password.errors %} has-error {% endif %}">
        {{form.password.label(class="col-sm-4")}}
        <div class="col-sm-3">
        {{ form.password(placeholder="Min. 6 Characters", class="form-control") }}
        </div>
        <p class="col-sm-5 help-block">
        {{ form.password.errors[0] }}
        
        </div>
        <div class="form-group {% if form.username.errors %} has-error {% endif %}">
        {{form.username.label(class="col-sm-4")}}
        <div class="col-sm-3">
        {{form.username(placeholder="E.g. ashwinhegde", class="form-control")}}
        </div>
        <p class="col-sm-5 help-block">
        {{ form.username.errors[0] }}
        
        </div>
        <div class="form-group {% if form.agree.errors %} has-error {% endif %}">
        <label class="checkbox col-sm-7">
        {{form.agree}} {{form.agree.label}}
        </label>
        <p class="col-sm-5 help-block">
        {{ form.agree.errors[0] }}
        
        </div>
        <button class="btn btn-success" type="submit">Sign Up</button>
</form>

I hope you did not get overwhelmed by the snippet above. It's actually a complexity of WTForm coupled with Bootstrap 3 basic form layout. If we extracted the core features, it will be just as follows:

  1.  <form action="{{url_for('signup')}}" . We told Flask that the method that will handle user submitted signup form is signup() method.
  2. As WTForms include a built-in CSRF prevention attack, we simply have to call form.hidden_tag(). It will create special token for your current form.
  3. Inspect a form-group class, for example the one that involved in rendering email input field. In the form-group opening div, we add an {% if %} statement, checking whether there are validation errors. If it is, we simply use Bootstrap 3 has-error modifier. This will render our input field with error validation style. You may also experiment with other validation style class, such as has-warning and has-success.
  4. To generate input field label, we simply use {{form.email.label}}. It will generate a <label> element. But, as the label need another Bootstrap attribute  class="col-sm-4", hence we instantiate form.email.label, complete with the required attribute: {{form.email.label(class="col-sm-4")}}. 
  5. To generate the actual input field, we use {{form.email}}. And as it also need another Bootstrap attribute class="form-control" and placeholder, the same rule apply when we render the actual input element: we instantiate it complete with the required attribute, {{ form.email(placeholder="E.g. brucewayne@wayne.com", class="form-control") }}.
  6. Lastly, we render the validation error in the last column using statement: {{ form.email.errors[0] }}.

Great!

If you have finished your code until this point, now you can  safely run it in localhost or even push it to Openshift.

B3. Handling Submitted Form

B3-1. Making POST Method

We already have a signup form class, form template and method that will render the form. Our next step question is, how do we handle submitted form? If you try to to press that green Sign Up button, your browser will display something like:

Trying to send form to a non POST method
Trying to send form to a non POST method

This is because our signup() method, by default, was only configured to handle GET request, not POST request. To configure our signup() method to be able to handle POST method, is as simply as adding methods argument in application.route decorator as follows:

1
2
3
@application.route('/signup', methods=['GET', 'POST'])
def signup():
    return render_template('signup.html', form = SignupForm(), page_title = 'Signup to Bio Application')

Try resubmitting the form after adding that methods argument. Have your form validate its input fields?

B3-2. Validating Input 

Not yet!

Our form submitted data was not yet validated against the already defined validators. But, as now our signup() method already configured to accept POST data, we can modify our signup() method as follows to start validating its input field:

1
2
3
4
5
6
7
8
9
@application.route('/signup', methods=['GET', 'POST'])
def signup():
    if request.method == 'POST':
        form = SignupForm(request.form)
        if form.validate():
            pass
        else:
            return render_template('signup.html', form = form, page_title = 'Signup to Bio Application')
    return render_template('signup.html', form = SignupForm(), page_title = 'Signup to Bio Application')

Up until now, this is the most complicated Python code I presented in this article  series. It use nested if statement. The first if statement is to test whether this signup() method was called as a result of a POST request. If not, we simply return render_template with a newly instantiated empty SignupForm object. If it is indeed a POST request, first we instantiated a SignupForm() object using form data sent by browser. The result is not an empty SignupForm object, but a SignupForm object filled with data sent by browser.

After that, we validate its data by calling form.validate() method. This is the sole code that responsible in validating form data sent by browser (hence, by our users). If the validation return True, well, we pass it for now. In the next immediate section we will handle user registration. But if the validation return False, we  return ender_template with previous form submitted data. This way, our users will not have to retyped all of his/her previous input. Better yet, WTForms is smart enough to not pass around our user previous typed password. Users must retyped his/her password again.


 

B3-3. Registering User

After we have a valid data input from sign up form, the task of registering a user can be summed up as follows:

  1. Checking whether the email and/or username already taken or not
  2. If yes, return user to Sign Up page with the informative error message
  3. If no, insert user data into Users table and bring user to Sign Up Success Page

Below is our enhanced signup() method. Observe that, we only expand the single line pass statement in the previous signup() method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@application.route('/signup', methods=['GET', 'POST'])
def signup():
    if request.method == 'POST':
        form = SignupForm(request.form)
        if form.validate():
            user = Users()
            form.populate_obj(user)
            user_exist = Users.query.filter_by(username=form.username.data).first()
            email_exist = Users.query.filter_by(email=form.email.data).first()
            if user_exist:
                form.username.errors.append('Username already taken')
            if email_exist:
                form.email.errors.append('Email already use')
            if user_exist or email_exist:
                return render_template('signup.html', form = form, page_title = 'Signup to Bio Application')
            else:
                user.firstname = "Firstname"
                user.lastname = "Lastname"
                user.tagline = "Tagline of how special you are"
                user.bio = "Explain to the rest of the world why you are the very most unique person to have a look at"
                user.avatar = '/static/batman.jpeg'
                db.session.add(user)
                db.session.commit()
                return render_template('signup-success.html', user = user, page_title = 'Sign Up Success!')
        else:
            return render_template('signup.html', form = form, page_title = 'Signup to Bio Application')
    return render_template('signup.html', form = SignupForm(), page_title = 'Signup to Bio Application')



If form data is valid, here are what the code does:

  1. Create a new empty User class
  2. Populate it with user data returned from form data  using form.populate_obj() method, which contains email, username and password
  3. Check whether username and email already registered in our database
  4. If it is, add an informative validation message in related fields. Brought back user to Sign Up page
  5. If both username and email address are free to use, we initialise our user profile with default values, insert it to our Users table and bring user to our warm welcoming sign up success page

Pretty easy to understand, right? Below is our warm welcoming sign up success page:

A warm welcoming sign up success page

 

Of course the Sign In button won't work as expected right now. It will just bring user to our Homepage 

C. Conclusion

In this sixth instalment, we have discuss the topic of form layout using supported Bootstrap 3 form layout. We also working on how to correctly render and validate our input fields using Flask-WTF. And, lastly, we do the actual registration of our newly sign up user. That's a pretty much broad topic to grasp! Make sure to understand it fully before working on to our next step of this tutorial : User Sign In using Flask-Login.

Actually, there are two important steps that was skipped in this article, which are:

  1. Sending confirmation email to our newly signed up user
  2. Protecting saved password using Hash/Encryption. 

Both of those topic will be covered in later part of this article series.

As always, you can download the working source code of this application here : bio-part-6.zip.

And you can always test it live here : http://bio-ekowibowo.rhcloud.com

Stay tuned! 


 

 

 




Leave comments

authimage
  • Thanks for your correction! :)
    It really is something that got to do with how LifeType manage url content. Somehow it just prefixed it with this domain

    "p.s. i really enjoy your tutorial. thank you for putting all this effort!"

    • eko
  • oops,

    for some reason it shows the buttons in the comment and not what i wrote (you should add cgi.escape :-) )

    the href should be just: {{ url_for('signup')}}

    • greg
  • Hi Eko,

    there is a typo in the signup button href (under A2. Create Route/View Method)

    you wrote: Signup now!

    I think it should be:
    Signup now!

    cheers,

    p.s. i really enjoy your tutorial. thank you for putting all this effort!

    • greg

Copyright(c) 2014 - PythonBlogs.com
By using this website, you signify your acceptance of Terms and Conditions and Privacy Policy
All rights reserved