Index: ow/models/root.py
===================================================================
--- ow/models/root.py	(revision 39b14977ea6c9734e86296a94d7560ff9591a1c9)
+++ ow/models/root.py	(revision 1d92bf2b47c1a3bf1482a589cdd6cc7ec38d552f)
@@ -3,9 +3,16 @@
 from repoze.folder import Folder
 from repoze.catalog.indexes.field import CatalogFieldIndex
+from repoze.catalog.query import Eq
 
 from pyramid.security import Allow, Everyone
 
 from ow.models.user import User
-from ow.catalog import get_catalog
+from ow.catalog import (
+    get_catalog,
+    install_catalog,
+    reindex_object,
+    remove_from_catalog,
+    resources_from_query_results
+)
 
 
@@ -14,4 +21,7 @@
     Root object, contains basically all the users, and in turn
     the users contain their workouts.
+
+    Users are stored in a dict-like structure, using a string containing
+    the user id (uid) as a key, and the User object as the value.
     """
 
@@ -20,32 +30,85 @@
     __acl__ = [
                (Allow, Everyone, 'view'),
-               (Allow, 'group:admins', 'edit')
+               (Allow, 'admins', 'edit')
               ]
+
+    def __init__(self, **kw):
+        install_catalog(self)
+        super(OpenWorkouts, self).__init__(**kw)
 
     def _get_catalog_indexes(self):
         indexes = {
+            'email': CatalogFieldIndex('email'),
             'sport': CatalogFieldIndex('sport'),
         }
         return indexes
 
-    def all_usernames(self):
+    def reindex(self, obj):
         """
-        Return all available usernames
+        Reindex the given object in the catalog
         """
-        return self.keys()
+        reindex_object(self.catalog, obj)
 
-    def lowercase_usernames(self):
+    def query(self, query):
         """
-        Return all available usernames in lower case. Useful for
-        username uniqueness validation on signup
+        Execute the given query on the catalog, returning the results
+        (generator with events found or empty list if no results were found)
         """
-        return [name.lower() for name in self.keys()]
+        catalog = get_catalog(self)
+        number, results = catalog.query(query)
+        if number:
+            return resources_from_query_results(catalog, results, self)
+        return []
 
+    def add_user(self, user):
+        self.add(str(user.uid), user)
+        self.reindex(user)
+
+    def del_user(self, user):
+        remove_from_catalog(self.catalog, user)
+        self.remove(str(user.uid))
+
+    def get_user_by_uid(self, uid):
+        return self.get(str(uid), None)
+
+    def get_user_by_email(self, email):
+        if email is not None:
+            # for some reason, when searching for None
+            # the catalog will return all users
+            res = self.query(Eq('email', email))
+            if res:
+                return next(res)
+        return None
+
+    @property
+    def users(self):
+        """
+        Return all user objects
+        """
+        return [user for user in self.values() if isinstance(user, User)]
+
+    @property
+    def all_nicknames(self):
+        """
+        Return all available nicknames
+        """
+        return [user.nickname for user in self.users if user.nickname]
+
+    @property
+    def lowercase_nicknames(self):
+        """
+        Return all available nicknames in lower case. Useful for
+        nickname uniqueness validation on signup
+        """
+        return [nick.lower() for nick in self.all_nicknames]
+
+    @property
     def emails(self):
         """
         Return all emails currently in use by users
         """
-        return [u.email for u in self.users()]
+        return [user.email for user in self.users]
 
+    @property
     def lowercase_emails(self):
         """
