Killer Web Development

by Marco Laspe

7.1 Registering Users

Register a user

The first thing we need to do with users: registering them. Luckily web2py comes with on-board resources, that provides most we need.

Remember the mockup for the Register Form from section 4.2

web application sketches register or edit profile

Compare the mockup to the Register page at http://localhost:8000/tukker/user/register

The Register page looks pretty similar to what we want to achieve, but to suit it our needs we will have to do some improvements:

  1. For the Tukker.Me application, John wants users to have a user name that is unique, the email address alone is not sufficient for us. Imagine @marco@killer-web-development.com in a direct Tukker message - not very elegant. John wants something like @killerwebdev.

  2. John wants that users are able to upload a profile picture.

  3. Finally John wants us to style the Register page with proper margins and padding Creating the user with picture.

Testing the register form.

Before we go on we need to define tests for the register form. Unfortunately testing the Register form is not as simple as testing the static pages. When we register a user, we write it to the database. Because users need to be unique, we would have to write a new test for every test run or we would need to change the users with ervery test run - both solution don't seem very practical and trust worthy. We have another possibility, after every test run for the Register page, we will delete the user we created with the test run.

First, let us write some general tests, which we used allready with the static pages, that are important with the register page, too. Create a new file fts/test_register_form.py and put in the first tests:

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
from functional_tests import FunctionalTest, ROOT

class TestRegisterPage (FunctionalTest):

def setUp(self):
# John opens his browser and goes to the home-page of the tukker app
self.url = ROOT + '/tukker/user/register'
get_browser=self.browser.get(self.url)

def test_can_view_home_page(self):
# Let's check if the website was loaded ok => response code == 200
response_code = self.get_response_code(self.url)
self.assertEqual(response_code, 200)

def test_has_right_title(self):
# First he looks at the topbar and sees 'Microposts On Steroids'
title = self.browser.find_element_by_tag_name('title')
self.assertEqual('Tukker.Me Registration', title.text)

This is pretty much the same stuff we testet on our static pages, let's check if everything passes:

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
$ python functional_tests.py
...
use "kill -SIGTERM 10438" to shutdown the web2py server
.F
======================================================================
FAIL: test_has_right_title (test_register_form.TestRegisterPage)
----------------------------------------------------------------------
Traceback (most recent call last):
File "~/web2py/applications/tukker/fts/test_register_form.py", line 20,
in test_has_right_title
self.assertEqual('Tukker.Me Registration', title.text)
AssertionError: 'Tukker.Me Registration' != u'Microposts On Steroids'

----------------------------------------------------------------------
Ran 11 tests in 30.179s

FAILED (failures=1)

Unfortunately we failed one test. Our registration page has the same title as the home page, but John wanted a better fitting title for the registration page. This is why we chose the title Tukker.Me Registration for the test_has_right_title test.

To pass that test we need to know which function we must change. Let's have a look at the url of the registration page: http://localhost:8000/tukker/default/user/register.

If you remember web2py's dispatching machanism from section 3.3, you should hava an idea which function should be responsible for the registration page :

web application dispatching algorithm web2py

For the sake of repetition let's do the dispatching for our registration page by hand:

  • application = tuker
  • controller = default
  • function = user (Did you guess this?)
  • args = register (What the hell are args?)

Ok, now we know the function we need to change is user, but you might ask yourself: What are args?

args are the URL arguments behind the function name. Different parameters are divided by a forward slash: http://hostname/application/controller/function/argument1/argument2/ - web2py maps these URL arguments to a Python list, that is provided by the request.args variable :

1.
request.args = [argument1, argument2, ...]

Let's recab:

  • we need to change the user function in the default.py controller
  • we need to watch for the URL argument register
  • our title is wrong => we need to change the variable response.title

With this knowledge it's quite easy to change the user function:

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
def user():
"""
exposes:
http://..../[app]/default/user/login
http://..../[app]/default/user/logout
http://..../[app]/default/user/register
http://..../[app]/default/user/profile
http://..../[app]/default/user/retrieve_password
http://..../[app]/default/user/change_password
use @auth.requires_login()
@auth.requires_membership('group name')
@auth.requires_permission('read','table name',record_id)
to decorate functions that need access control
"""
if request.args[0] == "register":
response.title = "Tukker.Me Registration"
return dict(form=auth())

we added only two lines to the user function :

1.
if request.args[0] == "register":

looks, if the first item of the request.args list is register. If it's true, then we change the response.title variable to Tukker.Me Registration.

Re-run the test and check if you pass all tests.

Now we will write a test for the register form. The test will pass, to make sure web2py's standard functionality is doing fine. I know: Tests should fail the first time, but we are using web2py's standard features without any customization - this time we can make an exception.

First make sure to add one more line to functional_test.py:

1.
2.
3.
4.
5.
6.
...

sys.path.append('./fts/lib')
sys.path.append('../../gluon') # we need this to use web2py's modules

...

This lets us use web2py'S features in our tests. Don't worry to much about it.

Here is the test code, add it to fts/test_register_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.
def test_put_values_in_register_form(self):
# John fills in the form with his personal data
first_name = self.browser.find_element_by_name("first_name")
first_name.send_keys("John")

last_name = self.browser.find_element_by_name("last_name")
last_name.send_keys("Tukker")

email = self.browser.find_element_by_name("email")
email.send_keys("john@tukker.me")

