This tutorial describes how to develop a simple phonebook application using gae-init as a starting point. By building a personal phonebook, you will learn how to develop, build, run, and deploy your applications.

Before you begin you should go through the requirements and make sure that you have: Google App Engine, Node.js, pip, virtualenv and Git.

We are also going to assume that you will follow all the suggested names for the directories and files, because in some steps we might refer to what was suggested without further explanations. If you decide to have your own names, continue at your own risk.

In this tutorial we're not going to explain how Google App Engine works, or teach you how to code in Python. So if you are unfamiliar with these topics, you should at least finish the Getting Started: Python 2.7 before continuing with this one.

Clone the gae-init project to get the code locally on your computer using Git:

$ git clone https://github.com/gae-init/gae-init.git phonebook

If you are more familiar with Mercurial use:

$ hg clone https://bitbucket.org/gae-init/gae-init phonebook

This will create a new directory that is called phonebook including another directory called main which contains all the source files, libraries and more.

We assume that you already know how to run the Google App Engine applications from a command line, but if not you should refer to the documentation. Execute in your terminal the run.py from the root directory

$ cd /path/to/phonebook
$ ./run.py -s

If everything went smoothly you can test the application by visiting the following URL in your web browser:

http://localhost:8080/

Feel free to spend some time by clicking on the different menus. As a first change within the app you can sign in as administrator and after visiting the http://localhost:8080/admin/config/ change the Brand Name from gae-init to Phonebook (or to anything else you like). By doing that you should see on the top left corner the new name for your application.

As we are building a personal phonebook, we are going to need to store the contact list in a datastore.

Create a new file called contact.py in the main/model directory with the following contents:

from google.appengine.ext import ndb
import model

class Contact(model.Base):
  user_key = ndb.KeyProperty(kind=model.User, required=True)
  name = ndb.StringProperty(required=True)
  email = ndb.StringProperty(default='')
  phone = ndb.StringProperty(default='')
  address = ndb.StringProperty(default='')

The user_key property will store the User's key to make the contact list personal for every user. The rest of the properties are self explanatory.

Finally import it in the main/model/__init__.py file like this:

from .contact import Contact

There are a few things that we need to do in order to start adding new contacts into the datastore. In short we're going to create a Form, a Handler and a Template.

Contact Create Form

Create a new file contact.py in the main directory and add the following code that will be responsible for validating the user's input.

from flask.ext import wtf
import wtforms

class ContactUpdateForm(wtf.Form):
  name = wtforms.StringField('Name', [wtforms.validators.required()])
  email = wtforms.StringField('Email', [wtforms.validators.optional(), wtforms.validators.email()])
  phone = wtforms.StringField('Phone', [wtforms.validators.optional()])
  address = wtforms.TextAreaField('Address', [wtforms.validators.optional()])

For more information regarding the form validation refer to Flask-WTForms.

Contact Create Handler

After creating the form we have to create a handler for it. Add the following code into the main/contact.py file.

import flask
import auth
import model
from main import app

@app.route('/contact/create/', methods=['GET', 'POST'])
@auth.login_required
def contact_create():
  form = ContactUpdateForm()
  if form.validate_on_submit():
    contact_db = model.Contact(
        user_key=auth.current_user_key(),
        name=form.name.data,
        email=form.email.data,
        phone=form.phone.data,
        address=form.address.data,
      )
    contact_db.put()
    return flask.redirect(flask.url_for('welcome'))
  return flask.render_template(
      'contact_create.html',
      html_class='contact-create',
      title='Create Contact',
      form=form,
    )

Let's take a closer look in this new snippet

import flask
import auth
import model
from main import app

The necessary imports for creation handler, just put them in the beginning of the main/contact.py file with the rest of the imports.

@app.route('/contact/create/', methods=['GET', 'POST'])

The route and the methods that we are going to use. GET is to serve the html form and POST is to submit the data.

For more information refer to Flask documentation on routing.

@auth.login_required

This decorator's purpose is to make sure that who ever is entering this URL will be already signed in so we could use the user_key of the authenticated user. If the user is not logged in, she will be redirected to the sign-in page and then back to this URL.

Contact Create Template

After creating the form and a handler, we are going to need a template to be able to get the user data. Create a new file contact_create.html in the templates directory and paste the following code there:

# extends 'base.html'
# import 'macro/forms.html' as forms

# block content
  <div class="page-header">
    <h1>{{title}}</h1>
  </div>
  <div class="row">
    <div class="col-md-4">
      <form method="POST" action=".">
        <fieldset>
          {{form.csrf_token}}

          {{forms.text_field(form.name, autofocus=True)}}
          {{forms.email_field(form.email)}}
          {{forms.text_field(form.phone)}}
          {{forms.textarea_field(form.address)}}

          <button type="submit" class="btn btn-primary btn-block">
            Create Contact
          </button>
        </fieldset>
      </form>
    </div>
  </div>