@@ -54,23 +117,9 @@
         against the currently used addresses
         """
-        return [u.email.lower() for u in self.users()]
-
-    def users(self):
-        """
-        Return all user objects
-        """
-        return [u for u in self.values() if isinstance(u, User)]
-
-    def get_user(self, uid):
-        return self.get(uid, None)
-
-    def add_user(self, uid, **kw):
-        u = User(**kw)
-        self[uid] = u
+        return [email.lower() for email in self.emails]
 
     @property
     def sports(self):
-        catalog = get_catalog(self)
-        return [s for s in catalog['sport']._fwd_index]
+        return [s for s in self.catalog['sport']._fwd_index]
 
     @property
Index: ow/models/user.py
===================================================================
--- ow/models/user.py	(revision 39b14977ea6c9734e86296a94d7560ff9591a1c9)
+++ ow/models/user.py	(revision 1d92bf2b47c1a3bf1482a589cdd6cc7ec38d552f)
@@ -23,4 +23,5 @@
     def __init__(self, **kw):
         self.uid = kw.get('uid', uuid1())
+        self.nickname = kw.get('nickname', '')
         self.firstname = kw.get('firstname', '')
         self.lastname = kw.get('lastname', '')
Index: ow/schemas/user.py
===================================================================
--- ow/schemas/user.py	(revision 39b14977ea6c9734e86296a94d7560ff9591a1c9)
+++ ow/schemas/user.py	(revision 1d92bf2b47c1a3bf1482a589cdd6cc7ec38d552f)
@@ -19,12 +19,11 @@
 
 
-class UniqueUsername(validators.UnicodeString):
+class UniqueNickname(validators.UnicodeString):
     messages = {
-        "name_exists": _('Another user is already registered with the '
-                         'username %(name)s')
+        "name_exists": _('Another user is already using the nickname %(name)s')
     }
 
     def _validate_python(self, value, state):
-        super(UniqueUsername, self)._validate_python(value, state)
+        super(UniqueNickname, self)._validate_python(value, state)
         if value.lower() in state.names:
             raise validators.Invalid(
@@ -51,8 +50,8 @@
     allow_extra_fields = True
     filter_extra_fields = True
-    uid = validators.UnicodeString()
+    email = UniqueEmail(not_empty=True)
+    nickname = UniqueNickname(if_missing='')
     firstname = validators.UnicodeString()
     lastname = validators.UnicodeString()
-    email = validators.Email(not_empty=True)
     group = validators.UnicodeString(if_missing='')
 
@@ -92,5 +91,5 @@
     allow_extra_fields = True
     filter_extra_fields = True
-    username = UniqueUsername(not_empty=True)
+    nickname = UniqueNickname()
     firstname = validators.UnicodeString(not_empty=True)
     lastname = validators.UnicodeString(not_empty=True)
@@ -108,4 +107,4 @@
     allow_extra_fields = True
     filter_extra_fields = True
-    username = UniqueUsername(not_empty=True)
+    nickname = UniqueNickname()
     email = UniqueEmail(not_empty=True)
Index: ow/security.py
===================================================================
--- ow/security.py	(revision 39b14977ea6c9734e86296a94d7560ff9591a1c9)
+++ ow/security.py	(revision 1d92bf2b47c1a3bf1482a589cdd6cc7ec38d552f)
@@ -9,5 +9,6 @@
     allow other users to view workouts from a given user
     """
-    if uid in request.root.all_usernames():
-        return [uid]
+    user = request.root.get_user_by_uid(str(uid))
+    if user is not None:
+        return [str(user.uid)]
     return []
Index: ow/templates/add_user.pt
===================================================================
--- ow/templates/add_user.pt	(revision 39b14977ea6c9734e86296a94d7560ff9591a1c9)
+++ ow/templates/add_user.pt	(revision 1d92bf2b47c1a3bf1482a589cdd6cc7ec38d552f)
@@ -22,10 +22,10 @@
 
       <fieldset>
-        ${form.errorlist('uid')}
-        <label for="email" i18n:translate="">User ID:</label>
-        ${form.text('uid')}
         ${form.errorlist('email')}
         <label for="email" i18n:translate="">E-Mail:</label>
         ${form.text('email')}
+        ${form.errorlist('nickname')}
+        <label for="nickname" i18n:translate="">Nickname</label>
+        ${form.text('nickname')}
       </fieldset>
 
Index: ow/templates/login.pt
===================================================================
--- ow/templates/login.pt	(revision 39b14977ea6c9734e86296a94d7560ff9591a1c9)
+++ ow/templates/login.pt	(revision 1d92bf2b47c1a3bf1482a589cdd6cc7ec38d552f)
@@ -24,8 +24,8 @@
           <fieldset>
             <div>
-              <label for="username" i18n:translate="">Username</label>
-              <input placeholder="Your username..." type="text" name="username"
+              <label for="email" i18n:translate="">Email</label>
+              <input placeholder="Your email..." type="text" name="email"
                      value="" i18n:attributes="placeholder"
-                     tal:attributes="value username">
+                     tal:attributes="value email">
             </div>
             <div>
Index: ow/templates/signup.pt
===================================================================
--- ow/templates/signup.pt	(revision 39b14977ea6c9734e86296a94d7560ff9591a1c9)
+++ ow/templates/signup.pt	(revision 1d92bf2b47c1a3bf1482a589cdd6cc7ec38d552f)
@@ -18,11 +18,4 @@
           <fieldset>
             <div>
-              ${form.errorlist('username')}
-              <label for="username" i18n:translate="">Username</label>
-              <input placeholder="Choose a cool username" type="text"
-                     name="username" value="" i18n:attributes="placeholder">
-            </div>
-
-            <div>
               ${form.errorlist('email')}
               <label for="email" i18n:translate="">Email</label>
@@ -30,4 +23,10 @@
                      type="text" name="email"
                      value="" i18n:attributes="placeholder">
+            </div>
+            <div>
+              ${form.errorlist('nickname')}
+              <label for="nickname" i18n:translate="">Nickname</label>
+              <input placeholder="Choose a cool nickname" type="text"
+                     name="nickname" value="" i18n:attributes="placeholder">
             </div>
             <div>