password = self.browser.find_element_by_name("password")
password.send_keys("pass")

verify_password = self.browser.find_element_by_name("password_two")
verify_password.send_keys("pass")

nickname = self.browser.find_element_by_name("nickname")
nickname.send_keys("john")

# John klicks on the register button
register_button = self.browser.find_element_by_css_selector("#submit_record__row input")
register_button.click()

# John sees the welcome Message from web2py
welcome_message = self.browser.find_element_by_css_selector(".flash")
self.assertEqual('Welcome to Tukker.Me.', welcome_message.text)

# John logs out if any session are still open
self.url = ROOT + '/tukker/default/user/logout'
get_browser=self.browser.get(self.url)

The test finds every input in the firm and fills in values.

1.
first_name = self.browser.find_element_by_name("first_name")

finds the element by its name - but what is the name of an element? Use Firebug to inspect the First Name input field of the register form. You will see something like this

inspectiong form element with firebug

The name tag of input field makes it possible to connect certain labels to this input field.

After we selected the input field we put some value in it :

1.
first_name.send_keys("John")

the send_keys() method simulates keyboard input from the user.

We do this two steps for every input field in the register form. After we filled all input fields we simulate a click on the Register button :

1.
2.
register_button = self.browser.find_element_by_css_selector("#submit_record__row input")
register_button.click()

This time we use a css selector to select the button (buttons have no name attribute) and use the click() method to click on the button.

Finally we make the assertion that determinates if our test passes or fails :

1.
2.
welcome_message = self.browser.find_element_by_css_selector(".flash")
self.assertEqual('Welcome to Tukker.Me.', welcome_message.text)

What are we testing here? We assume, if John's registration works, then he gets redirected to the homepage and on the homepage he sees a flash message Welcome to Tukker.Me.

Let's check if our test works :

1.
2.
3.
4.
5.
6.
$ python functional_tests.py
...
----------------------------------------------------------------------
Ran 12 tests in 34.977s

OK

We created a user John Tukker, but what happens if we run the test again? :

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
$ python functional_tests.py
...
..
F
======================================================================
FAIL: test_put_values_in_register_form (test_register_form.TestRegisterPage)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/macco/Dropbox/web2pytutorial/web2py/applications/tukker/fts/test_register_form.py", line 45,
in test_put_values_in_register_form
self.assertEqual('Welcome to Tukker.Me.', welcome_message.text)
AssertionError: 'Welcome to Tukker.Me.' != u''

----------------------------------------------------------------------
Ran 12 tests in 33.712s

FAILED (failures=1)

The test doesn't pass any more. That's because we can't register a second John Tukker. You can see the database entry if you login the admin area at http://localhost:8000/admin/design/tukker and click on database administration and then on db.auth_user. There it is: John Tukker, if you click on the id at the beginning of the row you get to a detailed view where you can mark a check box to delete the entry.

Next time the test should pass again. To prevent this problem from happening again, we need a way to delete John Tukker from our database after every succesfull test.

How can we do this? Remeber the setUp() method at the beginning of any test class? We can use something similar at the and of our test class: tearDown():

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
def tearDown(self):
# We need to kick John ou of the database, otherwise the test will fail
# in the future
path_to_database = path.join(path.curdir, "databases")
db = DAL('sqlite://storage.sqlite', folder=path_to_database)
db.import_table_definitions(path_to_database)

#This gives us the users with the email address john@tukker.me
db_query = db(db.auth_user.email == 'john@tukker.me').select()
if len(db_query) > 0:
db_query[0].delete_record() # delete John
db.commit()

To make tearDown() work, we must import the the Python's os.path module and web2py's dal module, put :

1.
2.
import os
from os import path

in the first line of test_register_form.py. Python modules are libraries that can be called from other Python scripts.

What are we doing here? First we build a path with the Python's path module :

1.
path_to_database = path.join(path.curdir, "databases")

the join method puts together several path elements together to one big absolute path; the curdir variable of the path module holds the current working directory we are (in our case the base directory of our app). The beauty of this solution is, that it works across all operating systems.

Then we connect to the database :

1.
2.
db = DAL('sqlite://storage.sqlite', folder=path_to_database)
db.import_table_definitions(path_to_database)

In essence we do the same web2py does when we start it: we make a connection to the database with web2py's database abstraction layer (DAL) while we creating an instance of the DAL class and then we import the database tables.

After that we look for database users with the email john@tukker.me :

1.
db_query = db(db.auth_user.email == 'john@tukker.me').select()

The statement returns a list of of database entries with one or zero entries - hence there can be only on user in the database with the email adress john@tukker.me. Don't worry if you not fully understand the database query, we look into queries in more detail later.

Next we check if there length of db_query is longer than zero, if this is true (it means we found something in the databse) we delete the entry and finally we commit the changes to the database :

1.
2.
3.
if len(db_query) > 0:
db_query[0].delete_record() # delete John
db.commit()

That's it, we wrote the initial tests of our register page.

I hope this section wasn't to intimidating. I know testing is hard work, but it's worth the price. Your learned how to simulate user input on a web page and why you should clean up the database if you changed the databse during your test. In the next chapter we will think about our user model and refine our tests to suit our user model. We will then change our app to pass the tests.

Finally let's commit the changes to our repository:

1.
hg commit -m"First tests for the register form"

Books often read by web2py and Python experts:

Comments

Leave a Reply

Required fields are marked *.