# endblock

Finalise Contact Creation

Before we can actually test the contact creation there are couple of things that we have to complete.

Import contact.py

We will have to import the contact.py in the main.py, because otherwise the Flask application won't be able to figure out the routing rules. Include the following line bellow the rest of the imports in the main.py file.

import contact

Adding a link on the top bar

The most important thing to make the user experience better, we will have to create a link for the users to be able to start adding new contacts in their phonebook. Add the lines 2 - 4 inside the <ul class="nav">...</ul> element that you will find in the header.html file that is located in the templates/bit directory.

<ul class="nav navbar-nav">
  <li class="{{'active' if html_class == 'contact-create'}}">
    <a href="{{url_for('contact_create')}}"><i class="fa fa-file"></i> Create Contact</a>
  </li>
  ...
</ul>

Testing Contact Creation

If your development server is still running, then by visting the http://localhost:8080/ you should notice the Create Contact link on the top bar and you should be able to actually start creating new contacts!

Create couple of new contacts and also try to add a contact without a name, or try to enter an invalid email and then press on Create Contact button.

Since the contact list is not visible in the application yet, you can visit the Google App Engine's admin console to make sure that the contacts are being added, by visiting the admin console: http://localhost:8081/datastore.

In order to present the contacts we will have to do three things: create the handler that will retrieve the contacts from the datastore, the template to present them in the browser and the link on the top bar to access this list.

Contact List Handler

Add the following code to the contact.py file:

import util

@app.route('/contact/')
@auth.login_required
def contact_list():
  contact_dbs, contact_cursor = model.Contact.get_dbs(
      user_key=auth.current_user_key(),
    )

  return flask.render_template(
      'contact_list.html',
      html_class='contact-list',
      title='Contact List',
      contact_dbs=contact_dbs,
      next_url=util.generate_next_url(contact_cursor),
    )

Contact List Template

After creating the handler, we are going to need a template to be able to present a table of all the contacts that we've added so far. Create a new file contact_list.html in the templates directory and paste the following code there:

# extends 'base.html'

# block content
  <div class="page-header">
    <h1>{{title}}</h1>
  </div>
  <table class="table table-bordered">
    <thead>
      <tr>
        <th>ID</th>
        <th>Name</th>
        <th>Email</th>
        <th>Phone</th>
        <th>Address</th>
      </tr>
    </thead>
    <tbody>
      # for contact_db in contact_dbs
        <tr>
          <td>{{contact_db.key.id()}}</td>
          <td>{{contact_db.name}}</td>
          <td>{{contact_db.email}}</td>
          <td>{{contact_db.phone}}</td>
          <td>{{contact_db.address}}</td>
        </tr>
      # endfor
    </tbody>
  </table>
# endblock

You can test the handler and the above template by visiting the url http://localhost:8080/contact/, but in the next section we'll add a link to make it easier to access.

Finalise Contact Listing

As a final touch we're going to do two things: adding the link in the top bar and changing the redirect after creating the contact to take us to the this list instead of going back to the welcome page.

Adding a link on the top bar

Add the lines 3 - 5 inside the <ul class="nav">...</ul> element that you will find in the header.html file that is located in the templates/bit directory.

<ul class="nav">
  ...
  <li class="{{'active' if html_class == 'contact-list'}}">
    <a href="{{url_for('contact_list')}}"><i class="fa fa-list"></i> Contact List</a>
  </li>
  ...
</ul>