@@ -50,5 +49,4 @@
                      type="password" i18n:attributes="placeholder">
             </div>
-
             <div>
               ${form.errorlist('password_confirm')}
Index: ow/tests/models/test_root.py
===================================================================
--- ow/tests/models/test_root.py	(revision 39b14977ea6c9734e86296a94d7560ff9591a1c9)
+++ ow/tests/models/test_root.py	(revision 1d92bf2b47c1a3bf1482a589cdd6cc7ec38d552f)
@@ -1,6 +1,8 @@
 import json
+from unittest.mock import Mock
 from datetime import datetime, timedelta, timezone
 
 import pytest
+from repoze.catalog.catalog import Catalog
 
 from ow.models.workout import Workout
@@ -12,9 +14,14 @@
 
     @pytest.fixture
-    def root(self):
+    def john(self):
+        user = User(firstname='John', lastname='Doe',
+                    email='john.doe@example.net')
+        user.password = 's3cr3t'
+        return user
+
+    @pytest.fixture
+    def root(self, john):
         root = OpenWorkouts()
-        root['john'] = User(firstname='John', lastname='Doe',
-                            email='john.doe@example.net')
-        root['john'].password = 's3cr3t'
+        root.add_user(john)
         workout = Workout(
             start=datetime(2015, 6, 28, 12, 55, tzinfo=timezone.utc),
@@ -22,43 +29,104 @@
             distance=30, sport='cycling'
         )
-        root['john'].add_workout(workout)
+        john.add_workout(workout)
         return root
 
-    def test_all_usernames(self, root):
-        # the method returns an OOBtree object
-        assert [u for u in root.all_usernames()] == ['john']
+    def test__init__(self, root):
+        # a new OpenWorkouts instance has a catalog created automatically
+        assert isinstance(root.catalog, Catalog)
+        assert len(root.catalog) == 2
+        assert 'email' in root.catalog
+        assert 'sport' in root.catalog
 
-    def test_lowercase_usernames(self, root):
-        root.add_user(uid='Jack', firstname='Jack', lastname='Dumb',
-                      email='jack.dumb@example.net')
-        assert root.lowercase_usernames() == ['jack', 'john']
+    def test_add_user_ok(self, root):
+        assert len(root.users) == 1
+        user = User(firstname='New', lastname='For Testing',
+                    email='new.for.testing@example.net')
+        root.add_user(user)
+        assert len(root.users) == 2
+        assert user in root.users
+
+    def test_add_user_invalid(self, root):
+        assert len(root.users) == 1
+        with pytest.raises(AttributeError):
+            root.add_user('faked-user-object')
+
+    def test_del_user_ok(self, root, john):
+        assert len(root.users) == 1
+        root.del_user(john)
+        assert len(root.users) == 0
+
+    def test_del_user_failure(self, root):
+        assert len(root.users) == 1
+        with pytest.raises(AttributeError):
+            root.add_user('faked-user-object')
+
+    def test_get_user_by_uid(self, root, john):
+        # first, get an user that does exist
+        user = root.get_user_by_uid(str(john.uid))
+        assert user == john
+        # now, without converting first to str, works too
+        user = root.get_user_by_uid(john.uid)
+        assert user == john
+        # now, something that is not there
+        new_user = User(firstname='someone', lastname='else',
+                        email='someone.else@example.net')
+        user = root.get_user_by_uid(new_user.uid)
+        assert user is None
+        # now, something that is not an uid
+        user = root.get_user_by_uid('faked-user-uid')
+        assert user is None
+
+    def test_get_user_by_email(self, root, john):
+        # first, get an user that does exist
+        user = root.get_user_by_email(str(john.email))
+        assert user == john
+        # now, something that is not there
+        new_user = User(firstname='someone', lastname='else',
+                        email='someone.else@example.net')
+        user = root.get_user_by_email(new_user.email)
+        assert user is None
+        # now, something that is not an email
+        user = root.get_user_by_email('faked-user-email')
+        assert user is None
+        # passing in None
+        user = root.get_user_by_email(None)
+        assert user is None
+        # passing in something that is not None or a string will break
+        # the query code
+        with pytest.raises(TypeError):
+            user = root.get_user_by_email(False)
+        with pytest.raises(TypeError):
+            user = root.get_user_by_email(Mock())
+
+    def test_users(self, root, john):
+        assert root.users == [john]
+
+    def test_all_nicknames(self, root, john):
+        # the existing user has not a nickname, and empty nicknames are not
+        # added to the list of nicknames
+        assert root.all_nicknames == []
+        # now set one
+        john.nickname = 'MrJohn'
+        assert root.all_nicknames == ['MrJohn']
+
+    def test_lowercase_nicknames(self, root, john):
+        # the existing user has not a nickname
+        assert root.lowercase_nicknames == []
+        # now set one
+        john.nickname = 'MrJohn'
+        assert root.lowercase_nicknames == ['mrjohn']
 
     def test_emails(self, root):
