Killer Web Development

by Marco Laspe

7.2 User Model

In section 3.1 I showed you the basic user model of a "naked" web2py app. This was not the whole truth. Actually web2py stores 3 more database fields in it's user model:

  • registration key
  • reset password key
  • registration identifier

I only tell you this cause of the sake of completeness. This 3 fields won't bother us. Which fields do?

Let's have a look the Register/Change Profile sketch from section 4.2:

web application sketches register or edit profile

The first thing that comes to mind is the nickname of the user. The nickname is a central element in a microblogging service. The next thing that is obvious is the image of the user. This would mean our model looks like this:

Tukker.Me user model

Add database field to the user model

Now we know what we want, so how we do it?

For the nickname of the user we will use a simple text field with a maximum length of 20 characters - otherwise usernames will get to long. The username should be unique to one user and it should be mandatory to choose one.

The profile image will be a special upload field - web2py has a special construct for uploading files, that does not only store the path to the file name, but stores the file in a folder.

Now, that know what we need, we can start changing our user model. Oh no, you're right: We have to write tests for it first:

  1. We change our test to fill the additional field.

  2. We make a test that prooves the restrictions of the nickname field.

Let's change to test for the additional fields:

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
...
verify_password.send_keys("pass")

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

image = self.browser.find_element_by_name("image")
path_to_image = path.abspath(path.join(path.curdir, "fts/test_image.png"))
image.send_keys(path_to_image)

# John klicks on the register button
...

Nothing new here. This is the same stuff we're doing before. We are looking for a element nickname and are filling in the name John; we are doing the same for image, except that we are putting a string in the field that holds the path to test_image.png.

How do we the name of the input field before we created it? web2py uses the name of the database field for the name of the input field - this is a convention in web2py.

So let's make the test pass again. We need to change the user model. Open models/db.py:

1.
2.
3.
4.
auth.settings.extra_fields['auth_user']= [
Field('nickname', 'string'),
Field('image', 'upload')
]

This line adds two extra fields to the auth_user database table, which represents our user model. :

1.
Field('nickname', 'string')

defines a database field with the name nickname and its type string.:

1.
Field('image', 'upload')

analogically defines a field with the name image with its type upload.

If we run our tests again they should pass. I leave that to you.

Now we have to test for several for restrictions of the nickname, these are basicly variations of our last test.

First we will test if we can't submit the register form if the nickname field is empty:

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.
...
self.assertEqual('Welcome to Tukker.Me.', welcome_message.text)

def test_put_values_in_register_form_with_empty(self):
# John fills in the form with his personal data
first_name = self.browser.find_element_by_name("first_name")
first_name.send_keys("First")

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

email = self.browser.find_element_by_name("email")
email.send_keys("test@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")

image = self.browser.find_element_by_name("image")
path_to_image = path.abspath(path.join(path.curdir, "fts/test_image.png"))
image.send_keys(path_to_image)

# 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
error_message = self.browser.find_element_by_id("nickname__error")
self.assertEqual('enter a value', error_message.text)

def tearDown(self):
...

We just removed the line where we insert the nickname.

Second we check if it is possible to enter a nickname that is langer than 20 chars:

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.
...
self.assertEqual('Welcome to Tukker.Me.', welcome_message.text)

def test_put_values_in_register_form_nickname_with_24_chars(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("testtesttesttesttesttest")

image = self.browser.find_element_by_name("image")
path_to_image = path.abspath(path.join(path.curdir, "fts/test_imae.png"))
image.send_keys(path_to_image)

# 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
error_message = self.browser.find_element_by_id("nickname__error")
self.assertNotEqual('enter from 0 to 20 characters', error_message.text)

def tearDown(self):
...

This time we simply inserted 24 chars to the nickname field.

The last think we need to check is if we can't submit a nickname more than on time. For this we must make sure that the nickname we will submit is allready used by a user in the database. We could to this via Selenium and registering one user, then log her out und register a new one with the same nickname. We will introduce another way, that is called a fixture. Remember how we delete the database in the tearDown() method? We can create database entries the same way. We will do this in the setUp() method:

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
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)

path_to_database = path.join(path.curdir, "databases")
self.db = DAL('sqlite://storage.sqlite', folder=path_to_database)
self.db.import_table_definitions(path_to_database)

self.db.auth_user.insert(
first_name='Duplicate',
last_name='Nickname',
email='test@user.com',
password = 'test',
nickname = 'duplicate'
)

self.db.commit()

WIKIPEDIA SAYS:

Fixture

Test fixture refers to the fixed state used as a baseline for running tests in software testing. The purpose of a test fixture is to ensure that there is a well known and fixed environment in which tests are run so that results are repeatable. Some people call this the test context.

based on the new setUp() method we have to change the tearDown() accordingly:

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
def tearDown(self):
# We need clean up our database, otherwise the tests will fail
# in the future
self.db.auth_user.truncate() # This resets the whole database table.
self.db.commit()

# Delete all files from the uploads directory, because we are
# creating files with every upload
dirPath = path.join(path.curdir, "uploads")
fileList = os.listdir(dirPath)
for fileName in fileList:
os.remove(dirPath+"/"+fileName)

Making the register form look pretty

The register form allready looks quite simular to our sketch from chapter 4, but probably needs a bit more styling.

Add the following code to static/css/custom.css:

1.
.w2p_fw {padding: 5px;}

That's it, our register form is finished.

This is how it should look like

web application sketches register or edit profile

Again, let's commit the changes:

1.
$ hg commit-m"refined register form with nickname and image"

Books often read by web2py and Python experts:

Comments

Leave a Reply

Required fields are marked *.