After refreshing the page (http://localhost:8080/), you should be able to see the link on the top and if you're in watching the contact list it should also be active.

Changing the redirect

After creating a new contact we will now redirect the user to the contact list instead of the welcome page and also and also show her a message that the new contact was successfully created.

Find the following line in the contact_create handler:

return flask.redirect(flask.url_for('welcome'))

and replace it with the following:

flask.flash('New contact was successfully created!', category='success')
return flask.redirect(flask.url_for('contact_list', order='-created'))

The first line is to flash the message that the creation was successful. The argument category is optional and can be one of the following: warning (default), danger, success or info.

In the second line notice that we are using the order='-created' in the url_for function, which will be translated to: http://localhost:8080/contact/?order=-created. You can choose whatever order you like and you can also play with the url by providing different orders for the fields that you have in the Contact model. Use the - if you want the inverse order and , to combine more than one field for order.

In order to view the contact we will have to do three things: create the handler that will retrieve the contact from the datastore, the template to view her in the browser and the link on the contact list for easy access.

Contact View Handler

Add the following code to the contact.py file:

@app.route('/contact/<int:contact_id>/')
@auth.login_required
def contact_view(contact_id):
  contact_db = model.Contact.get_by_id(contact_id)
  if not contact_db or contact_db.user_key != auth.current_user_key():
    flask.abort(404)
  return flask.render_template(
      'contact_view.html',
      html_class='contact-view',
      title=contact_db.name,
      contact_db=contact_db,
    )

Contact View Template

After creating the handler, we are going to need a template to be able to present a contact's personal page. Create a new file contact_view.html in the templates directory and paste the following code there:

# extends 'base.html'

# block content
  <div class="page-header">
    <h1>{{title}}</h1>
  </div>
  <p>{{contact_db.email}}</p>
  <p>{{contact_db.phone}}</p>
  <p>{{contact_db.address}}</p>
  <hr>
  <p>
    <a href="{{url_for('contact_list')}}">Back</a>
  </p>
# endblock

It's hard to test this form right now because we have to create manually the URL, but in the next section we'll add a link to make it easier to access.

Finalise Contact View

As a final touch in order to view the contacts we'll modify the contact list so when you click on their names you should be able to view them.

Find the line that renders the name of the contact and replace it with the following:

...
<td>
  <a href="{{url_for('contact_view', contact_id=contact_db.key.id())}}">
    {{contact_db.name}}
  </a>
</td>
...

Now visit the contact list (http://localhost:8080/contact/), and you should be able to click on the name of each contact and view their properties.

In order to update the contact we will have to do three things: create the handler that will retrieve the contact from the datastore, the template to update her in the browser and the link on the contact list for easy access.

In this step there are very similar actions on what we have to do that we've seen in creation and viewing of the contact. So in the final step we'll combine the create and update templates to maintain only one.

Contact Update Handler

Add the following code to the contact.py file:

@app.route('/contact/<int:contact_id>/update/', methods=['GET', 'POST'])
@auth.login_required
def contact_update(contact_id):
  contact_db = model.Contact.get_by_id(contact_id)
  if not contact_db or contact_db.user_key != auth.current_user_key():
    flask.abort(404)
  form = ContactUpdateForm(obj=contact_db)
  if form.validate_on_submit():
    form.populate_obj(contact_db)
    contact_db.put()
    return flask.redirect(flask.url_for('contact_list', order='-modified'))
  return flask.render_template(
      'contact_update.html',
      html_class='contact-update',
      title=contact_db.name,
      form=form,
      contact_db=contact_db,
    )

Contact Update Template

After the handler, we are going to need a template to be able to update the user's details. Create a new file contact_update.html in the templates directory and paste the following code there:

# extends 'base.html'
# import 'macro/forms.html' as forms

# block content
  <div class="page-header">
    <h1>{{title}}</h1>
  </div>
  <div class="row">
    <div class="col-md-4">
      <form method="POST" action=".">
        <fieldset>
          {{form.csrf_token}}
          {{forms.text_field(form.name, autofocus=True)}}
          {{forms.email_field(form.email)}}
          {{forms.text_field(form.phone)}}
          {{forms.textarea_field(form.address)}}
          <button type="submit" class="btn btn-primary btn-block">
            Update Contact
          </button>
        </fieldset>
      </form>
    </div>
  </div>
# endblock

Finalise Contact Update

As a final touch we're going to do two things: add some links to the contact list to access the update contacts page easier and also we'll get rid of the contact_create.html because it is very similar to the contact_update.html.

Add a link on the contact list

Find the line that renders the ID of the contact and replace it with the following:

...
<td>
  <a href="{{url_for('contact_update', contact_id=contact_db.key.id())}}">
    {{contact_db.key.id()}}
  </a>
</td>
...

Now visit the contact list (http://localhost:8080/contact/), and you should be able to click on the ID of each contact and update their properties.

Get rid of contact_create.html

At the moment the only difference between the contact_create.html and the contact_update.html files is the caption of the submit form. Just delete the contact_create.html form and change the button in contact_update.html to the following:

...
<button type="submit" class="btn btn-primary btn-block">
  # if contact_db
    Update Contact
  # else
    Create Contact
  # endif
</button>
...

Finally since we deleted the contact_create.html we'll have to simply update the create handler to render the correct template and by now you should be able to find that yourself.

Before deploying your application you must minify and uglify your styles and scripts because otherwise your latest changes won't appear online!

To minify your styles and scripts execute the run.py script by providing the -m argument

$ ./run.py -m

After that make sure that you have created a new application on App Engine and the name of the application is updated in the app.yaml file that is located in the main directory.

To deploy your application execute the following command from the root directory:

$ appcfg.py update main

Test your application by adding new contacts online!

Source Code & Demo

Get the code for this tutorial from GitHub (or Bitbucket) or check out the final result in action: Phonebook.

Next steps

Continue with our Guide. It will explain the different building blocks of gae-init so you can use it like a pro.