-        assert root.emails() == ['john.doe@example.net']
+        assert root.emails == ['john.doe@example.net']
 
     def test_lowercase_emails(self, root):
-        root.add_user(uid='Jack', firstname='Jack', lastname='Dumb',
-                      email='Jack.Dumb@example.net')
-        assert root.lowercase_emails() == ['jack.dumb@example.net',
-                                           'john.doe@example.net']
+        user = User(firstname='Jack', lastname='Dumb',
+                    email='Jack.Dumb@example.net')
+        root.add_user(user)
+        assert root.lowercase_emails == ['john.doe@example.net',
+                                         'jack.dumb@example.net']
 
-    def test_users(self, root):
-        # the method returns an OOBtree object
-        assert [u for u in root.users()] == [root['john']]
-
-    def test_get_user(self, root):
-        assert root.get_user('john') == root['john']
-        assert root.get_user('jack') is None
-        with pytest.raises(TypeError):
-            root.get_user()
-
-    def test_add_user(self, root):
-        assert len(root.users()) == 1
-        root.add_user(uid='jack', firstname='Jack', lastname='Dumb',
-                      email='jack.dumb@example.net')
-        assert len(root.users()) == 2
-        assert 'jack' in root.all_usernames()
-
-    def test_sports(self, root):
+    def test_sports(self, root, john):
         assert root.sports == ['cycling']
         workout = Workout(
@@ -66,8 +134,8 @@
             duration=timedelta(minutes=60),
             distance=10, sport='running')
-        root['john'].add_workout(workout)
+        john.add_workout(workout)
         assert root.sports == ['cycling', 'running']
 
-    def test_sports_json(self, root):
+    def test_sports_json(self, root, john):
         assert root.sports_json == json.dumps(["cycling"])
         workout = Workout(
@@ -75,4 +143,4 @@
             duration=timedelta(minutes=60),
             distance=10, sport='running')
-        root['john'].add_workout(workout)
+        john.add_workout(workout)
         assert root.sports_json == json.dumps(["cycling", "running"])
Index: ow/tests/models/test_workout.py
===================================================================
--- ow/tests/models/test_workout.py	(revision 39b14977ea6c9734e86296a94d7560ff9591a1c9)
+++ ow/tests/models/test_workout.py	(revision 1d92bf2b47c1a3bf1482a589cdd6cc7ec38d552f)
@@ -56,8 +56,4 @@
         assert expected == joe['1'].end
         assert 250 == joe['1'].distance * 1000
-
-    def test_add_user(self, root):
-        root.add_user('fred', firstname=u'Fred', lastname=u'Flintstone')
-        assert u'Fred Flintstone' == root['fred'].fullname
 
     def test_workout_id(self, root):
Index: ow/tests/schemas/test_user.py
===================================================================
--- ow/tests/schemas/test_user.py	(revision 39b14977ea6c9734e86296a94d7560ff9591a1c9)
+++ ow/tests/schemas/test_user.py	(revision 1d92bf2b47c1a3bf1482a589cdd6cc7ec38d552f)
@@ -4,5 +4,5 @@
 
 from ow.models.user import User
-from ow.schemas.user import PasswordMatch, UniqueUsername, UniqueEmail
+from ow.schemas.user import PasswordMatch, UniqueNickname, UniqueEmail
 
 
@@ -27,5 +27,5 @@
 
 
-class TestUniqueUsername(object):
+class TestUniqueNickname(object):
 
     @pytest.fixture
@@ -35,5 +35,5 @@
 
     def test_validate_python_exists(self, state):
-        validator = UniqueUsername()
+        validator = UniqueNickname()
         with pytest.raises(Invalid):
             validator._validate_python('test', state)
@@ -42,5 +42,5 @@
 
     def test_validate_python_not_exists(self, state):
-        validator = UniqueUsername()
+        validator = UniqueNickname()
         res = validator._validate_python('not-existing', state)
         assert res is None
Index: ow/tests/test_catalog.py
===================================================================
--- ow/tests/test_catalog.py	(revision 39b14977ea6c9734e86296a94d7560ff9591a1c9)
+++ ow/tests/test_catalog.py	(revision 1d92bf2b47c1a3bf1482a589cdd6cc7ec38d552f)
@@ -56,5 +56,5 @@
         changes = update_indexes(catalog, indexes)
         assert changes['added'] == ['newindex']
-        assert changes['removed'] == ['sport']
+        assert changes['removed'] == ['email', 'sport']
 
     def test_update_indexes_empty(self, root):
@@ -63,8 +63,10 @@
         changes = update_indexes(catalog, indexes)
         assert changes['added'] == []
-        assert changes['removed'] == ['sport']
+        assert changes['removed'] == ['email', 'sport']
 
     def test_install_catalog(self):
         root = OpenWorkouts()
+        assert isinstance(getattr(root, 'catalog', None), Catalog)
+        del root.catalog
         assert getattr(root, 'catalog', None) is None
         install_catalog(root)
@@ -78,4 +80,6 @@
     def test_get_catalog_not_existing_catalog(self):
         root = OpenWorkouts()
+        assert isinstance(getattr(root, 'catalog', None), Catalog)
+        del root.catalog
         assert getattr(root, 'catalog', None) is None
         catalog = get_catalog(root)
Index: ow/tests/test_security.py
===================================================================
--- ow/tests/test_security.py	(revision 39b14977ea6c9734e86296a94d7560ff9591a1c9)
+++ ow/tests/test_security.py	(revision 1d92bf2b47c1a3bf1482a589cdd6cc7ec38d552f)
@@ -22,5 +22,5 @@
         request.root = root
         # User does exist
-        assert groupfinder('john', request) == ['john']
+        assert groupfinder('john', request) == [str(root['john'].uid)]
         # User does not exist
         assert groupfinder('jack', request) == []
Index: ow/tests/views/test_root.py
===================================================================
--- ow/tests/views/test_root.py	(revision 39b14977ea6c9734e86296a94d7560ff9591a1c9)
+++ ow/tests/views/test_root.py	(revision 1d92bf2b47c1a3bf1482a589cdd6cc7ec38d552f)
@@ -20,9 +20,14 @@
 
     @pytest.fixture
-    def root(self):
+    def john(self):
+        user = User(firstname='John', lastname='Doe',
+                    email='john.doe@example.net')
+        user.password = 's3cr3t'
+        return user
+
+    @pytest.fixture
+    def root(self, john):
         root = OpenWorkouts()
-        root['john'] = User(firstname='John', lastname='Doe',
-                            email='john.doe@example.net')
-        root['john'].password = 's3cr3t'
+        root.add_user(john)
         workout = Workout(
             start=datetime(2015, 6, 28, 12, 55, tzinfo=timezone.utc),
@@ -30,5 +35,5 @@
             distance=30, sport='cycling'
         )
-        root['john'].add_workout(workout)
+        john.add_workout(workout)
         return root
 
@@ -50,8 +55,8 @@
         return request
 
-    def test_user_list(self, get_request):
+    def test_user_list(self, get_request, john):
         request = get_request
         response = user_list(request.root, request)
-        assert list(response['users']) == [request.root['john']]
+        assert list(response['users']) == [john]
 
     def test_add_user_get(self, get_request):
@@ -66,12 +71,16 @@
         response = add_user(request.root, request)
         assert 'form' in response
-        # All required fields (4) are marked in the form errors
+        # All required fields (3) are marked in the form errors
         # You can see which fields are required in the schema
         # ow.schemas.user.UserAddSchema
-        assert len(response['form'].form.errors) == 4
+        errors = response['form'].form.errors
+        assert len(errors) == 3
+        assert 'email' in errors
+        assert 'firstname' in errors
+        assert 'lastname' in errors
 
     def test_add_user_post_valid(self, post_request):
         request = post_request
-        request.POST['uid'] = 'addeduser'
+        request.POST['nickname'] = 'addeduser'
         request.POST['email'] = 'addeduser@example.net'
         request.POST['firstname'] = 'added'
@@ -80,4 +89,5 @@
         assert isinstance(response, HTTPFound)
         assert response.location.endswith('/userlist')
-        assert len(request.root.all_usernames()) == 2
-        assert 'addeduser' in request.root.all_usernames()
+        # 1 nick name, as the default user has no nickname
+        assert len(request.root.all_nicknames) == 1
+        assert 'addeduser' in request.root.all_nicknames
Index: ow/tests/views/test_user.py
===================================================================
--- ow/tests/views/test_user.py	(revision 39b14977ea6c9734e86296a94d7560ff9591a1c9)
+++ ow/tests/views/test_user.py	(revision 1d92bf2b47c1a3bf1482a589cdd6cc7ec38d552f)
@@ -24,9 +24,14 @@
 
     @pytest.fixture
-    def root(self):
+    def john(self):
+        user = User(firstname='John', lastname='Doe',
+                    email='john.doe@example.net')
+        user.password = 's3cr3t'
+        return user
+
+    @pytest.fixture
+    def root(self, john):
         root = OpenWorkouts()
-        root['john'] = User(firstname='John', lastname='Doe',
-                            email='john.doe@example.net')
-        root['john'].password = 's3cr3t'
+        root.add_user(john)
         workout = Workout(
             start=datetime(2015, 6, 28, 12, 55, tzinfo=timezone.utc),
@@ -34,5 +39,5 @@
             distance=30
         )
-        root['john'].add_workout(workout)
+        john.add_workout(workout)
         return root
 
@@ -44,10 +49,10 @@
 
     @pytest.fixture
-    def profile_post_request(self, root):
+    def profile_post_request(self, root, john):
         """
         This is a valid POST request to update an user profile.
         Form will validate, but nothing will be really updated/changed.
         """
-        user = root['john']
+        user = john
         request = DummyRequest()
         request.root = root
@@ -94,5 +99,5 @@
         request.POST = MultiDict({
             'submit': True,
-            'username': 'JackBlack',
+            'nickname': 'JackBlack',
             'email': 'jack.black@example.net',
             'firstname': 'Jack',
@@ -134,20 +139,18 @@
         assert response.location == '/dashboard'
 
-    def test_dashboard(self, dummy_request):
+    def test_dashboard(self, dummy_request, john):
         """
         Renders the user dashboard
         """
         request = dummy_request
-        user = request.root['john']
-        response = user_views.dashboard(user, request)
+        response = user_views.dashboard(john, request)
         assert response == {}
 
-    def test_profile(self, dummy_request):
+    def test_profile(self, dummy_request, john):
         """
         Renders the user profile page
         """
         request = dummy_request
-        user = request.root['john']
-        response = user_views.profile(user, request)
+        response = user_views.profile(john, request)
         assert response == {}
 
@@ -159,9 +162,9 @@
         response = user_views.login(request.root, request)
         assert response['message'] == ''
-        assert response['username'] == ''
+        assert response['email'] == ''
         assert response['password'] == ''
         assert response['redirect_url'] == request.resource_url(request.root)
 
-    def test_login_get_return_to(self, dummy_request):
+    def test_login_get_return_to(self, dummy_request, john):
         """
         GET request to access the login page, if there is a page set to where
@@ -170,5 +173,5 @@
         """
         request = dummy_request
-        workout = request.root['john'].workouts()[0]
+        workout = john.workouts()[0]
         workout_url = request.resource_url(workout)
         request.params['return_to'] = workout_url
@@ -176,20 +179,20 @@
         assert response['redirect_url'] == workout_url
 
-    def test_login_post_bad_username(self, dummy_request):
+    def test_login_post_wrong_email(self, dummy_request):
         request = dummy_request
         request.method = 'POST'
         request.POST['submit'] = True
-        request.POST['username'] = 'jack'
+        request.POST['email'] = 'jack@example.net'
         response = user_views.login(request.root, request)
-        assert response['message'] == u'Bad username'
-
-    def test_login_post_bad_password(self, dummy_request):
+        assert response['message'] == u'Wrong email address'
+
+    def test_login_post_wrong_password(self, dummy_request):
         request = dummy_request
         request.method = 'POST'
         request.POST['submit'] = True
-        request.POST['username'] = 'john'
+        request.POST['email'] = 'john.doe@example.net'
         request.POST['password'] = 'badpassword'
         response = user_views.login(request.root, request)
-        assert response['message'] == u'Bad password'
+        assert response['message'] == u'Wrong password'
 
     @patch('ow.views.user.remember')
@@ -198,5 +201,5 @@
         request.method = 'POST'
         request.POST['submit'] = True
-        request.POST['username'] = 'john'
+        request.POST['email'] = 'john.doe@example.net'
         request.POST['password'] = 's3cr3t'
         response = user_views.login(request.root, request)
@@ -216,5 +219,5 @@
 
     @pytest.mark.parametrize('extension', extensions)
-    def test_profile_picture(self, extension, dummy_request):
+    def test_profile_picture(self, extension, dummy_request, john):
         """
         GET request to get the profile picture of an user.
@@ -222,5 +225,5 @@
         request = dummy_request
         # Get the user
-        user = request.root['john']
+        user = john
         # Get the path to the image, then open it and copy it to a new Blob
         # object
@@ -242,5 +245,5 @@
         assert response.content_type == 'image'
 
-    def test_edit_profile_get(self, dummy_request):
+    def test_edit_profile_get(self, dummy_request, john):
         """
         GET request to the edit profile page, returns the form ready to
@@ -248,5 +251,5 @@
         """
         request = dummy_request
-        user = request.root['john']
+        user = john
         response = user_views.edit_profile(user, request)
         assert isinstance(response['form'], OWFormRenderer)
@@ -261,7 +264,7 @@
         assert response['form'].data['email'] == 'john.doe@example.net'
 
-    def test_edit_profile_post_ok(self, profile_post_request):
+    def test_edit_profile_post_ok(self, profile_post_request, john):
         request = profile_post_request
-        user = request.root['john']
+        user = john
         # Update the bio field
         bio = 'Some text about this user'
@@ -272,8 +275,9 @@
         assert user.bio == bio
 
-    def test_edit_profile_post_missing_required(self, profile_post_request):
+    def test_edit_profile_post_missing_required(
+            self, profile_post_request, john):
         request = profile_post_request
         request.POST['email'] = ''
-        user = request.root['john']
+        user = john
         response = user_views.edit_profile(user, request)
         assert isinstance(response['form'], OWFormRenderer)
@@ -284,7 +288,7 @@
         assert response['form'].errors_for('email') == [error]
 
-    def test_change_password_get(self, dummy_request):
-        request = dummy_request
-        user = request.root['john']
+    def test_change_password_get(self, dummy_request, john):
+        request = dummy_request
+        user = john
         response = user_views.change_password(user, request)
         assert isinstance(response['form'], OWFormRenderer)
@@ -292,7 +296,7 @@
         assert response['form'].errorlist() == ''
 
-    def test_change_password_post_ok(self, passwd_post_request):
+    def test_change_password_post_ok(self, passwd_post_request, john):
         request = passwd_post_request
-        user = request.root['john']
+        user = john
         request.POST['old_password'] = 's3cr3t'
         request.POST['password'] = 'h1dd3n s3cr3t'
@@ -305,7 +309,7 @@
         assert user.check_password('h1dd3n s3cr3t')
 
-    def test_change_password_post_no_values(self, passwd_post_request):
+    def test_change_password_post_no_values(self, passwd_post_request, john):
         request = passwd_post_request
-        user = request.root['john']
+        user = john
         response = user_views.change_password(user, request)
         assert isinstance(response['form'], OWFormRenderer)
@@ -322,7 +326,8 @@
         assert user.check_password('s3cr3t')
 
-    def test_change_password_post_bad_old_password(self, passwd_post_request):
+    def test_change_password_post_bad_old_password(
+            self, passwd_post_request, john):
         request = passwd_post_request
-        user = request.root['john']
+        user = john
         request.POST['old_password'] = 'FAIL PASSWORD'
         request.POST['password'] = 'h1dd3n s3cr3t'
@@ -338,7 +343,8 @@
         assert not user.check_password('h1dd3n s3cr3t')
 
-    def test_change_password_post_password_mismatch(self, passwd_post_request):
+    def test_change_password_post_password_mismatch(
+            self, passwd_post_request, john):
         request = passwd_post_request
-        user = request.root['john']
+        user = john
         request.POST['old_password'] = 's3cr3t'
         request.POST['password'] = 'h1dd3n s3cr3ts'
@@ -363,14 +369,17 @@
     def test_signup_post_ok(self, signup_post_request):
         request = signup_post_request
-        assert 'JackBlack' not in request.root.all_usernames()
+        assert 'jack.black@example.net' not in request.root.emails
+        assert 'JackBlack' not in request.root.all_nicknames
         response = user_views.signup(request.root, request)
         assert isinstance(response, HTTPFound)
         assert response.location == request.resource_url(request.root)
-        assert 'JackBlack' in request.root.all_usernames()
+        assert 'jack.black@example.net' in request.root.emails
+        assert 'JackBlack' in request.root.all_nicknames
 
     def test_signup_missing_required(self, signup_post_request):
         request = signup_post_request
         request.POST['email'] = ''
-        assert 'JackBlack' not in request.root.all_usernames()
+        assert 'jack.black@example.net' not in request.root.emails
+        assert 'JackBlack' not in request.root.all_nicknames
         response = user_views.signup(request.root, request)
         assert isinstance(response['form'], OWFormRenderer)
@@ -382,13 +391,20 @@
         assert errorlist == html_error
         assert response['form'].errors_for('email') == [error]
-        assert 'JackBlack' not in request.root.all_usernames()
-
-    def test_signup_existing_username(self, signup_post_request):
+        assert 'jack.black@example.net' not in request.root.emails
+        assert 'JackBlack' not in request.root.all_nicknames
+
+    def test_signup_existing_nickname(self, signup_post_request, john):
         request = signup_post_request
-        request.POST['username'] = 'john'
-        assert 'JackBlack' not in request.root.all_usernames()
+        # assign john a nickname first
+        john.nickname = 'john'
+        # now set it for the POST request
+        request.POST['nickname'] = 'john'
+        # check jack is not there yet
+        assert 'jack.black@example.net' not in request.root.emails
+        assert 'JackBlack' not in request.root.all_nicknames
+        # now signup as jack, but trying to set the nickname 'john'
         response = user_views.signup(request.root, request)
         assert isinstance(response['form'], OWFormRenderer)
-        error = u'Another user is already registered with the username john'
+        error = u'Another user is already using the nickname john'
         html_error = '<ul class="error">'
         html_error += '<li>' + error + '</li>'
@@ -396,11 +412,14 @@
         errorlist = response['form'].errorlist().replace('\n', '')
         assert errorlist == html_error
-        assert response['form'].errors_for('username') == [error]
-        assert 'JackBlack' not in request.root.all_usernames()
+        assert response['form'].errors_for('nickname') == [error]
+        # all the errors, and jack is not there
+        assert 'jack.black@example.net' not in request.root.emails
+        assert 'JackBlack' not in request.root.all_nicknames
 
     def test_signup_existing_email(self, signup_post_request):
         request = signup_post_request
         request.POST['email'] = 'john.doe@example.net'
-        assert 'JackBlack' not in request.root.all_usernames()
+        assert 'jack.black@example.net' not in request.root.emails
+        assert 'JackBlack' not in request.root.all_nicknames
         response = user_views.signup(request.root, request)
         assert isinstance(response['form'], OWFormRenderer)
@@ -413,3 +432,4 @@
         assert errorlist == html_error
         assert response['form'].errors_for('email') == [error]
-        assert 'JackBlack' not in request.root.all_usernames()
+        assert 'jack.black@example.net' not in request.root.emails
+        assert 'JackBlack' not in request.root.all_nicknames
Index: ow/views/root.py
===================================================================
--- ow/views/root.py	(revision 39b14977ea6c9734e86296a94d7560ff9591a1c9)
+++ ow/views/root.py	(revision 1d92bf2b47c1a3bf1482a589cdd6cc7ec38d552f)
@@ -1,4 +1,4 @@
 from pyramid.view import view_config
-from pyramid_simpleform import Form
+from pyramid_simpleform import Form, State
 from pyramid_simpleform.renderers import FormRenderer
 from pyramid.httpexceptions import HTTPFound
@@ -18,5 +18,5 @@
     Show a list of all the users to admins
     """
-    users = context.users()
+    users = context.users
     return {'users': users}
 
@@ -31,10 +31,12 @@
     Form to add a user
     """
-    form = Form(request, schema=UserAddSchema())
+    state = State(emails=context.lowercase_emails,
+                  names=context.lowercase_nicknames)
+
+    form = Form(request, schema=UserAddSchema(), state=state)
 
     if 'submit' in request.POST and form.validate():
-        uid = request.POST['uid']
-        user = form.bind(User(), exclude=['uid'])
-        context[uid] = user
+        user = form.bind(User())
+        context[str(user.uid)] = user
         return HTTPFound(location=request.resource_url(context, 'userlist'))
 
Index: ow/views/user.py
===================================================================
--- ow/views/user.py	(revision 39b14977ea6c9734e86296a94d7560ff9591a1c9)
+++ ow/views/user.py	(revision 1d92bf2b47c1a3bf1482a589cdd6cc7ec38d552f)
@@ -25,6 +25,11 @@
     """
     if request.authenticated_userid:
-        user = request.root.get_user(request.authenticated_userid)
-        return HTTPFound(location=request.resource_url(user))
+        user = request.root.get_user_by_uid(request.authenticated_userid)
+        if user:
+            return HTTPFound(location=request.resource_url(user))
+        else:
+            # an authenticated user session, for an user that does not exist
+            # anymore, logout!
+            return HTTPFound(location=request.resource_url(context, 'logout'))
     return HTTPFound(location=request.resource_url(context, 'login'))
 
@@ -36,5 +41,5 @@
 def login(context, request):
     message = ''
-    username = ''
+    email = ''
     password = ''
     return_to = request.params.get('return_to')
@@ -42,19 +47,20 @@
 
     if 'submit' in request.POST:
-        username = request.POST.get('username', None)
-        if username in request.root.all_usernames():
-            user = request.root[username]
+        email = request.POST.get('email', None)
+        user = context.get_user_by_email(email)
+        if user:
             password = request.POST.get('password', None)
             if password is not None and user.check_password(password):
-                headers = remember(request, username)
+                headers = remember(request, str(user.uid))
+                redirect_url = return_to or request.resource_url(user)
                 return HTTPFound(location=redirect_url, headers=headers)
             else:
-                message = u'Bad password'
+                message = _('Wrong password')
         else:
-            message = u'Bad username'
+            message = _('Wrong email address')
 
     return {
         'message': message,
-        'username': username,
+        'email': email,
         'password': password,
         'redirect_url': redirect_url
@@ -73,12 +79,11 @@
     renderer='ow:templates/signup.pt')
 def signup(context, request):
-    state = State(emails=context.lowercase_emails(),
-                  names=context.lowercase_usernames())
+    state = State(emails=context.lowercase_emails,
+                  names=context.lowercase_nicknames)
     form = Form(request, schema=SignUpSchema(), state=state)
 
     if 'submit' in request.POST and form.validate():
-        username = request.POST['username']
-        user = form.bind(User(), exclude=['username', 'password_confirm'])
-        context[username] = user
+        user = form.bind(User(), exclude=['password_confirm'])
+        context.add_user(user)
         # Send to login
         return HTTPFound(location=request.resource_url(context))
